From af10c45d7b3a44af0b461a557847fe03263dcc10 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 22 Apr 2021 17:06:54 -0700 Subject: Move burn into burn --- src/burn/CustomizedNativeRecommendedRules.ruleset | 8 + src/burn/Directory.Build.props | 26 + src/burn/Directory.Build.targets | 73 + src/burn/Directory.Packages.props | 20 + src/burn/Directory.csproj.props | 13 + src/burn/Directory.vcxproj.props | 118 + src/burn/NativeMultiTargeting.Build.props | 10 + src/burn/README.md | 2 + src/burn/appveyor.cmd | 17 + src/burn/appveyor.yml | 44 + src/burn/burn.sln | 61 + src/burn/engine/EngineForApplication.cpp | 529 ++++ src/burn/engine/EngineForApplication.h | 44 + src/burn/engine/EngineForExtension.cpp | 263 ++ src/burn/engine/EngineForExtension.h | 27 + src/burn/engine/apply.cpp | 3096 +++++++++++++++++++ src/burn/engine/apply.h | 104 + src/burn/engine/approvedexe.cpp | 262 ++ src/burn/engine/approvedexe.h | 67 + src/burn/engine/burnextension.cpp | 264 ++ src/burn/engine/burnextension.h | 61 + src/burn/engine/cabextract.cpp | 974 ++++++ src/burn/engine/cabextract.h | 40 + src/burn/engine/cache.cpp | 2052 +++++++++++++ src/burn/engine/cache.h | 216 ++ src/burn/engine/condition.cpp | 1057 +++++++ src/burn/engine/condition.h | 39 + src/burn/engine/container.cpp | 398 +++ src/burn/engine/container.h | 191 ++ src/burn/engine/core.cpp | 1856 +++++++++++ src/burn/engine/core.h | 218 ++ src/burn/engine/dependency.cpp | 1312 ++++++++ src/burn/engine/dependency.h | 168 + src/burn/engine/detect.cpp | 469 +++ src/burn/engine/detect.h | 44 + src/burn/engine/elevation.cpp | 3239 ++++++++++++++++++++ src/burn/engine/elevation.h | 176 ++ src/burn/engine/embedded.cpp | 197 ++ src/burn/engine/embedded.h | 27 + src/burn/engine/engine.cpp | 992 ++++++ src/burn/engine/engine.mc | 1090 +++++++ src/burn/engine/engine.vcxproj | 186 ++ src/burn/engine/exeengine.cpp | 816 +++++ src/burn/engine/exeengine.h | 50 + src/burn/engine/externalengine.cpp | 805 +++++ src/burn/engine/externalengine.h | 181 ++ src/burn/engine/inc/burnsources.h | 4 + src/burn/engine/inc/engine.h | 27 + src/burn/engine/logging.cpp | 754 +++++ src/burn/engine/logging.h | 153 + src/burn/engine/manifest.cpp | 164 + src/burn/engine/manifest.h | 28 + src/burn/engine/msiengine.cpp | 2035 ++++++++++++ src/burn/engine/msiengine.h | 104 + src/burn/engine/mspengine.cpp | 1197 ++++++++ src/burn/engine/mspengine.h | 84 + src/burn/engine/msuengine.cpp | 529 ++++ src/burn/engine/msuengine.h | 50 + src/burn/engine/netfxchainer.cpp | 418 +++ src/burn/engine/netfxchainer.h | 98 + src/burn/engine/package.cpp | 692 +++++ src/burn/engine/package.h | 380 +++ src/burn/engine/packages.config | 5 + src/burn/engine/payload.cpp | 314 ++ src/burn/engine/payload.h | 107 + src/burn/engine/pipe.cpp | 821 +++++ src/burn/engine/pipe.h | 113 + src/burn/engine/plan.cpp | 2699 ++++++++++++++++ src/burn/engine/plan.h | 456 +++ src/burn/engine/platform.cpp | 16 + src/burn/engine/platform.h | 34 + src/burn/engine/precomp.cpp | 3 + src/burn/engine/precomp.h | 102 + src/burn/engine/pseudobundle.cpp | 241 ++ src/burn/engine/pseudobundle.h | 40 + src/burn/engine/registration.cpp | 1702 ++++++++++ src/burn/engine/registration.h | 225 ++ src/burn/engine/relatedbundle.cpp | 483 +++ src/burn/engine/relatedbundle.h | 20 + src/burn/engine/search.cpp | 1303 ++++++++ src/burn/engine/search.h | 163 + src/burn/engine/section.cpp | 399 +++ src/burn/engine/section.h | 54 + src/burn/engine/splashscreen.cpp | 355 +++ src/burn/engine/splashscreen.h | 31 + src/burn/engine/uithread.cpp | 222 ++ src/burn/engine/uithread.h | 23 + src/burn/engine/update.cpp | 44 + src/burn/engine/update.h | 33 + src/burn/engine/userexperience.cpp | 2653 ++++++++++++++++ src/burn/engine/userexperience.h | 545 ++++ src/burn/engine/variable.cpp | 2323 ++++++++++++++ src/burn/engine/variable.h | 185 ++ src/burn/engine/variant.cpp | 321 ++ src/burn/engine/variant.h | 100 + src/burn/nuget.config | 10 + src/burn/stub/StubSection.cpp | 23 + src/burn/stub/WixToolset.Burn.props | 13 + src/burn/stub/packages.config | 8 + src/burn/stub/precomp.cpp | 3 + src/burn/stub/precomp.h | 17 + src/burn/stub/stub.cpp | 106 + src/burn/stub/stub.ico | Bin 0 -> 2238 bytes src/burn/stub/stub.nuspec | 25 + src/burn/stub/stub.rc | 3 + src/burn/stub/stub.vcxproj | 120 + src/burn/test/BurnUnitTest/AssemblyInfo.cpp | 12 + src/burn/test/BurnUnitTest/BurnTestException.h | 93 + src/burn/test/BurnUnitTest/BurnTestFixture.h | 75 + src/burn/test/BurnUnitTest/BurnUnitTest.h | 48 + src/burn/test/BurnUnitTest/BurnUnitTest.rc | 6 + src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj | 109 + .../test/BurnUnitTest/BurnUnitTest.vcxproj.filters | 80 + src/burn/test/BurnUnitTest/CacheTest.cpp | 119 + src/burn/test/BurnUnitTest/ElevationTest.cpp | 221 ++ src/burn/test/BurnUnitTest/ManifestHelpers.cpp | 41 + src/burn/test/BurnUnitTest/ManifestHelpers.h | 24 + src/burn/test/BurnUnitTest/ManifestTest.cpp | 62 + src/burn/test/BurnUnitTest/PlanTest.cpp | 1473 +++++++++ src/burn/test/BurnUnitTest/RegistrationTest.cpp | 772 +++++ src/burn/test/BurnUnitTest/SearchTest.cpp | 815 +++++ .../TestData/CacheTest/CacheSignatureTest.File | 1 + .../BasicFunctionality_BundleA_manifest.xml | 1 + .../PlanTest/MsiTransaction_BundleAv1_manifest.xml | 1 + .../PlanTest/Slipstream_BundleA_manifest.xml | 1 + src/burn/test/BurnUnitTest/VariableHelpers.cpp | 217 ++ src/burn/test/BurnUnitTest/VariableHelpers.h | 36 + src/burn/test/BurnUnitTest/VariableTest.cpp | 532 ++++ src/burn/test/BurnUnitTest/VariantTest.cpp | 221 ++ src/burn/test/BurnUnitTest/packages.config | 15 + src/burn/test/BurnUnitTest/precomp.cpp | 3 + src/burn/test/BurnUnitTest/precomp.h | 79 + 132 files changed, 50139 insertions(+) create mode 100644 src/burn/CustomizedNativeRecommendedRules.ruleset create mode 100644 src/burn/Directory.Build.props create mode 100644 src/burn/Directory.Build.targets create mode 100644 src/burn/Directory.Packages.props create mode 100644 src/burn/Directory.csproj.props create mode 100644 src/burn/Directory.vcxproj.props create mode 100644 src/burn/NativeMultiTargeting.Build.props create mode 100644 src/burn/README.md create mode 100644 src/burn/appveyor.cmd create mode 100644 src/burn/appveyor.yml create mode 100644 src/burn/burn.sln create mode 100644 src/burn/engine/EngineForApplication.cpp create mode 100644 src/burn/engine/EngineForApplication.h create mode 100644 src/burn/engine/EngineForExtension.cpp create mode 100644 src/burn/engine/EngineForExtension.h create mode 100644 src/burn/engine/apply.cpp create mode 100644 src/burn/engine/apply.h create mode 100644 src/burn/engine/approvedexe.cpp create mode 100644 src/burn/engine/approvedexe.h create mode 100644 src/burn/engine/burnextension.cpp create mode 100644 src/burn/engine/burnextension.h create mode 100644 src/burn/engine/cabextract.cpp create mode 100644 src/burn/engine/cabextract.h create mode 100644 src/burn/engine/cache.cpp create mode 100644 src/burn/engine/cache.h create mode 100644 src/burn/engine/condition.cpp create mode 100644 src/burn/engine/condition.h create mode 100644 src/burn/engine/container.cpp create mode 100644 src/burn/engine/container.h create mode 100644 src/burn/engine/core.cpp create mode 100644 src/burn/engine/core.h create mode 100644 src/burn/engine/dependency.cpp create mode 100644 src/burn/engine/dependency.h create mode 100644 src/burn/engine/detect.cpp create mode 100644 src/burn/engine/detect.h create mode 100644 src/burn/engine/elevation.cpp create mode 100644 src/burn/engine/elevation.h create mode 100644 src/burn/engine/embedded.cpp create mode 100644 src/burn/engine/embedded.h create mode 100644 src/burn/engine/engine.cpp create mode 100644 src/burn/engine/engine.mc create mode 100644 src/burn/engine/engine.vcxproj create mode 100644 src/burn/engine/exeengine.cpp create mode 100644 src/burn/engine/exeengine.h create mode 100644 src/burn/engine/externalengine.cpp create mode 100644 src/burn/engine/externalengine.h create mode 100644 src/burn/engine/inc/burnsources.h create mode 100644 src/burn/engine/inc/engine.h create mode 100644 src/burn/engine/logging.cpp create mode 100644 src/burn/engine/logging.h create mode 100644 src/burn/engine/manifest.cpp create mode 100644 src/burn/engine/manifest.h create mode 100644 src/burn/engine/msiengine.cpp create mode 100644 src/burn/engine/msiengine.h create mode 100644 src/burn/engine/mspengine.cpp create mode 100644 src/burn/engine/mspengine.h create mode 100644 src/burn/engine/msuengine.cpp create mode 100644 src/burn/engine/msuengine.h create mode 100644 src/burn/engine/netfxchainer.cpp create mode 100644 src/burn/engine/netfxchainer.h create mode 100644 src/burn/engine/package.cpp create mode 100644 src/burn/engine/package.h create mode 100644 src/burn/engine/packages.config create mode 100644 src/burn/engine/payload.cpp create mode 100644 src/burn/engine/payload.h create mode 100644 src/burn/engine/pipe.cpp create mode 100644 src/burn/engine/pipe.h create mode 100644 src/burn/engine/plan.cpp create mode 100644 src/burn/engine/plan.h create mode 100644 src/burn/engine/platform.cpp create mode 100644 src/burn/engine/platform.h create mode 100644 src/burn/engine/precomp.cpp create mode 100644 src/burn/engine/precomp.h create mode 100644 src/burn/engine/pseudobundle.cpp create mode 100644 src/burn/engine/pseudobundle.h create mode 100644 src/burn/engine/registration.cpp create mode 100644 src/burn/engine/registration.h create mode 100644 src/burn/engine/relatedbundle.cpp create mode 100644 src/burn/engine/relatedbundle.h create mode 100644 src/burn/engine/search.cpp create mode 100644 src/burn/engine/search.h create mode 100644 src/burn/engine/section.cpp create mode 100644 src/burn/engine/section.h create mode 100644 src/burn/engine/splashscreen.cpp create mode 100644 src/burn/engine/splashscreen.h create mode 100644 src/burn/engine/uithread.cpp create mode 100644 src/burn/engine/uithread.h create mode 100644 src/burn/engine/update.cpp create mode 100644 src/burn/engine/update.h create mode 100644 src/burn/engine/userexperience.cpp create mode 100644 src/burn/engine/userexperience.h create mode 100644 src/burn/engine/variable.cpp create mode 100644 src/burn/engine/variable.h create mode 100644 src/burn/engine/variant.cpp create mode 100644 src/burn/engine/variant.h create mode 100644 src/burn/nuget.config create mode 100644 src/burn/stub/StubSection.cpp create mode 100644 src/burn/stub/WixToolset.Burn.props create mode 100644 src/burn/stub/packages.config create mode 100644 src/burn/stub/precomp.cpp create mode 100644 src/burn/stub/precomp.h create mode 100644 src/burn/stub/stub.cpp create mode 100644 src/burn/stub/stub.ico create mode 100644 src/burn/stub/stub.nuspec create mode 100644 src/burn/stub/stub.rc create mode 100644 src/burn/stub/stub.vcxproj create mode 100644 src/burn/test/BurnUnitTest/AssemblyInfo.cpp create mode 100644 src/burn/test/BurnUnitTest/BurnTestException.h create mode 100644 src/burn/test/BurnUnitTest/BurnTestFixture.h create mode 100644 src/burn/test/BurnUnitTest/BurnUnitTest.h create mode 100644 src/burn/test/BurnUnitTest/BurnUnitTest.rc create mode 100644 src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj create mode 100644 src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters create mode 100644 src/burn/test/BurnUnitTest/CacheTest.cpp create mode 100644 src/burn/test/BurnUnitTest/ElevationTest.cpp create mode 100644 src/burn/test/BurnUnitTest/ManifestHelpers.cpp create mode 100644 src/burn/test/BurnUnitTest/ManifestHelpers.h create mode 100644 src/burn/test/BurnUnitTest/ManifestTest.cpp create mode 100644 src/burn/test/BurnUnitTest/PlanTest.cpp create mode 100644 src/burn/test/BurnUnitTest/RegistrationTest.cpp create mode 100644 src/burn/test/BurnUnitTest/SearchTest.cpp create mode 100644 src/burn/test/BurnUnitTest/TestData/CacheTest/CacheSignatureTest.File create mode 100644 src/burn/test/BurnUnitTest/TestData/PlanTest/BasicFunctionality_BundleA_manifest.xml create mode 100644 src/burn/test/BurnUnitTest/TestData/PlanTest/MsiTransaction_BundleAv1_manifest.xml create mode 100644 src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_manifest.xml create mode 100644 src/burn/test/BurnUnitTest/VariableHelpers.cpp create mode 100644 src/burn/test/BurnUnitTest/VariableHelpers.h create mode 100644 src/burn/test/BurnUnitTest/VariableTest.cpp create mode 100644 src/burn/test/BurnUnitTest/VariantTest.cpp create mode 100644 src/burn/test/BurnUnitTest/packages.config create mode 100644 src/burn/test/BurnUnitTest/precomp.cpp create mode 100644 src/burn/test/BurnUnitTest/precomp.h (limited to 'src/burn') diff --git a/src/burn/CustomizedNativeRecommendedRules.ruleset b/src/burn/CustomizedNativeRecommendedRules.ruleset new file mode 100644 index 00000000..142b141c --- /dev/null +++ b/src/burn/CustomizedNativeRecommendedRules.ruleset @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/burn/Directory.Build.props b/src/burn/Directory.Build.props new file mode 100644 index 00000000..fb34d54e --- /dev/null +++ b/src/burn/Directory.Build.props @@ -0,0 +1,26 @@ + + + + + + Debug + false + + $(MSBuildProjectName) + $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\)) + $(BaseOutputPath)obj\$(ProjectName)\ + $(BaseOutputPath)$(Configuration)\ + + WiX Toolset Team + WiX Toolset + Copyright (c) .NET Foundation and contributors. All rights reserved. + MS-RL + WiX Toolset + + + + + diff --git a/src/burn/Directory.Build.targets b/src/burn/Directory.Build.targets new file mode 100644 index 00000000..44701fb6 --- /dev/null +++ b/src/burn/Directory.Build.targets @@ -0,0 +1,73 @@ + + + + + + $(BaseOutputPath)obj\.tools + $(SigningToolFolder)\SignClient.exe + $(SigningToolFolder)\empty-filelist.txt + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), signing.json))\signing.json + + + + false + $(OutputPath)\$(AssemblyName).xml + + + + + $(PrivateRepositoryUrl.Replace('.git','')) + + $(MSBuildProjectName).nuspec + $(MSBuildProjectDirectory) + $(NuspecProperties);Id=$(PackageId);Authors="$(Authors)";Configuration=$(Configuration);Copyright="$(Copyright)";Description="$(Description)";Title="$(Title)" + $(NuspecProperties);Version=$(NPMPackageVersion);RepositoryCommit=$(SourceRevisionId);RepositoryType=$(RepositoryType);RepositoryUrl=$(PrivateRepositoryUrl);ProjectFolder=$(MSBuildProjectDirectory)\;ProjectUrl=$(ProjectUrl) + true + snupkg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/burn/Directory.Packages.props b/src/burn/Directory.Packages.props new file mode 100644 index 00000000..369c51e7 --- /dev/null +++ b/src/burn/Directory.Packages.props @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/burn/Directory.csproj.props b/src/burn/Directory.csproj.props new file mode 100644 index 00000000..81d24ad1 --- /dev/null +++ b/src/burn/Directory.csproj.props @@ -0,0 +1,13 @@ + + + + + true + true + $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk)) + false + + diff --git a/src/burn/Directory.vcxproj.props b/src/burn/Directory.vcxproj.props new file mode 100644 index 00000000..63d73b36 --- /dev/null +++ b/src/burn/Directory.vcxproj.props @@ -0,0 +1,118 @@ + + + + + + Win32 + $(BaseIntermediateOutputPath)$(Configuration)\$(Platform)\ + $(OutputPath)$(Platform)\ + + + $(Company) + $(Copyright) + + win-x86;win-x64;win-arm64 + native,Version=v0.0 + + + + $([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0')) + + + + $(MSBuildThisFileDirectory)CustomizedNativeRecommendedRules.ruleset + + + + + $(DisableSpecificCompilerWarnings) + Level4 + $(ProjectDir)inc;$(MSBuildProjectDirectory);$(IntDir);$(SqlCESdkIncludePath);$(ProjectAdditionalIncludeDirectories);%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;_WIN32_MSI=500;_WIN32_WINNT=0x0600;$(ArmPreprocessorDefinitions);$(UnicodePreprocessorDefinitions);_CRT_STDIO_LEGACY_WIDE_SPECIFIERS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) + Use + precomp.h + StdCall + true + false + Guard + -YlprecompDefine + /Zc:threadSafeInit- %(AdditionalOptions) + true + + + $(ArmPreprocessorDefinitions);%(PreprocessorDefinitions) + $(ProjectAdditionalResourceIncludeDirectories);%(AdditionalIncludeDirectories) + + + $(OutDir);$(AdditionalMultiTargetLibraryPath);$(ProjectAdditionalLibraryDirectories);%(AdditionalLibraryDirectories) + + + $(ProjectSubSystem) + $(ProjectModuleDefinitionFile) + $(ResourceOnlyDll) + true + $(ProjectAdditionalLinkLibraries);advapi32.lib;comdlg32.lib;user32.lib;oleaut32.lib;gdi32.lib;shell32.lib;ole32.lib;version.lib;%(AdditionalDependencies) + $(OutDir);$(AdditionalMultiTargetLibraryPath);$(ArmLibraryDirectories);$(ProjectAdditionalLinkLibraryDirectories);%(AdditionalLibraryDirectories) + /IGNORE:4099 %(AdditionalOptions) + + + + + + NoExtensions + + + + + CDecl + + + + + OldStyle + true + true + + + + + Disabled + EnableFastChecks + _DEBUG;DEBUG;%(PreprocessorDefinitions) + MultiThreadedDebug + + + + + + + MultiThreadedDebugDll + + + + + MinSpace + NDEBUG;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + true + true + + + + + + + MultiThreadedDll + + + + + $(LinkKeyFile) + $(LinkDelaySign) + + + diff --git a/src/burn/NativeMultiTargeting.Build.props b/src/burn/NativeMultiTargeting.Build.props new file mode 100644 index 00000000..1ff46559 --- /dev/null +++ b/src/burn/NativeMultiTargeting.Build.props @@ -0,0 +1,10 @@ + + + + + + + $(BaseIntermediateOutputPath)$(Configuration)\$(PlatformToolset)\$(PlatformTarget)\ + $(OutputPath)$(PlatformToolset)\$(PlatformTarget)\ + + diff --git a/src/burn/README.md b/src/burn/README.md new file mode 100644 index 00000000..a564d971 --- /dev/null +++ b/src/burn/README.md @@ -0,0 +1,2 @@ +# burn +burn.lib - Burn engine diff --git a/src/burn/appveyor.cmd b/src/burn/appveyor.cmd new file mode 100644 index 00000000..a35e10d5 --- /dev/null +++ b/src/burn/appveyor.cmd @@ -0,0 +1,17 @@ +@setlocal +@pushd %~dp0 +@set _C=Release +@if /i "%1"=="debug" set _C=Debug + +nuget restore || exit /b + +msbuild -t:Test -p:Configuration=%_C% src\test\BurnUnitTest || exit /b + +msbuild -p:Configuration=%_C%;Platform=x86 || exit /b +msbuild -p:Configuration=%_C%;Platform=x64 || exit /b +msbuild -p:Configuration=%_C%;Platform=arm64 || exit /b + +msbuild -p:Configuration=%_C% -t:PackNative src\stub\stub.vcxproj || exit /b + +@popd +@endlocal \ No newline at end of file diff --git a/src/burn/appveyor.yml b/src/burn/appveyor.yml new file mode 100644 index 00000000..364569cf --- /dev/null +++ b/src/burn/appveyor.yml @@ -0,0 +1,44 @@ +# Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. +# +# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml +# then update all of the repos. + +branches: + only: + - master + - develop + +image: Visual Studio 2019 + +version: 0.0.0.{build} +configuration: Release + +environment: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + NUGET_XMLDOC_MODE: skip + +build_script: + - appveyor.cmd + +test: off + +pull_requests: + do_not_increment_build_number: true + +nuget: + disable_publish_on_pr: true + +skip_branch_with_pr: true +skip_tags: true + +artifacts: +- path: build\Release\**\*.nupkg + name: nuget +- path: build\Release\**\*.snupkg + name: snupkg + +notifications: +- provider: Slack + incoming_webhook: + secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA= diff --git a/src/burn/burn.sln b/src/burn/burn.sln new file mode 100644 index 00000000..6a64b8f0 --- /dev/null +++ b/src/burn/burn.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30711.63 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "engine", "src\engine\engine.vcxproj", "{8119537D-E1D9-6591-D51A-49768A2F9C37}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stub", "src\stub\stub.vcxproj", "{C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BurnUnitTest", "src\test\BurnUnitTest\BurnUnitTest.vcxproj", "{9D1F1BA3-9393-4833-87A3-D5F1FC08EF67}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Debug|ARM64.Build.0 = Debug|ARM64 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Debug|x64.ActiveCfg = Debug|x64 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Debug|x64.Build.0 = Debug|x64 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Debug|x86.ActiveCfg = Debug|Win32 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Debug|x86.Build.0 = Debug|Win32 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Release|ARM64.ActiveCfg = Release|ARM64 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Release|ARM64.Build.0 = Release|ARM64 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Release|x64.ActiveCfg = Release|x64 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Release|x64.Build.0 = Release|x64 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Release|x86.ActiveCfg = Release|Win32 + {8119537D-E1D9-6591-D51A-49768A2F9C37}.Release|x86.Build.0 = Release|Win32 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Debug|ARM64.Build.0 = Debug|ARM64 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Debug|x64.ActiveCfg = Debug|x64 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Debug|x64.Build.0 = Debug|x64 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Debug|x86.ActiveCfg = Debug|Win32 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Debug|x86.Build.0 = Debug|Win32 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Release|ARM64.ActiveCfg = Release|ARM64 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Release|ARM64.Build.0 = Release|ARM64 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Release|x64.ActiveCfg = Release|x64 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Release|x64.Build.0 = Release|x64 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Release|x86.ActiveCfg = Release|Win32 + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1}.Release|x86.Build.0 = Release|Win32 + {9D1F1BA3-9393-4833-87A3-D5F1FC08EF67}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {9D1F1BA3-9393-4833-87A3-D5F1FC08EF67}.Debug|x64.ActiveCfg = Debug|Win32 + {9D1F1BA3-9393-4833-87A3-D5F1FC08EF67}.Debug|x86.ActiveCfg = Debug|Win32 + {9D1F1BA3-9393-4833-87A3-D5F1FC08EF67}.Debug|x86.Build.0 = Debug|Win32 + {9D1F1BA3-9393-4833-87A3-D5F1FC08EF67}.Release|ARM64.ActiveCfg = Release|ARM64 + {9D1F1BA3-9393-4833-87A3-D5F1FC08EF67}.Release|x64.ActiveCfg = Release|Win32 + {9D1F1BA3-9393-4833-87A3-D5F1FC08EF67}.Release|x86.ActiveCfg = Release|Win32 + {9D1F1BA3-9393-4833-87A3-D5F1FC08EF67}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A35910C5-8A89-473E-9578-E084172DD3C9} + EndGlobalSection +EndGlobal diff --git a/src/burn/engine/EngineForApplication.cpp b/src/burn/engine/EngineForApplication.cpp new file mode 100644 index 00000000..83d88ba1 --- /dev/null +++ b/src/burn/engine/EngineForApplication.cpp @@ -0,0 +1,529 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +static HRESULT BAEngineGetPackageCount( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_GETPACKAGECOUNT_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_GETPACKAGECOUNT_RESULTS, pResults); + + ExternalEngineGetPackageCount(pContext->pEngineState, &pResults->cPackages); + +LExit: + return hr; +} + +static HRESULT BAEngineGetVariableNumeric( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_GETVARIABLENUMERIC_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_GETVARIABLENUMERIC_RESULTS, pResults); + + hr = ExternalEngineGetVariableNumeric(pContext->pEngineState, pArgs->wzVariable, &pResults->llValue); + +LExit: + return hr; +} + +static HRESULT BAEngineGetVariableString( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_GETVARIABLESTRING_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_GETVARIABLESTRING_RESULTS, pResults); + + hr = ExternalEngineGetVariableString(pContext->pEngineState, pArgs->wzVariable, pResults->wzValue, &pResults->cchValue); + +LExit: + return hr; +} + +static HRESULT BAEngineGetVariableVersion( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_GETVARIABLEVERSION_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_GETVARIABLEVERSION_RESULTS, pResults); + + hr = ExternalEngineGetVariableVersion(pContext->pEngineState, pArgs->wzVariable, pResults->wzValue, &pResults->cchValue); + +LExit: + return hr; +} + +static HRESULT BAEngineFormatString( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_FORMATSTRING_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_FORMATSTRING_RESULTS, pResults); + + hr = ExternalEngineFormatString(pContext->pEngineState, pArgs->wzIn, pResults->wzOut, &pResults->cchOut); + +LExit: + return hr; +} + +static HRESULT BAEngineEscapeString( + __in BOOTSTRAPPER_ENGINE_CONTEXT* /*pContext*/, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_ESCAPESTRING_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_ESCAPESTRING_RESULTS, pResults); + + hr = ExternalEngineEscapeString(pArgs->wzIn, pResults->wzOut, &pResults->cchOut); + +LExit: + return hr; +} + +static HRESULT BAEngineEvaluateCondition( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_EVALUATECONDITION_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_EVALUATECONDITION_RESULTS, pResults); + + hr = ExternalEngineEvaluateCondition(pContext->pEngineState, pArgs->wzCondition, &pResults->f); + +LExit: + return hr; +} + +static HRESULT BAEngineLog( + __in BOOTSTRAPPER_ENGINE_CONTEXT* /*pContext*/, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + REPORT_LEVEL rl = REPORT_NONE; + ValidateMessageArgs(hr, pvArgs, BAENGINE_LOG_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_LOG_RESULTS, pResults); + + switch (pArgs->level) + { + case BOOTSTRAPPER_LOG_LEVEL_STANDARD: + rl = REPORT_STANDARD; + break; + + case BOOTSTRAPPER_LOG_LEVEL_VERBOSE: + rl = REPORT_VERBOSE; + break; + + case BOOTSTRAPPER_LOG_LEVEL_DEBUG: + rl = REPORT_DEBUG; + break; + + case BOOTSTRAPPER_LOG_LEVEL_ERROR: + rl = REPORT_ERROR; + break; + + default: + ExitFunction1(hr = E_INVALIDARG); + } + + hr = ExternalEngineLog(rl, pArgs->wzMessage); + ExitOnFailure(hr, "Failed to log BA message."); + +LExit: + return hr; +} + +static HRESULT BAEngineSendEmbeddedError( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_SENDEMBEDDEDERROR_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_SENDEMBEDDEDERROR_RESULTS, pResults); + + hr = ExternalEngineSendEmbeddedError(pContext->pEngineState, pArgs->dwErrorCode, pArgs->wzMessage, pArgs->dwUIHint, &pResults->nResult); + +LExit: + return hr; +} + +static HRESULT BAEngineSendEmbeddedProgress( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_SENDEMBEDDEDPROGRESS_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_SENDEMBEDDEDPROGRESS_RESULTS, pResults); + + hr = ExternalEngineSendEmbeddedProgress(pContext->pEngineState, pArgs->dwProgressPercentage, pArgs->dwOverallProgressPercentage, &pResults->nResult); + +LExit: + return hr; +} + +static HRESULT BAEngineSetUpdate( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_SETUPDATE_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_SETUPDATE_RESULTS, pResults); + + hr = ExternalEngineSetUpdate(pContext->pEngineState, pArgs->wzLocalSource, pArgs->wzDownloadSource, pArgs->qwSize, pArgs->hashType, pArgs->rgbHash, pArgs->cbHash); + +LExit: + return hr; +} + +static HRESULT BAEngineSetLocalSource( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_SETLOCALSOURCE_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_SETLOCALSOURCE_RESULTS, pResults); + + hr = ExternalEngineSetLocalSource(pContext->pEngineState, pArgs->wzPackageOrContainerId, pArgs->wzPayloadId, pArgs->wzPath); + +LExit: + return hr; +} + +static HRESULT BAEngineSetDownloadSource( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_SETDOWNLOADSOURCE_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_SETDOWNLOADSOURCE_RESULTS, pResults); + + hr = ExternalEngineSetDownloadSource(pContext->pEngineState, pArgs->wzPackageOrContainerId, pArgs->wzPayloadId, pArgs->wzUrl, pArgs->wzUser, pArgs->wzPassword); + +LExit: + return hr; +} + +static HRESULT BAEngineSetVariableNumeric( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_SETVARIABLENUMERIC_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_SETVARIABLENUMERIC_RESULTS, pResults); + + hr = ExternalEngineSetVariableNumeric(pContext->pEngineState, pArgs->wzVariable, pArgs->llValue); + +LExit: + return hr; +} + +static HRESULT BAEngineSetVariableString( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_SETVARIABLESTRING_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_SETVARIABLESTRING_RESULTS, pResults); + + hr = ExternalEngineSetVariableString(pContext->pEngineState, pArgs->wzVariable, pArgs->wzValue, pArgs->fFormatted); + +LExit: + return hr; +} + +static HRESULT BAEngineSetVariableVersion( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_SETVARIABLEVERSION_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_SETVARIABLEVERSION_RESULTS, pResults); + + hr = ExternalEngineSetVariableVersion(pContext->pEngineState, pArgs->wzVariable, pArgs->wzValue); + +LExit: + return hr; +} + +static HRESULT BAEngineCloseSplashScreen( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_CLOSESPLASHSCREEN_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_CLOSESPLASHSCREEN_RESULTS, pResults); + + ExternalEngineCloseSplashScreen(pContext->pEngineState); + +LExit: + return hr; +} + +static HRESULT BAEngineCompareVersions( + __in BOOTSTRAPPER_ENGINE_CONTEXT* /*pContext*/, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_COMPAREVERSIONS_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_COMPAREVERSIONS_RESULTS, pResults); + + hr = ExternalEngineCompareVersions(pArgs->wzVersion1, pArgs->wzVersion2, &pResults->nResult); + +LExit: + return hr; +} + +static HRESULT BAEngineDetect( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_DETECT_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_DETECT_RESULTS, pResults); + + hr = ExternalEngineDetect(pContext->dwThreadId, pArgs->hwndParent); + +LExit: + return hr; +} + +static HRESULT BAEnginePlan( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_PLAN_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_PLAN_RESULTS, pResults); + + hr = ExternalEnginePlan(pContext->dwThreadId, pArgs->action); + +LExit: + return hr; +} + +static HRESULT BAEngineElevate( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_ELEVATE_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_ELEVATE_RESULTS, pResults); + + hr = ExternalEngineElevate(pContext->pEngineState, pContext->dwThreadId, pArgs->hwndParent); + +LExit: + return hr; +} + +static HRESULT BAEngineApply( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_APPLY_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_APPLY_RESULTS, pResults); + + hr = ExternalEngineApply(pContext->dwThreadId, pArgs->hwndParent); + +LExit: + return hr; +} + +static HRESULT BAEngineQuit( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_QUIT_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_QUIT_RESULTS, pResults); + + hr = ExternalEngineQuit(pContext->dwThreadId, pArgs->dwExitCode); + +LExit: + return hr; +} + +static HRESULT BAEngineLaunchApprovedExe( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_LAUNCHAPPROVEDEXE_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_LAUNCHAPPROVEDEXE_RESULTS, pResults); + + hr = ExternalEngineLaunchApprovedExe(pContext->pEngineState, pContext->dwThreadId, pArgs->hwndParent, pArgs->wzApprovedExeForElevationId, pArgs->wzArguments, pArgs->dwWaitForInputIdleTimeout); + +LExit: + return hr; +} + +static HRESULT BAEngineSetUpdateSource( + __in BOOTSTRAPPER_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BAENGINE_SETUPDATESOURCE_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BAENGINE_SETUPDATESOURCE_RESULTS, pResults); + + hr = ExternalEngineSetUpdateSource(pContext->pEngineState, pArgs->wzUrl); + +LExit: + return hr; +} + +HRESULT WINAPI EngineForApplicationProc( + __in BOOTSTRAPPER_ENGINE_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults, + __in_opt LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + BOOTSTRAPPER_ENGINE_CONTEXT* pContext = reinterpret_cast(pvContext); + + if (!pContext || !pvArgs || !pvResults) + { + ExitFunction1(hr = E_INVALIDARG); + } + + switch (message) + { + case BOOTSTRAPPER_ENGINE_MESSAGE_GETPACKAGECOUNT: + hr = BAEngineGetPackageCount(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_GETVARIABLENUMERIC: + hr = BAEngineGetVariableNumeric(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_GETVARIABLESTRING: + hr = BAEngineGetVariableString(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_GETVARIABLEVERSION: + hr = BAEngineGetVariableVersion(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_FORMATSTRING: + hr = BAEngineFormatString(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_ESCAPESTRING: + hr = BAEngineEscapeString(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_EVALUATECONDITION: + hr = BAEngineEvaluateCondition(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_LOG: + hr = BAEngineLog(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_SENDEMBEDDEDERROR: + hr = BAEngineSendEmbeddedError(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_SENDEMBEDDEDPROGRESS: + hr = BAEngineSendEmbeddedProgress(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_SETUPDATE: + hr = BAEngineSetUpdate(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_SETLOCALSOURCE: + hr = BAEngineSetLocalSource(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_SETDOWNLOADSOURCE: + hr = BAEngineSetDownloadSource(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_SETVARIABLENUMERIC: + hr = BAEngineSetVariableNumeric(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_SETVARIABLESTRING: + hr = BAEngineSetVariableString(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_SETVARIABLEVERSION: + hr = BAEngineSetVariableVersion(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_CLOSESPLASHSCREEN: + hr = BAEngineCloseSplashScreen(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_DETECT: + hr = BAEngineDetect(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_PLAN: + hr = BAEnginePlan(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_ELEVATE: + hr = BAEngineElevate(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_APPLY: + hr = BAEngineApply(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_QUIT: + hr = BAEngineQuit(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_LAUNCHAPPROVEDEXE: + hr = BAEngineLaunchApprovedExe(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_SETUPDATESOURCE: + hr = BAEngineSetUpdateSource(pContext, pvArgs, pvResults); + break; + case BOOTSTRAPPER_ENGINE_MESSAGE_COMPAREVERSIONS: + hr = BAEngineCompareVersions(pContext, pvArgs, pvResults); + break; + default: + hr = E_NOTIMPL; + break; + } + +LExit: + return hr; +} diff --git a/src/burn/engine/EngineForApplication.h b/src/burn/engine/EngineForApplication.h new file mode 100644 index 00000000..e5e8f6d7 --- /dev/null +++ b/src/burn/engine/EngineForApplication.h @@ -0,0 +1,44 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + +// constants + +enum WM_BURN +{ + WM_BURN_FIRST = WM_APP + 0xFFF, // this enum value must always be first. + + WM_BURN_DETECT, + WM_BURN_PLAN, + WM_BURN_ELEVATE, + WM_BURN_APPLY, + WM_BURN_LAUNCH_APPROVED_EXE, + WM_BURN_QUIT, + + WM_BURN_LAST, // this enum value must always be last. +}; + +// structs + +typedef struct _BOOTSTRAPPER_ENGINE_CONTEXT +{ + BURN_ENGINE_STATE* pEngineState; + DWORD dwThreadId; +} BOOTSTRAPPER_ENGINE_CONTEXT; + +// function declarations + +HRESULT WINAPI EngineForApplicationProc( + __in BOOTSTRAPPER_ENGINE_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults, + __in_opt LPVOID pvContext + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/EngineForExtension.cpp b/src/burn/engine/EngineForExtension.cpp new file mode 100644 index 00000000..2e1c98fd --- /dev/null +++ b/src/burn/engine/EngineForExtension.cpp @@ -0,0 +1,263 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +static HRESULT BEEngineEscapeString( + __in BURN_EXTENSION_ENGINE_CONTEXT* /*pContext*/, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_ESCAPESTRING_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_ESCAPESTRING_RESULTS, pResults); + + hr = ExternalEngineEscapeString(pArgs->wzIn, pResults->wzOut, &pResults->cchOut); + +LExit: + return hr; +} + +static HRESULT BEEngineEvaluateCondition( + __in BURN_EXTENSION_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_EVALUATECONDITION_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_EVALUATECONDITION_RESULTS, pResults); + + hr = ExternalEngineEvaluateCondition(pContext->pEngineState, pArgs->wzCondition, &pResults->f); + +LExit: + return hr; +} + +static HRESULT BEEngineFormatString( + __in BURN_EXTENSION_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_FORMATSTRING_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_FORMATSTRING_RESULTS, pResults); + + hr = ExternalEngineFormatString(pContext->pEngineState, pArgs->wzIn, pResults->wzOut, &pResults->cchOut); + +LExit: + return hr; +} + +static HRESULT BEEngineGetVariableNumeric( + __in BURN_EXTENSION_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_GETVARIABLENUMERIC_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_GETVARIABLENUMERIC_RESULTS, pResults); + + hr = ExternalEngineGetVariableNumeric(pContext->pEngineState, pArgs->wzVariable, &pResults->llValue); + +LExit: + return hr; +} + +static HRESULT BEEngineGetVariableString( + __in BURN_EXTENSION_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_GETVARIABLESTRING_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_GETVARIABLESTRING_RESULTS, pResults); + + hr = ExternalEngineGetVariableString(pContext->pEngineState, pArgs->wzVariable, pResults->wzValue, &pResults->cchValue); + +LExit: + return hr; +} + +static HRESULT BEEngineGetVariableVersion( + __in BURN_EXTENSION_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_GETVARIABLEVERSION_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_GETVARIABLEVERSION_RESULTS, pResults); + + hr = ExternalEngineGetVariableVersion(pContext->pEngineState, pArgs->wzVariable, pResults->wzValue, &pResults->cchValue); + +LExit: + return hr; +} + +static HRESULT BEEngineLog( + __in BURN_EXTENSION_ENGINE_CONTEXT* /*pContext*/, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + REPORT_LEVEL rl = REPORT_NONE; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_LOG_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_LOG_RESULTS, pResults); + + switch (pArgs->level) + { + case BUNDLE_EXTENSION_LOG_LEVEL_STANDARD: + rl = REPORT_STANDARD; + break; + + case BUNDLE_EXTENSION_LOG_LEVEL_VERBOSE: + rl = REPORT_VERBOSE; + break; + + case BUNDLE_EXTENSION_LOG_LEVEL_DEBUG: + rl = REPORT_DEBUG; + break; + + case BUNDLE_EXTENSION_LOG_LEVEL_ERROR: + rl = REPORT_ERROR; + break; + + default: + ExitFunction1(hr = E_INVALIDARG); + } + + hr = ExternalEngineLog(rl, pArgs->wzMessage); + ExitOnFailure(hr, "Failed to log Bundle Extension message."); + +LExit: + return hr; +} + +static HRESULT BEEngineSetVariableNumeric( + __in BURN_EXTENSION_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_SETVARIABLENUMERIC_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_SETVARIABLENUMERIC_RESULTS, pResults); + + hr = ExternalEngineSetVariableNumeric(pContext->pEngineState, pArgs->wzVariable, pArgs->llValue); + +LExit: + return hr; +} + +static HRESULT BEEngineSetVariableString( + __in BURN_EXTENSION_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_SETVARIABLESTRING_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_SETVARIABLESTRING_RESULTS, pResults); + + hr = ExternalEngineSetVariableString(pContext->pEngineState, pArgs->wzVariable, pArgs->wzValue, pArgs->fFormatted); + +LExit: + return hr; +} + +static HRESULT BEEngineSetVariableVersion( + __in BURN_EXTENSION_ENGINE_CONTEXT* pContext, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_SETVARIABLEVERSION_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_SETVARIABLEVERSION_RESULTS, pResults); + + hr = ExternalEngineSetVariableVersion(pContext->pEngineState, pArgs->wzVariable, pArgs->wzValue); + +LExit: + return hr; +} + +static HRESULT BEEngineCompareVersions( + __in BURN_EXTENSION_ENGINE_CONTEXT* /*pContext*/, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + ValidateMessageArgs(hr, pvArgs, BUNDLE_EXTENSION_ENGINE_COMPAREVERSIONS_ARGS, pArgs); + ValidateMessageResults(hr, pvResults, BUNDLE_EXTENSION_ENGINE_COMPAREVERSIONS_RESULTS, pResults); + + hr = ExternalEngineCompareVersions(pArgs->wzVersion1, pArgs->wzVersion2, &pResults->nResult); + +LExit: + return hr; +} + +HRESULT WINAPI EngineForExtensionProc( + __in BUNDLE_EXTENSION_ENGINE_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults, + __in_opt LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + BURN_EXTENSION_ENGINE_CONTEXT* pContext = reinterpret_cast(pvContext); + + if (!pContext || !pvArgs || !pvResults) + { + ExitFunction1(hr = E_INVALIDARG); + } + + switch (message) + { + case BUNDLE_EXTENSION_ENGINE_MESSAGE_ESCAPESTRING: + hr = BEEngineEscapeString(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_EVALUATECONDITION: + hr = BEEngineEvaluateCondition(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_FORMATSTRING: + hr = BEEngineFormatString(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_GETVARIABLENUMERIC: + hr = BEEngineGetVariableNumeric(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_GETVARIABLESTRING: + hr = BEEngineGetVariableString(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_GETVARIABLEVERSION: + hr = BEEngineGetVariableVersion(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_LOG: + hr = BEEngineLog(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_SETVARIABLENUMERIC: + hr = BEEngineSetVariableNumeric(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_SETVARIABLESTRING: + hr = BEEngineSetVariableString(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_SETVARIABLEVERSION: + hr = BEEngineSetVariableVersion(pContext, pvArgs, pvResults); + break; + case BUNDLE_EXTENSION_ENGINE_MESSAGE_COMPAREVERSIONS: + hr = BEEngineCompareVersions(pContext, pvArgs, pvResults); + break; + default: + hr = E_NOTIMPL; + break; + } + +LExit: + return hr; +} diff --git a/src/burn/engine/EngineForExtension.h b/src/burn/engine/EngineForExtension.h new file mode 100644 index 00000000..bad5f08a --- /dev/null +++ b/src/burn/engine/EngineForExtension.h @@ -0,0 +1,27 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + +// structs + +typedef struct _BURN_EXTENSION_ENGINE_CONTEXT +{ + BURN_ENGINE_STATE* pEngineState; +} BURN_EXTENSION_ENGINE_CONTEXT; + +// function declarations + +HRESULT WINAPI EngineForExtensionProc( + __in BUNDLE_EXTENSION_ENGINE_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults, + __in_opt LPVOID pvContext + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp new file mode 100644 index 00000000..58d41b28 --- /dev/null +++ b/src/burn/engine/apply.cpp @@ -0,0 +1,3096 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +#ifdef DEBUG + #define IgnoreRollbackError(x, f, ...) if (FAILED(x)) { TraceError(x, f, __VA_ARGS__); } +#else + #define IgnoreRollbackError(x, f, ...) +#endif + +const DWORD BURN_CACHE_MAX_RECOMMENDED_VERIFY_TRYAGAIN_ATTEMPTS = 2; + +enum BURN_CACHE_PROGRESS_TYPE +{ + BURN_CACHE_PROGRESS_TYPE_ACQUIRE, + BURN_CACHE_PROGRESS_TYPE_CONTAINER_OR_PAYLOAD_VERIFY, + BURN_CACHE_PROGRESS_TYPE_EXTRACT, + BURN_CACHE_PROGRESS_TYPE_FINALIZE, + BURN_CACHE_PROGRESS_TYPE_HASH, + BURN_CACHE_PROGRESS_TYPE_PAYLOAD_VERIFY, + BURN_CACHE_PROGRESS_TYPE_STAGE, +}; + +// structs + +typedef struct _BURN_CACHE_CONTEXT +{ + BURN_USER_EXPERIENCE* pUX; + BURN_VARIABLES* pVariables; + BURN_PAYLOADS* pPayloads; + HANDLE hPipe; + HANDLE hSourceEngineFile; + DWORD64 qwTotalCacheSize; + DWORD64 qwSuccessfulCacheProgress; + LPCWSTR wzLayoutDirectory; + LPWSTR* rgSearchPaths; + DWORD cSearchPaths; + DWORD cSearchPathsMax; + LPWSTR sczLastUsedFolderCandidate; +} BURN_CACHE_CONTEXT; + +typedef struct _BURN_CACHE_PROGRESS_CONTEXT +{ + BURN_CACHE_CONTEXT* pCacheContext; + BURN_CACHE_PROGRESS_TYPE type; + BURN_CONTAINER* pContainer; + BURN_PACKAGE* pPackage; + BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem; + BURN_PAYLOAD* pPayload; + + BOOL fCancel; + HRESULT hrError; +} BURN_CACHE_PROGRESS_CONTEXT; + +typedef struct _BURN_EXECUTE_CONTEXT +{ + BURN_USER_EXPERIENCE* pUX; + BOOL fRollback; + BURN_PACKAGE* pExecutingPackage; + DWORD cExecutedPackages; + DWORD cExecutePackagesTotal; + DWORD* pcOverallProgressTicks; +} BURN_EXECUTE_CONTEXT; + + +// internal function declarations +static HRESULT WINAPI AuthenticationRequired( + __in LPVOID pData, + __in HINTERNET hUrl, + __in long lHttpCode, + __out BOOL* pfRetrySend, + __out BOOL* pfRetry + ); + +static void CalculateKeepRegistration( + __in BURN_ENGINE_STATE* pEngineState, + __inout BOOL* pfKeepRegistration + ); +static HRESULT ExecuteDependentRegistrationActions( + __in HANDLE hPipe, + __in const BURN_REGISTRATION* pRegistration, + __in_ecount(cActions) const BURN_DEPENDENT_REGISTRATION_ACTION* rgActions, + __in DWORD cActions + ); +static HRESULT ApplyCachePackage( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_PACKAGE* pPackage + ); +static HRESULT ApplyExtractContainer( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_CONTAINER* pContainer + ); +static HRESULT ApplyLayoutBundle( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_PAYLOAD_GROUP* pPayloads, + __in_z LPCWSTR wzExecutableName, + __in_z LPCWSTR wzUnverifiedPath, + __in DWORD64 qwBundleSize + ); +static HRESULT ApplyLayoutContainer( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_CONTAINER* pContainer + ); +static HRESULT ApplyProcessPayload( + __in BURN_CACHE_CONTEXT* pContext, + __in_opt BURN_PACKAGE* pPackage, + __in BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem + ); +static HRESULT ApplyCacheVerifyContainerOrPayload( + __in BURN_CACHE_CONTEXT* pContext, + __in_opt BURN_CONTAINER* pContainer, + __in_opt BURN_PACKAGE* pPackage, + __in_opt BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem + ); +static HRESULT ExtractContainer( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_CONTAINER* pContainer + ); +static HRESULT LayoutBundle( + __in BURN_CACHE_CONTEXT* pContext, + __in_z LPCWSTR wzExecutableName, + __in_z LPCWSTR wzUnverifiedPath, + __in DWORD64 qwBundleSize + ); +static HRESULT ApplyAcquireContainerOrPayload( + __in BURN_CACHE_CONTEXT* pContext, + __in_opt BURN_CONTAINER* pContainer, + __in_opt BURN_PACKAGE* pPackage, + __in_opt BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem + ); +static HRESULT AcquireContainerOrPayload( + __in BURN_CACHE_PROGRESS_CONTEXT* pProgress, + __out BOOL* pfRetry + ); +static BOOL IsValidLocalFile( + __in_z LPCWSTR wzFilePath, + __in DWORD64 qwFileSize, + __in BOOL fMinimumFileSize + ); +static HRESULT LayoutOrCacheContainerOrPayload( + __in BURN_CACHE_CONTEXT* pContext, + __in_opt BURN_CONTAINER* pContainer, + __in_opt BURN_PACKAGE* pPackage, + __in_opt BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem, + __in DWORD cTryAgainAttempts, + __out BOOL* pfRetry + ); +static HRESULT PreparePayloadDestinationPath( + __in_z LPCWSTR wzDestinationPath + ); +static HRESULT CopyPayload( + __in BURN_CACHE_PROGRESS_CONTEXT* pProgress, + __in HANDLE hSourceFile, + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzDestinationPath + ); +static HRESULT DownloadPayload( + __in BURN_CACHE_PROGRESS_CONTEXT* pProgress, + __in_z LPCWSTR wzDestinationPath + ); +static HRESULT CALLBACK CacheMessageHandler( + __in BURN_CACHE_MESSAGE* pMessage, + __in LPVOID pvContext + ); +static HRESULT CompleteCacheProgress( + __in BURN_CACHE_PROGRESS_CONTEXT* pContext, + __in DWORD64 qwFileSize + ); +static DWORD CALLBACK CacheProgressRoutine( + __in LARGE_INTEGER TotalFileSize, + __in LARGE_INTEGER TotalBytesTransferred, + __in LARGE_INTEGER StreamSize, + __in LARGE_INTEGER StreamBytesTransferred, + __in DWORD dwStreamNumber, + __in DWORD dwCallbackReason, + __in HANDLE hSourceFile, + __in HANDLE hDestinationFile, + __in_opt LPVOID lpData + ); +static void DoRollbackCache( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PLAN* pPlan, + __in HANDLE hPipe, + __in DWORD dwCheckpoint + ); +static HRESULT DoExecuteAction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in_opt HANDLE hCacheThread, + __in BURN_EXECUTE_CONTEXT* pContext, + __inout BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary, + __inout BURN_EXECUTE_ACTION_CHECKPOINT** ppCheckpoint, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +static HRESULT DoRollbackActions( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_CONTEXT* pContext, + __in DWORD dwCheckpoint, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +static HRESULT ExecuteExePackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __in BOOL fRollback, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +static HRESULT ExecuteMsiPackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __in BOOL fInsideMsiTransaction, + __in BOOL fRollback, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +static HRESULT ExecuteMspPackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __in BOOL fInsideMsiTransaction, + __in BOOL fRollback, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +static HRESULT ExecuteMsuPackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __in BOOL fRollback, + __in BOOL fStopWusaService, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +static HRESULT ExecutePackageProviderAction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pAction, + __in BURN_EXECUTE_CONTEXT* pContext + ); +static HRESULT ExecuteDependencyAction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pAction, + __in BURN_EXECUTE_CONTEXT* pContext + ); +static HRESULT ExecuteMsiBeginTransaction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary, + __in BURN_EXECUTE_CONTEXT* pContext + ); +static HRESULT ExecuteMsiCommitTransaction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary, + __in BURN_EXECUTE_CONTEXT* pContext + ); +static HRESULT ExecuteMsiRollbackTransaction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary, + __in BURN_EXECUTE_CONTEXT* pContext + ); +static void ResetTransactionRegistrationState( + __in BURN_ENGINE_STATE* pEngineState, + __in BOOL fCommit + ); +static HRESULT CleanPackage( + __in HANDLE hElevatedPipe, + __in BURN_PACKAGE* pPackage + ); +static int GenericExecuteMessageHandler( + __in GENERIC_EXECUTE_MESSAGE* pMessage, + __in LPVOID pvContext + ); +static int MsiExecuteMessageHandler( + __in WIU_MSI_EXECUTE_MESSAGE* pMessage, + __in_opt LPVOID pvContext + ); +static HRESULT ReportOverallProgressTicks( + __in BURN_USER_EXPERIENCE* pUX, + __in BOOL fRollback, + __in DWORD cOverallProgressTicksTotal, + __in DWORD cOverallProgressTicks + ); +static HRESULT ExecutePackageComplete( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_VARIABLES* pVariables, + __in BURN_PACKAGE* pPackage, + __in HRESULT hrOverall, + __in HRESULT hrExecute, + __in BOOL fRollback, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart, + __out BOOL* pfRetry, + __out BOOL* pfSuspend + ); + + +// function definitions + +extern "C" void ApplyInitialize() +{ + // Prevent the system from sleeping. + ::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); +} + +extern "C" void ApplyUninitialize() +{ + ::SetThreadExecutionState(ES_CONTINUOUS); +} + +extern "C" HRESULT ApplySetVariables( + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + + hr = VariableSetString(pVariables, BURN_BUNDLE_FORCED_RESTART_PACKAGE, NULL, TRUE, FALSE); + ExitOnFailure(hr, "Failed to set the bundle forced restart package built-in variable."); + +LExit: + return hr; +} + +extern "C" void ApplyReset( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PACKAGES* pPackages + ) +{ + UserExperienceExecuteReset(pUX); + + for (DWORD i = 0; i < pPackages->cPackages; ++i) + { + BURN_PACKAGE* pPackage = pPackages->rgPackages + i; + pPackage->hrCacheResult = S_OK; + pPackage->transactionRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + } +} + +extern "C" HRESULT ApplyLock( + __in BOOL /*fPerMachine*/, + __out HANDLE* phLock + ) +{ + HRESULT hr = S_OK; + *phLock = NULL; + +#if 0 // eventually figure out the correct way to support this. In its current form, embedded bundles (including related bundles) are hosed. + DWORD er = ERROR_SUCCESS; + HANDLE hLock = NULL; + + hLock = ::CreateMutexW(NULL, TRUE, fPerMachine ? L"Global\\WixBurnExecutionLock" : L"Local\\WixBurnExecutionLock"); + ExitOnNullWithLastError(hLock, hr, "Failed to create lock."); + + er = ::GetLastError(); + if (ERROR_ALREADY_EXISTS == er) + { + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_INSTALL_ALREADY_RUNNING)); + } + + *phLock = hLock; + hLock = NULL; + +LExit: + ReleaseHandle(hLock); +#endif + return hr; +} + +extern "C" HRESULT ApplyRegister( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + LPWSTR sczEngineWorkingPath = NULL; + + hr = UserExperienceOnRegisterBegin(&pEngineState->userExperience); + ExitOnRootFailure(hr, "BA aborted register begin."); + + // If we have a resume mode that suggests the bundle is on the machine. + if (BOOTSTRAPPER_RESUME_TYPE_REBOOT_PENDING < pEngineState->command.resumeType) + { + // resume previous session + if (pEngineState->registration.fPerMachine) + { + hr = ElevationSessionResume(pEngineState->companionConnection.hPipe, pEngineState->registration.sczResumeCommandLine, pEngineState->registration.fDisableResume, &pEngineState->variables); + ExitOnFailure(hr, "Failed to resume registration session in per-machine process."); + } + else + { + hr = RegistrationSessionResume(&pEngineState->registration, &pEngineState->variables); + ExitOnFailure(hr, "Failed to resume registration session."); + } + } + else // need to complete registration on the machine. + { + hr = CacheCalculateBundleWorkingPath(pEngineState->registration.sczId, pEngineState->registration.sczExecutableName, &sczEngineWorkingPath); + ExitOnFailure(hr, "Failed to calculate working path for engine."); + + // begin new session + if (pEngineState->registration.fPerMachine) + { + hr = ElevationSessionBegin(pEngineState->companionConnection.hPipe, sczEngineWorkingPath, pEngineState->registration.sczResumeCommandLine, pEngineState->registration.fDisableResume, &pEngineState->variables, pEngineState->plan.dwRegistrationOperations, pEngineState->plan.dependencyRegistrationAction, pEngineState->plan.qwEstimatedSize); + ExitOnFailure(hr, "Failed to begin registration session in per-machine process."); + } + else + { + hr = RegistrationSessionBegin(sczEngineWorkingPath, &pEngineState->registration, &pEngineState->variables, pEngineState->plan.dwRegistrationOperations, pEngineState->plan.dependencyRegistrationAction, pEngineState->plan.qwEstimatedSize); + ExitOnFailure(hr, "Failed to begin registration session."); + } + } + + // Apply any registration actions. + HRESULT hrExecuteRegistration = ExecuteDependentRegistrationActions(pEngineState->companionConnection.hPipe, &pEngineState->registration, pEngineState->plan.rgRegistrationActions, pEngineState->plan.cRegistrationActions); + UNREFERENCED_PARAMETER(hrExecuteRegistration); + + // Try to save engine state. + hr = CoreSaveEngineState(pEngineState); + if (FAILED(hr)) + { + LogErrorId(hr, MSG_STATE_NOT_SAVED); + hr = S_OK; + } + +LExit: + UserExperienceOnRegisterComplete(&pEngineState->userExperience, hr); + ReleaseStr(sczEngineWorkingPath); + + return hr; +} + +extern "C" HRESULT ApplyUnregister( + __in BURN_ENGINE_STATE* pEngineState, + __in BOOL fFailedOrRollback, + __in BOOL fSuspend, + __in BOOTSTRAPPER_APPLY_RESTART restart + ) +{ + HRESULT hr = S_OK; + BURN_RESUME_MODE resumeMode = BURN_RESUME_MODE_NONE; + BOOL fKeepRegistration = pEngineState->plan.fDisallowRemoval; + + CalculateKeepRegistration(pEngineState, &fKeepRegistration); + + hr = UserExperienceOnUnregisterBegin(&pEngineState->userExperience, &fKeepRegistration); + ExitOnRootFailure(hr, "BA aborted unregister begin."); + + // Calculate the correct resume mode. If a restart has been initiated, that trumps all other + // modes. If the user chose to suspend the install then we'll use that as the resume mode. + // Barring those special cases, if it was determined that we should keep the registration then + // do that, otherwise the resume mode was initialized to none and registration will be removed. + if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == restart) + { + resumeMode = BURN_RESUME_MODE_REBOOT_PENDING; + } + else if (fSuspend) + { + resumeMode = BURN_RESUME_MODE_SUSPEND; + } + else if (fKeepRegistration) + { + resumeMode = BURN_RESUME_MODE_ARP; + } + + // If apply failed in any way and we're going to be keeping the bundle registered then + // execute any rollback dependency registration actions. + if (fFailedOrRollback && fKeepRegistration) + { + // Execute any rollback registration actions. + HRESULT hrRegistrationRollback = ExecuteDependentRegistrationActions(pEngineState->companionConnection.hPipe, &pEngineState->registration, pEngineState->plan.rgRollbackRegistrationActions, pEngineState->plan.cRollbackRegistrationActions); + UNREFERENCED_PARAMETER(hrRegistrationRollback); + } + + if (pEngineState->registration.fPerMachine) + { + hr = ElevationSessionEnd(pEngineState->companionConnection.hPipe, resumeMode, restart, pEngineState->plan.dependencyRegistrationAction); + ExitOnFailure(hr, "Failed to end session in per-machine process."); + } + else + { + hr = RegistrationSessionEnd(&pEngineState->registration, &pEngineState->variables, &pEngineState->packages, resumeMode, restart, pEngineState->plan.dependencyRegistrationAction); + ExitOnFailure(hr, "Failed to end session in per-user process."); + } + + pEngineState->resumeMode = resumeMode; + +LExit: + UserExperienceOnUnregisterComplete(&pEngineState->userExperience, hr); + + return hr; +} + +extern "C" HRESULT ApplyCache( + __in HANDLE hSourceEngineFile, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_VARIABLES* pVariables, + __in BURN_PLAN* pPlan, + __in HANDLE hPipe, + __inout DWORD* pcOverallProgressTicks, + __inout BOOL* pfRollback + ) +{ + HRESULT hr = S_OK; + DWORD dwCheckpoint = 0; + BURN_CACHE_CONTEXT cacheContext = { }; + BURN_PACKAGE* pPackage = NULL; + + *pfRollback = FALSE; + + hr = UserExperienceOnCacheBegin(pUX); + ExitOnRootFailure(hr, "BA aborted cache."); + + cacheContext.hSourceEngineFile = hSourceEngineFile; + cacheContext.pPayloads = pPlan->pPayloads; + cacheContext.pUX = pUX; + cacheContext.pVariables = pVariables; + cacheContext.qwTotalCacheSize = pPlan->qwCacheSizeTotal; + cacheContext.wzLayoutDirectory = pPlan->sczLayoutDirectory; + + hr = MemAllocArray(reinterpret_cast(&cacheContext.rgSearchPaths), sizeof(LPWSTR), BURN_CACHE_MAX_SEARCH_PATHS); + ExitOnNull(cacheContext.rgSearchPaths, hr, E_OUTOFMEMORY, "Failed to allocate cache search paths array."); + + for (DWORD i = 0; i < pPlan->cCacheActions; ++i) + { + BURN_CACHE_ACTION* pCacheAction = pPlan->rgCacheActions + i; + cacheContext.hPipe = hPipe; + pPackage = NULL; + + switch (pCacheAction->type) + { + case BURN_CACHE_ACTION_TYPE_CHECKPOINT: + dwCheckpoint = pCacheAction->checkpoint.dwId; + break; + + case BURN_CACHE_ACTION_TYPE_LAYOUT_BUNDLE: + hr = ApplyLayoutBundle(&cacheContext, pCacheAction->bundleLayout.pPayloadGroup, pCacheAction->bundleLayout.sczExecutableName, pCacheAction->bundleLayout.sczUnverifiedPath, pCacheAction->bundleLayout.qwBundleSize); + ExitOnFailure(hr, "Failed cache action: %ls", L"layout bundle"); + + ++(*pcOverallProgressTicks); + + hr = ReportOverallProgressTicks(pUX, FALSE, pPlan->cOverallProgressTicksTotal, *pcOverallProgressTicks); + LogExitOnFailure(hr, MSG_USER_CANCELED, "Cancel during cache: %ls", L"layout bundle"); + + break; + + case BURN_CACHE_ACTION_TYPE_PACKAGE: + pPackage = pCacheAction->package.pPackage; + + if (!pPackage->fPerMachine && !cacheContext.wzLayoutDirectory) + { + hr = CacheGetCompletedPath(FALSE, pPackage->sczCacheId, &pPackage->sczCacheFolder); + ExitOnFailure(hr, "Failed to get cached path for package with cache id: %ls", pPackage->sczCacheId); + + cacheContext.hPipe = INVALID_HANDLE_VALUE; + } + + hr = ApplyCachePackage(&cacheContext, pPackage); + ExitOnFailure(hr, "Failed cache action: %ls", L"cache package"); + + ++(*pcOverallProgressTicks); + + hr = ReportOverallProgressTicks(pUX, FALSE, pPlan->cOverallProgressTicksTotal, *pcOverallProgressTicks); + LogExitOnFailure(hr, MSG_USER_CANCELED, "Cancel during cache: %ls", L"cache package"); + + break; + + case BURN_CACHE_ACTION_TYPE_CONTAINER: + Assert(pPlan->sczLayoutDirectory); + hr = ApplyLayoutContainer(&cacheContext, pCacheAction->container.pContainer); + ExitOnFailure(hr, "Failed cache action: %ls", L"layout container"); + + break; + + case BURN_CACHE_ACTION_TYPE_SIGNAL_SYNCPOINT: + if (!::SetEvent(pCacheAction->syncpoint.hEvent)) + { + ExitWithLastError(hr, "Failed to set syncpoint event."); + } + break; + + default: + AssertSz(FALSE, "Unknown cache action."); + break; + } + } + +LExit: + if (FAILED(hr)) + { + DoRollbackCache(pUX, pPlan, hPipe, dwCheckpoint); + *pfRollback = TRUE; + } + + // Clean up any remanents in the cache. + if (INVALID_HANDLE_VALUE != hPipe) + { + ElevationCacheCleanup(hPipe); + } + + CacheCleanup(FALSE, pPlan->wzBundleId); + + for (DWORD i = 0; i < cacheContext.cSearchPathsMax; ++i) + { + ReleaseNullStr(cacheContext.rgSearchPaths[i]); + } + ReleaseMem(cacheContext.rgSearchPaths); + ReleaseStr(cacheContext.sczLastUsedFolderCandidate); + + UserExperienceOnCacheComplete(pUX, hr); + return hr; +} + +extern "C" HRESULT ApplyExecute( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HANDLE hCacheThread, + __inout DWORD* pcOverallProgressTicks, + __out BOOL* pfRollback, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + HRESULT hrRollback = S_OK; + BURN_EXECUTE_ACTION_CHECKPOINT* pCheckpoint = NULL; + BURN_EXECUTE_CONTEXT context = { }; + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL; + BOOL fSeekNextRollbackBoundary = FALSE; + + context.pUX = &pEngineState->userExperience; + context.cExecutePackagesTotal = pEngineState->plan.cExecutePackagesTotal; + context.pcOverallProgressTicks = pcOverallProgressTicks; + + *pfRollback = FALSE; + *pfSuspend = FALSE; + + // Send execute begin to BA. + hr = UserExperienceOnExecuteBegin(&pEngineState->userExperience, pEngineState->plan.cExecutePackagesTotal); + ExitOnRootFailure(hr, "BA aborted execute begin."); + + // Do execute actions. + for (DWORD i = 0; i < pEngineState->plan.cExecuteActions; ++i) + { + BURN_EXECUTE_ACTION* pExecuteAction = &pEngineState->plan.rgExecuteActions[i]; + if (pExecuteAction->fDeleted) + { + continue; + } + + // If we are seeking the next rollback boundary, skip if this action wasn't it. + if (fSeekNextRollbackBoundary) + { + if (BURN_EXECUTE_ACTION_TYPE_ROLLBACK_BOUNDARY == pExecuteAction->type) + { + continue; + } + else + { + fSeekNextRollbackBoundary = FALSE; + } + } + + // Execute the action. + hr = DoExecuteAction(pEngineState, pExecuteAction, hCacheThread, &context, &pRollbackBoundary, &pCheckpoint, pfSuspend, pRestart); + + if (*pfSuspend || BOOTSTRAPPER_APPLY_RESTART_INITIATED == *pRestart) + { + if (pCheckpoint && pCheckpoint->pActiveRollbackBoundary && pCheckpoint->pActiveRollbackBoundary->fActiveTransaction) + { + hr = E_INVALIDSTATE; + LogId(REPORT_ERROR, MSG_RESTART_REQUEST_DURING_MSI_TRANSACTION, pCheckpoint->pActiveRollbackBoundary->sczId); + } + else + { + ExitFunction(); + } + } + + if (FAILED(hr)) + { + // If rollback is disabled, keep what we have and always end execution here. + if (pEngineState->plan.fDisableRollback) + { + LogId(REPORT_WARNING, MSG_PLAN_ROLLBACK_DISABLED); + + if (pCheckpoint && pCheckpoint->pActiveRollbackBoundary && pCheckpoint->pActiveRollbackBoundary->fActiveTransaction) + { + hrRollback = ExecuteMsiCommitTransaction(pEngineState, pCheckpoint->pActiveRollbackBoundary, &context); + IgnoreRollbackError(hrRollback, "Failed commit transaction from disable rollback"); + } + + *pfRollback = TRUE; + break; + } + + if (pCheckpoint) + { + // If inside a MSI transaction, roll it back. + if (pCheckpoint->pActiveRollbackBoundary && pCheckpoint->pActiveRollbackBoundary->fActiveTransaction) + { + hrRollback = ExecuteMsiRollbackTransaction(pEngineState, pCheckpoint->pActiveRollbackBoundary, &context); + IgnoreRollbackError(hrRollback, "Failed rolling back transaction"); + } + + // The action failed, roll back to previous rollback boundary. + hrRollback = DoRollbackActions(pEngineState, &context, pCheckpoint->dwId, pRestart); + IgnoreRollbackError(hrRollback, "Failed rollback actions"); + } + + // If the rollback boundary is vital, end execution here. + if (pRollbackBoundary && pRollbackBoundary->fVital) + { + *pfRollback = TRUE; + break; + } + + // Move forward to next rollback boundary. + fSeekNextRollbackBoundary = TRUE; + } + } + +LExit: + // Send execute complete to BA. + UserExperienceOnExecuteComplete(&pEngineState->userExperience, hr); + + return hr; +} + +extern "C" void ApplyClean( + __in BURN_USER_EXPERIENCE* /*pUX*/, + __in BURN_PLAN* pPlan, + __in HANDLE hPipe + ) +{ + HRESULT hr = S_OK; + + for (DWORD i = 0; i < pPlan->cCleanActions; ++i) + { + BURN_CLEAN_ACTION* pCleanAction = pPlan->rgCleanActions + i; + BURN_PACKAGE* pPackage = pCleanAction->pPackage; + + hr = CleanPackage(hPipe, pPackage); + } +} + + +// internal helper functions + +static void CalculateKeepRegistration( + __in BURN_ENGINE_STATE* pEngineState, + __inout BOOL* pfKeepRegistration + ) +{ + LogId(REPORT_STANDARD, MSG_POST_APPLY_CALCULATE_REGISTRATION); + + for (DWORD i = 0; i < pEngineState->packages.cPackages; ++i) + { + BURN_PACKAGE* pPackage = pEngineState->packages.rgPackages + i; + + if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + MspEngineFinalizeInstallRegistrationState(pPackage); + } + + LogId(REPORT_STANDARD, MSG_POST_APPLY_PACKAGE, pPackage->sczId, LoggingPackageRegistrationStateToString(pPackage->fCanAffectRegistration, pPackage->installRegistrationState), LoggingPackageRegistrationStateToString(pPackage->fCanAffectRegistration, pPackage->cacheRegistrationState)); + + if (!pPackage->fCanAffectRegistration) + { + continue; + } + + if (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->installRegistrationState || + BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->cacheRegistrationState) + { + *pfKeepRegistration = TRUE; + } + } +} + +static HRESULT ExecuteDependentRegistrationActions( + __in HANDLE hPipe, + __in const BURN_REGISTRATION* pRegistration, + __in_ecount(cActions) const BURN_DEPENDENT_REGISTRATION_ACTION* rgActions, + __in DWORD cActions + ) +{ + HRESULT hr = S_OK; + + for (DWORD iAction = 0; iAction < cActions; ++iAction) + { + const BURN_DEPENDENT_REGISTRATION_ACTION* pAction = rgActions + iAction; + + if (pRegistration->fPerMachine) + { + hr = ElevationProcessDependentRegistration(hPipe, pAction); + ExitOnFailure(hr, "Failed to execute dependent registration action."); + } + else + { + hr = DependencyProcessDependentRegistration(pRegistration, pAction); + ExitOnFailure(hr, "Failed to process dependency registration action."); + } + } + +LExit: + return hr; +} + +static HRESULT ApplyCachePackage( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + BOOL fCanceledBegin = FALSE; + BOOTSTRAPPER_CACHEPACKAGECOMPLETE_ACTION cachePackageCompleteAction = BOOTSTRAPPER_CACHEPACKAGECOMPLETE_ACTION_NONE; + + for (;;) + { + fCanceledBegin = FALSE; + + hr = UserExperienceOnCachePackageBegin(pContext->pUX, pPackage->sczId, pPackage->payloads.cItems, pPackage->payloads.qwTotalSize); + if (FAILED(hr)) + { + fCanceledBegin = TRUE; + } + else + { + for (DWORD i = 0; i < pPackage->payloads.cItems; ++i) + { + BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem = pPackage->payloads.rgItems + i; + + hr = ApplyProcessPayload(pContext, pPackage, pPayloadGroupItem); + if (FAILED(hr)) + { + break; + } + } + } + + pPackage->hrCacheResult = hr; + cachePackageCompleteAction = SUCCEEDED(hr) || pPackage->fVital || fCanceledBegin ? BOOTSTRAPPER_CACHEPACKAGECOMPLETE_ACTION_NONE : BOOTSTRAPPER_CACHEPACKAGECOMPLETE_ACTION_IGNORE; + UserExperienceOnCachePackageComplete(pContext->pUX, pPackage->sczId, hr, &cachePackageCompleteAction); + + if (SUCCEEDED(hr)) + { + break; + } + + if (BOOTSTRAPPER_CACHEPACKAGECOMPLETE_ACTION_RETRY == cachePackageCompleteAction) + { + for (DWORD i = 0; i < pPackage->payloads.cItems; ++i) + { + BURN_PAYLOAD_GROUP_ITEM* pItem = pPackage->payloads.rgItems + i; + if (pItem->fCached) + { + pItem->pPayload->cRemainingInstances += 1; + pItem->fCached = FALSE; + } + + if (pItem->qwCommittedCacheProgress) + { + pContext->qwSuccessfulCacheProgress -= pItem->qwCommittedCacheProgress; + pItem->qwCommittedCacheProgress = 0; + } + } + + LogErrorId(hr, MSG_CACHE_RETRYING_PACKAGE, pPackage->sczId, NULL, NULL); + + continue; + } + else if (BOOTSTRAPPER_CACHEPACKAGECOMPLETE_ACTION_IGNORE == cachePackageCompleteAction && !pPackage->fVital) // ignore non-vital download failures. + { + LogId(REPORT_STANDARD, MSG_CACHE_CONTINUING_NONVITAL_PACKAGE, pPackage->sczId, hr); + hr = S_OK; + } + else if (fCanceledBegin) + { + LogExitOnFailure(hr, MSG_USER_CANCELED, "Cancel during cache: %ls: %ls", L"begin cache package", pPackage->sczId); + } + + break; + } + +LExit: + return hr; +} + +static HRESULT ApplyExtractContainer( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_CONTAINER* pContainer + ) +{ + HRESULT hr = S_OK; + + if (pContainer->qwCommittedCacheProgress) + { + pContext->qwSuccessfulCacheProgress -= pContainer->qwCommittedCacheProgress; + pContainer->qwCommittedCacheProgress = 0; + } + + if (pContainer->qwCommittedExtractProgress) + { + pContext->qwSuccessfulCacheProgress -= pContainer->qwCommittedExtractProgress; + pContainer->qwCommittedExtractProgress = 0; + } + + if (!pContainer->fActuallyAttached) + { + hr = ApplyAcquireContainerOrPayload(pContext, pContainer, NULL, NULL); + LogExitOnFailure(hr, MSG_FAILED_ACQUIRE_CONTAINER, "Failed to acquire container: %ls to working path: %ls", pContainer->sczId, pContainer->sczUnverifiedPath); + } + + hr = ExtractContainer(pContext, pContainer); + LogExitOnFailure(hr, MSG_FAILED_EXTRACT_CONTAINER, "Failed to extract payloads from container: %ls to working path: %ls", pContainer->sczId, pContainer->sczUnverifiedPath); + + if (pContext->sczLastUsedFolderCandidate) + { + // We successfully copied from a source location, set that as the last used source. + CacheSetLastUsedSource(pContext->pVariables, pContext->sczLastUsedFolderCandidate, pContainer->sczFilePath); + } + + if (pContainer->qwExtractSizeTotal < pContainer->qwCommittedExtractProgress) + { + AssertSz(FALSE, "Container extracted more than planned."); + pContext->qwSuccessfulCacheProgress -= pContainer->qwCommittedExtractProgress; + pContext->qwSuccessfulCacheProgress += pContainer->qwExtractSizeTotal; + } + else + { + pContext->qwSuccessfulCacheProgress += pContainer->qwExtractSizeTotal - pContainer->qwCommittedExtractProgress; + } + + pContainer->qwCommittedExtractProgress = pContainer->qwExtractSizeTotal; + +LExit: + ReleaseNullStr(pContext->sczLastUsedFolderCandidate); + + return hr; +} + +static HRESULT ApplyLayoutBundle( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_PAYLOAD_GROUP* pPayloads, + __in_z LPCWSTR wzExecutableName, + __in_z LPCWSTR wzUnverifiedPath, + __in DWORD64 qwBundleSize + ) +{ + HRESULT hr = S_OK; + + hr = LayoutBundle(pContext, wzExecutableName, wzUnverifiedPath, qwBundleSize); + ExitOnFailure(hr, "Failed to layout bundle."); + + for (DWORD i = 0; i < pPayloads->cItems; ++i) + { + BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem = pPayloads->rgItems + i; + + hr = ApplyProcessPayload(pContext, NULL, pPayloadGroupItem); + ExitOnFailure(hr, "Failed to layout bundle payload: %ls", pPayloadGroupItem->pPayload->sczKey); + } + +LExit: + return hr; +} + +static HRESULT ApplyLayoutContainer( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_CONTAINER* pContainer + ) +{ + HRESULT hr = S_OK; + DWORD cTryAgainAttempts = 0; + BOOL fRetry = FALSE; + + Assert(!pContainer->fAttached); + + hr = ApplyCacheVerifyContainerOrPayload(pContext, pContainer, NULL, NULL); + if (SUCCEEDED(hr)) + { + ExitFunction(); + } + + for (;;) + { + fRetry = FALSE; + + hr = ApplyAcquireContainerOrPayload(pContext, pContainer, NULL, NULL); + LogExitOnFailure(hr, MSG_FAILED_ACQUIRE_CONTAINER, "Failed to acquire container: %ls to working path: %ls", pContainer->sczId, pContainer->sczUnverifiedPath); + + hr = LayoutOrCacheContainerOrPayload(pContext, pContainer, NULL, NULL, cTryAgainAttempts, &fRetry); + if (SUCCEEDED(hr)) + { + break; + } + else + { + LogErrorId(hr, MSG_FAILED_LAYOUT_CONTAINER, pContainer->sczId, pContext->wzLayoutDirectory, pContainer->sczUnverifiedPath); + + if (!fRetry) + { + ExitFunction(); + } + + ++cTryAgainAttempts; + pContext->qwSuccessfulCacheProgress -= pContainer->qwCommittedCacheProgress; + pContainer->qwCommittedCacheProgress = 0; + ReleaseNullStr(pContext->sczLastUsedFolderCandidate); + LogErrorId(hr, MSG_CACHE_RETRYING_CONTAINER, pContainer->sczId, NULL, NULL); + } + } + +LExit: + ReleaseNullStr(pContext->sczLastUsedFolderCandidate); + + return hr; +} + +static HRESULT ApplyProcessPayload( + __in BURN_CACHE_CONTEXT* pContext, + __in_opt BURN_PACKAGE* pPackage, + __in BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem + ) +{ + HRESULT hr = S_OK; + DWORD cTryAgainAttempts = 0; + BOOL fRetry = FALSE; + BURN_PAYLOAD* pPayload = pPayloadGroupItem->pPayload; + + Assert(pContext->pPayloads && pPackage || pContext->wzLayoutDirectory); + + if (pPayload->pContainer && pContext->wzLayoutDirectory) + { + ExitFunction(); + } + + hr = ApplyCacheVerifyContainerOrPayload(pContext, NULL, pPackage, pPayloadGroupItem); + if (SUCCEEDED(hr)) + { + ExitFunction(); + } + + for (;;) + { + fRetry = FALSE; + + hr = ApplyAcquireContainerOrPayload(pContext, NULL, pPackage, pPayloadGroupItem); + LogExitOnFailure(hr, MSG_FAILED_ACQUIRE_PAYLOAD, "Failed to acquire payload: %ls to working path: %ls", pPayload->sczKey, pPayload->sczUnverifiedPath); + + hr = LayoutOrCacheContainerOrPayload(pContext, NULL, pPackage, pPayloadGroupItem, cTryAgainAttempts, &fRetry); + if (SUCCEEDED(hr)) + { + break; + } + else + { + LogErrorId(hr, pContext->wzLayoutDirectory ? MSG_FAILED_LAYOUT_PAYLOAD : MSG_FAILED_CACHE_PAYLOAD, pPayload->sczKey, pContext->wzLayoutDirectory, pPayload->sczUnverifiedPath); + + if (!fRetry) + { + ExitFunction(); + } + + ++cTryAgainAttempts; + pContext->qwSuccessfulCacheProgress -= pPayloadGroupItem->qwCommittedCacheProgress; + pPayloadGroupItem->qwCommittedCacheProgress = 0; + ReleaseNullStr(pContext->sczLastUsedFolderCandidate); + LogErrorId(hr, MSG_CACHE_RETRYING_PAYLOAD, pPayload->sczKey, NULL, NULL); + } + } + +LExit: + ReleaseNullStr(pContext->sczLastUsedFolderCandidate); + + return hr; +} + +static HRESULT ApplyCacheVerifyContainerOrPayload( + __in BURN_CACHE_CONTEXT* pContext, + __in_opt BURN_CONTAINER* pContainer, + __in_opt BURN_PACKAGE* pPackage, + __in_opt BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem + ) +{ + AssertSz(pContainer || pPayloadGroupItem, "Must provide a container or a payload."); + + HRESULT hr = S_OK; + BURN_CACHE_PROGRESS_CONTEXT progress = { }; + + progress.pCacheContext = pContext; + progress.pContainer = pContainer; + progress.pPackage = pPackage; + progress.pPayloadGroupItem = pPayloadGroupItem; + + if (pContainer) + { + hr = CacheVerifyContainer(pContainer, pContext->wzLayoutDirectory, CacheMessageHandler, CacheProgressRoutine, &progress); + } + else if (!pContext->wzLayoutDirectory && INVALID_HANDLE_VALUE != pContext->hPipe) + { + hr = ElevationCacheVerifyPayload(pContext->hPipe, pPackage, pPayloadGroupItem->pPayload, CacheMessageHandler, CacheProgressRoutine, &progress); + } + else + { + hr = CacheVerifyPayload(pPayloadGroupItem->pPayload, pContext->wzLayoutDirectory ? pContext->wzLayoutDirectory : pPackage->sczCacheFolder, CacheMessageHandler, CacheProgressRoutine, &progress); + } + + return hr; +} + +static HRESULT ExtractContainer( + __in BURN_CACHE_CONTEXT* pContext, + __in BURN_CONTAINER* pContainer + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER_CONTEXT context = { }; + HANDLE hContainerHandle = INVALID_HANDLE_VALUE; + LPWSTR sczStreamName = NULL; + BURN_PAYLOAD* pExtract = NULL; + BURN_CACHE_PROGRESS_CONTEXT progress = { }; + + progress.pCacheContext = pContext; + progress.pContainer = pContainer; + progress.type = BURN_CACHE_PROGRESS_TYPE_EXTRACT; + + // If the container is actually attached, then it was planned to be acquired through hSourceEngineFile. + if (pContainer->fActuallyAttached) + { + hContainerHandle = pContext->hSourceEngineFile; + } + + hr = ContainerOpen(&context, pContainer, hContainerHandle, pContainer->sczUnverifiedPath); + ExitOnFailure(hr, "Failed to open container: %ls.", pContainer->sczId); + + while (S_OK == (hr = ContainerNextStream(&context, &sczStreamName))) + { + BOOL fExtracted = FALSE; + + hr = PayloadFindEmbeddedBySourcePath(pContext->pPayloads, sczStreamName, &pExtract); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to find embedded payload by source path: %ls container: %ls", sczStreamName, pContainer->sczId); + + // Skip payloads that weren't planned or have already been cached. + if (pExtract->sczUnverifiedPath && pExtract->cRemainingInstances) + { + progress.pPayload = pExtract; + + hr = PreparePayloadDestinationPath(pExtract->sczUnverifiedPath); + ExitOnFailure(hr, "Failed to prepare payload destination path: %ls", pExtract->sczUnverifiedPath); + + hr = UserExperienceOnCachePayloadExtractBegin(pContext->pUX, pContainer->sczId, pExtract->sczKey); + if (FAILED(hr)) + { + UserExperienceOnCachePayloadExtractComplete(pContext->pUX, pContainer->sczId, pExtract->sczKey, hr); + ExitOnRootFailure(hr, "BA aborted cache payload extract begin."); + } + + // TODO: Send progress when extracting stream to file. + hr = ContainerStreamToFile(&context, pExtract->sczUnverifiedPath); + // Error handling happens after sending complete message to BA. + + // If succeeded, send 100% complete here to make sure progress was sent to the BA. + if (SUCCEEDED(hr)) + { + hr = CompleteCacheProgress(&progress, pExtract->qwFileSize); + } + + UserExperienceOnCachePayloadExtractComplete(pContext->pUX, pContainer->sczId, pExtract->sczKey, hr); + ExitOnFailure(hr, "Failed to extract payload: %ls from container: %ls", sczStreamName, pContainer->sczId); + + fExtracted = TRUE; + } + } + + if (!fExtracted) + { + hr = ContainerSkipStream(&context); + ExitOnFailure(hr, "Failed to skip the extraction of payload: %ls from container: %ls", sczStreamName, pContainer->sczId); + } + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to extract all payloads from container: %ls", pContainer->sczId); + +LExit: + ReleaseStr(sczStreamName); + ContainerClose(&context); + + return hr; +} + +static HRESULT LayoutBundle( + __in BURN_CACHE_CONTEXT* pContext, + __in_z LPCWSTR wzExecutableName, + __in_z LPCWSTR wzUnverifiedPath, + __in DWORD64 qwBundleSize + ) +{ + HRESULT hr = S_OK; + LPWSTR sczBundlePath = NULL; + LPWSTR sczBundleDownloadUrl = NULL; + LPWSTR sczDestinationPath = NULL; + int nEquivalentPaths = 0; + BOOTSTRAPPER_CACHE_OPERATION cacheOperation = BOOTSTRAPPER_CACHE_OPERATION_NONE; + BURN_CACHE_PROGRESS_CONTEXT progress = { }; + BOOL fRetry = FALSE; + BOOL fRetryAcquire = FALSE; + BOOL fCanceledBegin = FALSE; + + progress.pCacheContext = pContext; + + hr = VariableGetString(pContext->pVariables, BURN_BUNDLE_SOURCE_PROCESS_PATH, &sczBundlePath); + if (FAILED(hr)) + { + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get path to bundle source process path to layout."); + } + + hr = PathForCurrentProcess(&sczBundlePath, NULL); + ExitOnFailure(hr, "Failed to get path to bundle to layout."); + } + + hr = PathConcat(pContext->wzLayoutDirectory, wzExecutableName, &sczDestinationPath); + ExitOnFailure(hr, "Failed to concat layout path for bundle."); + + // If the destination path is the currently running bundle, bail. + hr = PathCompare(sczBundlePath, sczDestinationPath, &nEquivalentPaths); + ExitOnFailure(hr, "Failed to determine if layout bundle path was equivalent with current process path."); + + if (CSTR_EQUAL == nEquivalentPaths && FileExistsEx(sczDestinationPath, NULL)) + { + hr = UserExperienceOnCacheContainerOrPayloadVerifyBegin(pContext->pUX, NULL, NULL); + if (FAILED(hr)) + { + UserExperienceOnCacheContainerOrPayloadVerifyComplete(pContext->pUX, NULL, NULL, hr); + ExitOnRootFailure(hr, "BA aborted cache payload verify begin."); + } + + progress.type = BURN_CACHE_PROGRESS_TYPE_CONTAINER_OR_PAYLOAD_VERIFY; + hr = CompleteCacheProgress(&progress, qwBundleSize); + + UserExperienceOnCacheContainerOrPayloadVerifyComplete(pContext->pUX, NULL, NULL, hr); + + ExitFunction(); + } + + do + { + hr = S_OK; + fRetry = FALSE; + progress.type = BURN_CACHE_PROGRESS_TYPE_ACQUIRE; + + for (;;) + { + fRetryAcquire = FALSE; + progress.fCancel = FALSE; + fCanceledBegin = FALSE; + + hr = UserExperienceOnCacheAcquireBegin(pContext->pUX, NULL, NULL, &sczBundlePath, &sczBundleDownloadUrl, NULL, &cacheOperation); + + if (FAILED(hr)) + { + fCanceledBegin = TRUE; + } + else + { + hr = CopyPayload(&progress, pContext->hSourceEngineFile, sczBundlePath, wzUnverifiedPath); + // Error handling happens after sending complete message to BA. + + // If succeeded, send 100% complete here to make sure progress was sent to the BA. + if (SUCCEEDED(hr)) + { + hr = CompleteCacheProgress(&progress, qwBundleSize); + } + } + + UserExperienceOnCacheAcquireComplete(pContext->pUX, NULL, NULL, hr, &fRetryAcquire); + if (fRetryAcquire) + { + continue; + } + else if (fCanceledBegin) + { + ExitOnRootFailure(hr, "BA aborted cache acquire begin."); + } + + ExitOnFailure(hr, "Failed to copy bundle from: '%ls' to: '%ls'", sczBundlePath, wzUnverifiedPath); + break; + } + + do + { + fCanceledBegin = FALSE; + + hr = UserExperienceOnCacheVerifyBegin(pContext->pUX, NULL, NULL); + + if (FAILED(hr)) + { + fCanceledBegin = TRUE; + } + else + { + hr = CacheLayoutBundle(wzExecutableName, pContext->wzLayoutDirectory, wzUnverifiedPath, qwBundleSize, CacheMessageHandler, CacheProgressRoutine, &progress); + } + + BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION action = BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION_NONE; + UserExperienceOnCacheVerifyComplete(pContext->pUX, NULL, NULL, hr, &action); + if (BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION_RETRYVERIFICATION == action) + { + hr = S_FALSE; // retry verify. + } + else if (BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION_RETRYACQUISITION == action) + { + fRetry = TRUE; // go back and retry acquire. + } + else if (fCanceledBegin) + { + ExitOnRootFailure(hr, "BA aborted cache verify begin."); + } + } while (S_FALSE == hr); + + if (fRetry) + { + pContext->qwSuccessfulCacheProgress -= qwBundleSize; // Acquire + } + } while (fRetry); + LogExitOnFailure(hr, MSG_FAILED_LAYOUT_BUNDLE, "Failed to layout bundle: %ls to layout directory: %ls", sczBundlePath, pContext->wzLayoutDirectory); + +LExit: + ReleaseStr(sczDestinationPath); + ReleaseStr(sczBundleDownloadUrl); + ReleaseStr(sczBundlePath); + + return hr; +} + +static HRESULT ApplyAcquireContainerOrPayload( + __in BURN_CACHE_CONTEXT* pContext, + __in_opt BURN_CONTAINER* pContainer, + __in_opt BURN_PACKAGE* pPackage, + __in_opt BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem + ) +{ + AssertSz(pContainer || pPayloadGroupItem, "Must provide a container or a payload."); + + HRESULT hr = S_OK; + BURN_CACHE_PROGRESS_CONTEXT progress = { }; + BOOL fRetry = FALSE; + + progress.pCacheContext = pContext; + progress.type = BURN_CACHE_PROGRESS_TYPE_ACQUIRE; + progress.pContainer = pContainer; + progress.pPackage = pPackage; + progress.pPayloadGroupItem = pPayloadGroupItem; + + do + { + hr = AcquireContainerOrPayload(&progress, &fRetry); + + if (fRetry) + { + LogErrorId(hr, pContainer ? MSG_APPLY_RETRYING_ACQUIRE_CONTAINER : MSG_APPLY_RETRYING_ACQUIRE_PAYLOAD, pContainer ? pContainer->sczId : pPayloadGroupItem->pPayload->sczKey, NULL, NULL); + hr = S_OK; + } + + ExitOnFailure(hr, "Failed to acquire %hs: %ls", pContainer ? "container" : "payload", pContainer ? pContainer->sczId : pPayloadGroupItem->pPayload->sczKey); + } while (fRetry); + +LExit: + return hr; +} + +static HRESULT AcquireContainerOrPayload( + __in BURN_CACHE_PROGRESS_CONTEXT* pProgress, + __out BOOL* pfRetry + ) +{ + BURN_CACHE_CONTEXT* pContext = pProgress->pCacheContext; + BURN_CONTAINER* pContainer = pProgress->pContainer; + BURN_PACKAGE* pPackage = pProgress->pPackage; + BURN_PAYLOAD* pPayload = pProgress->pPayloadGroupItem ? pProgress->pPayloadGroupItem->pPayload : NULL; + AssertSz(pContainer || pPayload, "Must provide a container or a payload."); + + HRESULT hr = S_OK; + int nEquivalentPaths = 0; + LPCWSTR wzPackageOrContainerId = pContainer ? pContainer->sczId : pPackage ? pPackage->sczId : NULL; + LPCWSTR wzPayloadId = pPayload ? pPayload->sczKey : NULL; + LPCWSTR wzPayloadContainerId = pPayload && pPayload->pContainer ? pPayload->pContainer->sczId : NULL; + LPCWSTR wzDestinationPath = pContainer ? pContainer->sczUnverifiedPath: pPayload->sczUnverifiedPath; + LPCWSTR wzRelativePath = pContainer ? pContainer->sczFilePath : pPayload->sczFilePath; + DWORD dwChosenSearchPath = 0; + DWORD dwDestinationSearchPath = 0; + BOOTSTRAPPER_CACHE_OPERATION cacheOperation = BOOTSTRAPPER_CACHE_OPERATION_NONE; + BOOTSTRAPPER_CACHE_RESOLVE_OPERATION resolveOperation = BOOTSTRAPPER_CACHE_RESOLVE_NONE; + LPWSTR* pwzDownloadUrl = pContainer ? &pContainer->downloadSource.sczUrl : &pPayload->downloadSource.sczUrl; + LPWSTR* pwzSourcePath = pContainer ? &pContainer->sczSourcePath : &pPayload->sczSourcePath; + BOOL fFoundLocal = FALSE; + BOOL fPreferExtract = FALSE; + DWORD64 qwFileSize = 0; + BOOL fMinimumFileSize = FALSE; + + if (pContainer) + { + if (pContainer->fAttached) + { + fMinimumFileSize = TRUE; + qwFileSize = pContainer->qwAttachedOffset + pContainer->qwFileSize; + } + else if (pContainer->pbHash && pContext->wzLayoutDirectory) + { + qwFileSize = pContainer->qwFileSize; + } + } + else if (pPayload->pbHash) + { + qwFileSize = pPayload->qwFileSize; + } + + pContext->cSearchPaths = 0; + *pfRetry = FALSE; + pProgress->fCancel = FALSE; + + hr = UserExperienceOnCacheAcquireBegin(pContext->pUX, wzPackageOrContainerId, wzPayloadId, pwzSourcePath, pwzDownloadUrl, wzPayloadContainerId, &cacheOperation); + ExitOnRootFailure(hr, "BA aborted cache acquire begin."); + + // Skip the Resolving event and probing local paths if the BA already knew it wanted to download or extract. + if (BOOTSTRAPPER_CACHE_OPERATION_DOWNLOAD != cacheOperation && + BOOTSTRAPPER_CACHE_OPERATION_EXTRACT != cacheOperation) + { + do + { + fFoundLocal = FALSE; + fPreferExtract = FALSE; + resolveOperation = BOOTSTRAPPER_CACHE_RESOLVE_NONE; + dwChosenSearchPath = 0; + dwDestinationSearchPath = 0; + + hr = CacheGetLocalSourcePaths(wzRelativePath, *pwzSourcePath, wzDestinationPath, pContext->wzLayoutDirectory, pContext->pVariables, &pContext->rgSearchPaths, &pContext->cSearchPaths, &dwChosenSearchPath, &dwDestinationSearchPath); + ExitOnFailure(hr, "Failed to search local source."); + + if (wzPayloadContainerId) + { + // When a payload comes from a container, the container has the highest chance of being correct. + // But we want to avoid extracting the container multiple times. + // So only consider the destination path, which means the container was already extracted. + if (IsValidLocalFile(pContext->rgSearchPaths[dwDestinationSearchPath], qwFileSize, fMinimumFileSize)) + { + fFoundLocal = TRUE; + dwChosenSearchPath = dwDestinationSearchPath; + } + else // don't prefer the container if extracting it already failed. + { + fPreferExtract = SUCCEEDED(pPayload->pContainer->hrExtract); + } + } + + if (!fFoundLocal) + { + for (DWORD i = 0; i < pContext->cSearchPaths; ++i) + { + // If the file exists locally with the correct size, choose it. + if (IsValidLocalFile(pContext->rgSearchPaths[i], qwFileSize, fMinimumFileSize)) + { + dwChosenSearchPath = i; + + fFoundLocal = TRUE; + break; + } + } + } + + if (BOOTSTRAPPER_CACHE_OPERATION_COPY == cacheOperation) + { + if (fFoundLocal) + { + resolveOperation = BOOTSTRAPPER_CACHE_RESOLVE_LOCAL; + } + } + else + { + if (fPreferExtract) // the file comes from a container which hasn't been extracted yet, so extract it. + { + resolveOperation = BOOTSTRAPPER_CACHE_RESOLVE_CONTAINER; + } + else if (fFoundLocal) // the file exists locally, so copy it. + { + resolveOperation = BOOTSTRAPPER_CACHE_RESOLVE_LOCAL; + } + else if (*pwzDownloadUrl && **pwzDownloadUrl) + { + resolveOperation = BOOTSTRAPPER_CACHE_RESOLVE_DOWNLOAD; + } + else if (wzPayloadContainerId) + { + resolveOperation = BOOTSTRAPPER_CACHE_RESOLVE_CONTAINER; + } + } + + // Let the BA have a chance to override the source. + hr = UserExperienceOnCacheAcquireResolving(pContext->pUX, wzPackageOrContainerId, wzPayloadId, pContext->rgSearchPaths, pContext->cSearchPaths, fFoundLocal, &dwChosenSearchPath, pwzDownloadUrl, wzPayloadContainerId, &resolveOperation); + ExitOnRootFailure(hr, "BA aborted cache acquire resolving."); + + switch (resolveOperation) + { + case BOOTSTRAPPER_CACHE_RESOLVE_LOCAL: + cacheOperation = BOOTSTRAPPER_CACHE_OPERATION_COPY; + break; + case BOOTSTRAPPER_CACHE_RESOLVE_DOWNLOAD: + cacheOperation = BOOTSTRAPPER_CACHE_OPERATION_DOWNLOAD; + break; + case BOOTSTRAPPER_CACHE_RESOLVE_CONTAINER: + cacheOperation = BOOTSTRAPPER_CACHE_OPERATION_EXTRACT; + break; + case BOOTSTRAPPER_CACHE_RESOLVE_RETRY: + pContext->cSearchPathsMax = max(pContext->cSearchPaths, pContext->cSearchPathsMax); + break; + } + } while (BOOTSTRAPPER_CACHE_RESOLVE_RETRY == resolveOperation); + } + + switch (cacheOperation) + { + case BOOTSTRAPPER_CACHE_OPERATION_COPY: + // If the source path and destination path are different, do the copy (otherwise there's no point). + hr = PathCompare(pContext->rgSearchPaths[dwChosenSearchPath], wzDestinationPath, &nEquivalentPaths); + ExitOnFailure(hr, "Failed to determine if payload paths were equivalent, source: %ls, destination: %ls.", pContext->rgSearchPaths[dwChosenSearchPath], wzDestinationPath); + + if (CSTR_EQUAL != nEquivalentPaths) + { + hr = CopyPayload(pProgress, INVALID_HANDLE_VALUE, pContext->rgSearchPaths[dwChosenSearchPath], wzDestinationPath); + ExitOnFailure(hr, "Failed to copy payload: %ls", wzPayloadId); + + // Store the source path so it can be used as the LastUsedFolder if it passes verification. + pContext->sczLastUsedFolderCandidate = pContext->rgSearchPaths[dwChosenSearchPath]; + pContext->rgSearchPaths[dwChosenSearchPath] = NULL; + } + + break; + case BOOTSTRAPPER_CACHE_OPERATION_DOWNLOAD: + hr = DownloadPayload(pProgress, wzDestinationPath); + ExitOnFailure(hr, "Failed to download payload: %ls", wzPayloadId); + + break; + case BOOTSTRAPPER_CACHE_OPERATION_EXTRACT: + Assert(pPayload && pPayload->pContainer); + + hr = ApplyExtractContainer(pContext, pPayload->pContainer); + ExitOnFailure(hr, "Failed to extract container for payload: %ls", wzPayloadId); + + break; + default: + hr = E_FILENOTFOUND; + LogExitOnFailure(hr, MSG_RESOLVE_SOURCE_FAILED, "Failed to resolve source, payload: %ls, package: %ls, container: %ls", wzPayloadId, pPackage ? pPackage->sczId : NULL, pContainer ? pContainer->sczId : NULL); + } + + // Send 100% complete here. This is sometimes the only progress sent to the BA. + hr = CompleteCacheProgress(pProgress, pContainer ? pContainer->qwFileSize : pPayload->qwFileSize); + +LExit: + if (BOOTSTRAPPER_CACHE_OPERATION_EXTRACT == cacheOperation) + { + if (FAILED(hr) && SUCCEEDED(pPayload->pContainer->hrExtract) && + (fFoundLocal || pPayload->downloadSource.sczUrl && *pPayload->downloadSource.sczUrl)) + { + *pfRetry = TRUE; + } + pPayload->pContainer->hrExtract = hr; + } + UserExperienceOnCacheAcquireComplete(pContext->pUX, wzPackageOrContainerId, wzPayloadId, hr, pfRetry); + + pContext->cSearchPathsMax = max(pContext->cSearchPaths, pContext->cSearchPathsMax); + + return hr; +} + +static BOOL IsValidLocalFile( + __in_z LPCWSTR wzFilePath, + __in DWORD64 qwFileSize, + __in BOOL fMinimumFileSize + ) +{ + LONGLONG llFileSize = 0; + + if (!qwFileSize) + { + return FileExistsEx(wzFilePath, NULL); + } + else + { + return SUCCEEDED(FileSize(wzFilePath, &llFileSize)) && + (static_cast(llFileSize) == qwFileSize || + fMinimumFileSize && static_cast(llFileSize) > qwFileSize); + } +} + +static HRESULT LayoutOrCacheContainerOrPayload( + __in BURN_CACHE_CONTEXT* pContext, + __in_opt BURN_CONTAINER* pContainer, + __in_opt BURN_PACKAGE* pPackage, + __in_opt BURN_PAYLOAD_GROUP_ITEM* pPayloadGroupItem, + __in DWORD cTryAgainAttempts, + __out BOOL* pfRetry + ) +{ + HRESULT hr = S_OK; + BURN_PAYLOAD* pPayload = pPayloadGroupItem ? pPayloadGroupItem->pPayload : NULL; + LPCWSTR wzPackageOrContainerId = pContainer ? pContainer->sczId : pPackage ? pPackage->sczId : L""; + LPCWSTR wzUnverifiedPath = pContainer ? pContainer->sczUnverifiedPath : pPayload->sczUnverifiedPath; + LPCWSTR wzPayloadId = pPayload ? pPayload->sczKey : L""; + BOOL fCanAffectRegistration = FALSE; + BURN_CACHE_PROGRESS_CONTEXT progress = { }; + BOOL fMove = !pPayload || 1 == pPayload->cRemainingInstances; + BOOL fCanceledBegin = FALSE; + + if (pContainer) + { + Assert(!pPayloadGroupItem); + } + else + { + Assert(pPayload); + AssertSz(0 < pPayload->cRemainingInstances, "Laying out payload more times than planned."); + AssertSz(!pPayloadGroupItem->fCached, "Laying out payload group item that was already cached."); + } + + if (!pContext->wzLayoutDirectory) + { + Assert(!pContainer); + Assert(pPackage); + + fCanAffectRegistration = pPackage->fCanAffectRegistration; + } + + *pfRetry = FALSE; + progress.pCacheContext = pContext; + progress.pContainer = pContainer; + progress.pPackage = pPackage; + progress.pPayloadGroupItem = pPayloadGroupItem; + + do + { + fCanceledBegin = FALSE; + + hr = UserExperienceOnCacheVerifyBegin(pContext->pUX, wzPackageOrContainerId, wzPayloadId); + + if (FAILED(hr)) + { + fCanceledBegin = TRUE; + } + else + { + if (pContext->wzLayoutDirectory) // layout the container or payload. + { + if (pContainer) + { + hr = CacheLayoutContainer(pContainer, pContext->wzLayoutDirectory, wzUnverifiedPath, fMove, CacheMessageHandler, CacheProgressRoutine, &progress); + } + else + { + hr = CacheLayoutPayload(pPayload, pContext->wzLayoutDirectory, wzUnverifiedPath, fMove, CacheMessageHandler, CacheProgressRoutine, &progress); + } + } + else if (INVALID_HANDLE_VALUE != pContext->hPipe) // pass the decision off to the elevated process. + { + hr = ElevationCacheCompletePayload(pContext->hPipe, pPackage, pPayload, wzUnverifiedPath, fMove, CacheMessageHandler, CacheProgressRoutine, &progress); + } + else // complete the payload. + { + hr = CacheCompletePayload(pPackage->fPerMachine, pPayload, pPackage->sczCacheId, wzUnverifiedPath, fMove, CacheMessageHandler, CacheProgressRoutine, &progress); + } + } + + if (SUCCEEDED(hr) && fCanAffectRegistration) + { + pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + + BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION action = FAILED(hr) && !fCanceledBegin && cTryAgainAttempts < BURN_CACHE_MAX_RECOMMENDED_VERIFY_TRYAGAIN_ATTEMPTS ? BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION_RETRYACQUISITION : BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION_NONE; + UserExperienceOnCacheVerifyComplete(pContext->pUX, wzPackageOrContainerId, wzPayloadId, hr, &action); + if (BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION_RETRYVERIFICATION == action) + { + hr = S_FALSE; // retry verify. + } + else if (BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION_RETRYACQUISITION == action) + { + *pfRetry = TRUE; // go back and retry acquire. + } + else if (fCanceledBegin) + { + ExitOnRootFailure(hr, "BA aborted cache verify begin."); + } + } while (S_FALSE == hr); + + if (SUCCEEDED(hr) && pPayloadGroupItem) + { + pPayload->cRemainingInstances -= 1; + pPayloadGroupItem->fCached = TRUE; + } + +LExit: + return hr; +} + +static HRESULT PreparePayloadDestinationPath( + __in_z LPCWSTR wzDestinationPath + ) +{ + HRESULT hr = S_OK; + DWORD dwFileAttributes = 0; + + // If the destination file already exists, clear the readonly bit to avoid E_ACCESSDENIED. + if (FileExistsEx(wzDestinationPath, &dwFileAttributes)) + { + if (FILE_ATTRIBUTE_READONLY & dwFileAttributes) + { + dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY; + if (!::SetFileAttributes(wzDestinationPath, dwFileAttributes)) + { + ExitWithLastError(hr, "Failed to clear readonly bit on payload destination path: %ls", wzDestinationPath); + } + } + } + +LExit: + if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + { + hr = S_OK; + } + + return hr; +} + +static HRESULT CopyPayload( + __in BURN_CACHE_PROGRESS_CONTEXT* pProgress, + __in HANDLE hSourceFile, + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzDestinationPath + ) +{ + HRESULT hr = S_OK; + LPCWSTR wzPackageOrContainerId = pProgress->pContainer ? pProgress->pContainer->sczId : pProgress->pPackage ? pProgress->pPackage->sczId : L""; + LPCWSTR wzPayloadId = pProgress->pPayloadGroupItem ? pProgress->pPayloadGroupItem->pPayload->sczKey : L""; + HANDLE hDestinationFile = INVALID_HANDLE_VALUE; + HANDLE hSourceOpenedFile = INVALID_HANDLE_VALUE; + + DWORD dwLogId = pProgress->pContainer ? MSG_ACQUIRE_CONTAINER : pProgress->pPackage ? MSG_ACQUIRE_PACKAGE_PAYLOAD : MSG_ACQUIRE_BUNDLE_PAYLOAD; + LogId(REPORT_STANDARD, dwLogId, wzPackageOrContainerId, wzPayloadId, "copy", wzSourcePath); + + hr = PreparePayloadDestinationPath(wzDestinationPath); + ExitOnFailure(hr, "Failed to prepare payload destination path: %ls", wzDestinationPath); + + if (INVALID_HANDLE_VALUE == hSourceFile) + { + hSourceOpenedFile = ::CreateFileW(wzSourcePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (INVALID_HANDLE_VALUE == hSourceOpenedFile) + { + ExitWithLastError(hr, "Failed to open source file to copy payload from: '%ls' to: %ls.", wzSourcePath, wzDestinationPath); + } + + hSourceFile = hSourceOpenedFile; + } + else + { + hr = FileSetPointer(hSourceFile, 0, NULL, FILE_BEGIN); + ExitOnRootFailure(hr, "Failed to read from start of source file to copy payload from: '%ls' to: %ls.", wzSourcePath, wzDestinationPath); + } + + hDestinationFile = ::CreateFileW(wzDestinationPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (INVALID_HANDLE_VALUE == hDestinationFile) + { + ExitWithLastError(hr, "Failed to open destination file to copy payload from: '%ls' to: %ls.", wzSourcePath, wzDestinationPath); + } + + hr = FileCopyUsingHandlesWithProgress(hSourceFile, hDestinationFile, 0, CacheProgressRoutine, pProgress); + if (FAILED(hr)) + { + if (pProgress->fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + ExitOnRootFailure(hr, "BA aborted copy of payload from: '%ls' to: %ls.", wzSourcePath, wzDestinationPath); + } + else + { + ExitOnRootFailure(hr, "Failed attempt to copy payload from: '%ls' to: %ls.", wzSourcePath, wzDestinationPath); + } + } + +LExit: + ReleaseFileHandle(hDestinationFile); + ReleaseFileHandle(hSourceOpenedFile); + + return hr; +} + +static HRESULT DownloadPayload( + __in BURN_CACHE_PROGRESS_CONTEXT* pProgress, + __in_z LPCWSTR wzDestinationPath + ) +{ + HRESULT hr = S_OK; + LPCWSTR wzPackageOrContainerId = pProgress->pContainer ? pProgress->pContainer->sczId : pProgress->pPackage ? pProgress->pPackage->sczId : L""; + LPCWSTR wzPayloadId = pProgress->pPayloadGroupItem ? pProgress->pPayloadGroupItem->pPayload->sczKey : L""; + DOWNLOAD_SOURCE* pDownloadSource = pProgress->pContainer ? &pProgress->pContainer->downloadSource : &pProgress->pPayloadGroupItem->pPayload->downloadSource; + DWORD64 qwDownloadSize = pProgress->pContainer ? pProgress->pContainer->qwFileSize : pProgress->pPayloadGroupItem->pPayload->qwFileSize; + DOWNLOAD_CACHE_CALLBACK cacheCallback = { }; + DOWNLOAD_AUTHENTICATION_CALLBACK authenticationCallback = { }; + APPLY_AUTHENTICATION_REQUIRED_DATA authenticationData = { }; + + DWORD dwLogId = pProgress->pContainer ? MSG_ACQUIRE_CONTAINER : pProgress->pPackage ? MSG_ACQUIRE_PACKAGE_PAYLOAD : MSG_ACQUIRE_BUNDLE_PAYLOAD; + LogId(REPORT_STANDARD, dwLogId, wzPackageOrContainerId, wzPayloadId, "download", pDownloadSource->sczUrl); + + hr = PreparePayloadDestinationPath(wzDestinationPath); + ExitOnFailure(hr, "Failed to prepare payload destination path: %ls", wzDestinationPath); + + cacheCallback.pfnProgress = CacheProgressRoutine; + cacheCallback.pfnCancel = NULL; // TODO: set this + cacheCallback.pv = pProgress; + + authenticationData.pUX = pProgress->pCacheContext->pUX; + authenticationData.wzPackageOrContainerId = wzPackageOrContainerId; + authenticationData.wzPayloadId = wzPayloadId; + authenticationCallback.pv = static_cast(&authenticationData); + authenticationCallback.pfnAuthenticate = &AuthenticationRequired; + + hr = DownloadUrl(pDownloadSource, qwDownloadSize, wzDestinationPath, &cacheCallback, &authenticationCallback); + ExitOnFailure(hr, "Failed attempt to download URL: '%ls' to: '%ls'", pDownloadSource->sczUrl, wzDestinationPath); + +LExit: + return hr; +} + +static HRESULT WINAPI AuthenticationRequired( + __in LPVOID pData, + __in HINTERNET hUrl, + __in long lHttpCode, + __out BOOL* pfRetrySend, + __out BOOL* pfRetry + ) +{ + Assert(401 == lHttpCode || 407 == lHttpCode); + + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + BOOTSTRAPPER_ERROR_TYPE errorType = (401 == lHttpCode) ? BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_SERVER : BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_PROXY; + LPWSTR sczError = NULL; + int nResult = IDNOACTION; + + *pfRetrySend = FALSE; + *pfRetry = FALSE; + + hr = StrAllocFromError(&sczError, HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED), NULL); + ExitOnFailure(hr, "Failed to allocation error string."); + + APPLY_AUTHENTICATION_REQUIRED_DATA* authenticationData = reinterpret_cast(pData); + + UserExperienceOnError(authenticationData->pUX, errorType, authenticationData->wzPackageOrContainerId, ERROR_ACCESS_DENIED, sczError, MB_RETRYTRYAGAIN, 0, NULL, &nResult); // ignore return value; + nResult = UserExperienceCheckExecuteResult(authenticationData->pUX, FALSE, MB_RETRYTRYAGAIN, nResult); + if (IDTRYAGAIN == nResult && authenticationData->pUX->hwndApply) + { + er = ::InternetErrorDlg(authenticationData->pUX->hwndApply, hUrl, ERROR_INTERNET_INCORRECT_PASSWORD, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, NULL); + if (ERROR_SUCCESS == er || ERROR_CANCELLED == er) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + else if (ERROR_INTERNET_FORCE_RETRY == er) + { + *pfRetrySend = TRUE; + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); + } + } + else if (IDRETRY == nResult) + { + *pfRetry = TRUE; + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); + } + +LExit: + ReleaseStr(sczError); + + return hr; +} + +static HRESULT CALLBACK CacheMessageHandler( + __in BURN_CACHE_MESSAGE* pMessage, + __in LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + BURN_CACHE_PROGRESS_CONTEXT* pProgress = static_cast(pvContext); + LPCWSTR wzPackageOrContainerId = pProgress->pContainer ? pProgress->pContainer->sczId : pProgress->pPackage ? pProgress->pPackage->sczId : NULL; + LPCWSTR wzPayloadId = pProgress->pPayloadGroupItem ? pProgress->pPayloadGroupItem->pPayload->sczKey : pProgress->pPayload ? pProgress->pPayload->sczKey : NULL; + + switch (pMessage->type) + { + case BURN_CACHE_MESSAGE_BEGIN: + switch (pMessage->begin.cacheStep) + { + case BURN_CACHE_STEP_HASH_TO_SKIP_ACQUIRE: + pProgress->type = BURN_CACHE_PROGRESS_TYPE_CONTAINER_OR_PAYLOAD_VERIFY; + hr = UserExperienceOnCacheContainerOrPayloadVerifyBegin(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId); + break; + case BURN_CACHE_STEP_HASH_TO_SKIP_VERIFY: + pProgress->type = BURN_CACHE_PROGRESS_TYPE_PAYLOAD_VERIFY; + break; + case BURN_CACHE_STEP_STAGE: + pProgress->type = BURN_CACHE_PROGRESS_TYPE_STAGE; + break; + case BURN_CACHE_STEP_HASH: + pProgress->type = BURN_CACHE_PROGRESS_TYPE_HASH; + break; + case BURN_CACHE_STEP_FINALIZE: + pProgress->type = BURN_CACHE_PROGRESS_TYPE_FINALIZE; + break; + } + break; + case BURN_CACHE_MESSAGE_SUCCESS: + hr = CompleteCacheProgress(pProgress, pMessage->success.qwFileSize); + break; + case BURN_CACHE_MESSAGE_COMPLETE: + switch (pProgress->type) + { + case BURN_CACHE_PROGRESS_TYPE_CONTAINER_OR_PAYLOAD_VERIFY: + hr = UserExperienceOnCacheContainerOrPayloadVerifyComplete(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, hr); + break; + } + } + + return hr; +} + +static HRESULT CompleteCacheProgress( + __in BURN_CACHE_PROGRESS_CONTEXT* pContext, + __in DWORD64 qwFileSize + ) +{ + HRESULT hr = S_OK; + LARGE_INTEGER liContainerOrPayloadSize = { }; + LARGE_INTEGER liZero = { }; + DWORD dwResult = 0; + DWORD64 qwCommitSize = 0; + + liContainerOrPayloadSize.QuadPart = qwFileSize; + + // Need to commit the steps that were skipped. + if (BURN_CACHE_PROGRESS_TYPE_CONTAINER_OR_PAYLOAD_VERIFY == pContext->type || BURN_CACHE_PROGRESS_TYPE_PAYLOAD_VERIFY == pContext->type) + { + Assert(!pContext->pPayload); + + qwCommitSize = qwFileSize * (pContext->pCacheContext->wzLayoutDirectory ? 2 : 3); // Acquire (+ Stage) + Hash + Finalize - 1 (that's added later) + + pContext->pCacheContext->qwSuccessfulCacheProgress += qwCommitSize; + + if (pContext->pContainer) + { + pContext->pContainer->qwCommittedCacheProgress += qwCommitSize; + } + else if (pContext->pPayloadGroupItem) + { + pContext->pPayloadGroupItem->qwCommittedCacheProgress += qwCommitSize; + } + } + + dwResult = CacheProgressRoutine(liContainerOrPayloadSize, liContainerOrPayloadSize, liZero, liZero, 0, 0, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, pContext); + + if (PROGRESS_CONTINUE == dwResult) + { + pContext->pCacheContext->qwSuccessfulCacheProgress += qwFileSize; + + if (pContext->pPayload) + { + pContext->pContainer->qwCommittedExtractProgress += qwFileSize; + } + else if (pContext->pContainer) + { + pContext->pContainer->qwCommittedCacheProgress += qwFileSize; + } + else if (pContext->pPayloadGroupItem) + { + pContext->pPayloadGroupItem->qwCommittedCacheProgress += qwFileSize; + } + + if (BURN_CACHE_PROGRESS_TYPE_FINALIZE == pContext->type && pContext->pCacheContext->sczLastUsedFolderCandidate) + { + // We successfully copied from a source location, set that as the last used source. + CacheSetLastUsedSource(pContext->pCacheContext->pVariables, pContext->pCacheContext->sczLastUsedFolderCandidate, pContext->pContainer ? pContext->pContainer->sczFilePath : pContext->pPayloadGroupItem->pPayload->sczFilePath); + } + } + else if (PROGRESS_CANCEL == dwResult) + { + if (pContext->fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + else + { + hr = pContext->hrError; + } + + if (qwCommitSize) + { + pContext->pCacheContext->qwSuccessfulCacheProgress -= qwCommitSize; + + if (pContext->pContainer) + { + pContext->pContainer->qwCommittedCacheProgress -= qwCommitSize; + } + else if (pContext->pPayloadGroupItem) + { + pContext->pPayloadGroupItem->qwCommittedCacheProgress -= qwCommitSize; + } + } + } + + return hr; +} + +static DWORD CALLBACK CacheProgressRoutine( + __in LARGE_INTEGER TotalFileSize, + __in LARGE_INTEGER TotalBytesTransferred, + __in LARGE_INTEGER /*StreamSize*/, + __in LARGE_INTEGER /*StreamBytesTransferred*/, + __in DWORD /*dwStreamNumber*/, + __in DWORD /*dwCallbackReason*/, + __in HANDLE /*hSourceFile*/, + __in HANDLE /*hDestinationFile*/, + __in_opt LPVOID lpData + ) +{ + HRESULT hr = S_OK; + DWORD dwResult = PROGRESS_CONTINUE; + BURN_CACHE_PROGRESS_CONTEXT* pProgress = static_cast(lpData); + LPCWSTR wzPackageOrContainerId = pProgress->pContainer ? pProgress->pContainer->sczId : pProgress->pPackage ? pProgress->pPackage->sczId : NULL; + LPCWSTR wzPayloadId = pProgress->pPayloadGroupItem ? pProgress->pPayloadGroupItem->pPayload->sczKey : pProgress->pPayload ? pProgress->pPayload->sczKey : NULL; + DWORD64 qwCacheProgress = pProgress->pCacheContext->qwSuccessfulCacheProgress + TotalBytesTransferred.QuadPart; + if (qwCacheProgress > pProgress->pCacheContext->qwTotalCacheSize) + { + //AssertSz(FALSE, "Apply has cached more than Plan envisioned."); + qwCacheProgress = pProgress->pCacheContext->qwTotalCacheSize; + } + DWORD dwOverallPercentage = pProgress->pCacheContext->qwTotalCacheSize ? static_cast(qwCacheProgress * 100 / pProgress->pCacheContext->qwTotalCacheSize) : 0; + + switch (pProgress->type) + { + case BURN_CACHE_PROGRESS_TYPE_ACQUIRE: + hr = UserExperienceOnCacheAcquireProgress(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, TotalBytesTransferred.QuadPart, TotalFileSize.QuadPart, dwOverallPercentage); + ExitOnRootFailure(hr, "BA aborted acquire of %hs: %ls", pProgress->pContainer ? "container" : "payload", pProgress->pContainer ? wzPackageOrContainerId : wzPayloadId); + break; + case BURN_CACHE_PROGRESS_TYPE_PAYLOAD_VERIFY: + hr = UserExperienceOnCacheVerifyProgress(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, TotalBytesTransferred.QuadPart, TotalFileSize.QuadPart, dwOverallPercentage, BOOTSTRAPPER_CACHE_VERIFY_STEP_HASH); + ExitOnRootFailure(hr, "BA aborted payload verify step during verify of %hs: %ls", pProgress->pContainer ? "container" : "payload", pProgress->pContainer ? wzPackageOrContainerId : wzPayloadId); + break; + case BURN_CACHE_PROGRESS_TYPE_STAGE: + hr = UserExperienceOnCacheVerifyProgress(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, TotalBytesTransferred.QuadPart, TotalFileSize.QuadPart, dwOverallPercentage, BOOTSTRAPPER_CACHE_VERIFY_STEP_STAGE); + ExitOnRootFailure(hr, "BA aborted stage step during verify of %hs: %ls", pProgress->pContainer ? "container" : "payload", pProgress->pContainer ? wzPackageOrContainerId : wzPayloadId); + break; + case BURN_CACHE_PROGRESS_TYPE_HASH: + hr = UserExperienceOnCacheVerifyProgress(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, TotalBytesTransferred.QuadPart, TotalFileSize.QuadPart, dwOverallPercentage, BOOTSTRAPPER_CACHE_VERIFY_STEP_HASH); + ExitOnRootFailure(hr, "BA aborted hash step during verify of %hs: %ls", pProgress->pContainer ? "container" : "payload", pProgress->pContainer ? wzPackageOrContainerId : wzPayloadId); + break; + case BURN_CACHE_PROGRESS_TYPE_FINALIZE: + hr = UserExperienceOnCacheVerifyProgress(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, TotalBytesTransferred.QuadPart, TotalFileSize.QuadPart, dwOverallPercentage, BOOTSTRAPPER_CACHE_VERIFY_STEP_FINALIZE); + ExitOnRootFailure(hr, "BA aborted finalize step during verify of %hs: %ls", pProgress->pContainer ? "container" : "payload", pProgress->pContainer ? wzPackageOrContainerId : wzPayloadId); + break; + case BURN_CACHE_PROGRESS_TYPE_CONTAINER_OR_PAYLOAD_VERIFY: + hr = UserExperienceOnCacheContainerOrPayloadVerifyProgress(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, TotalBytesTransferred.QuadPart, TotalFileSize.QuadPart, dwOverallPercentage); + ExitOnRootFailure(hr, "BA aborted container or payload verify: %ls", wzPayloadId); + break; + case BURN_CACHE_PROGRESS_TYPE_EXTRACT: + hr = UserExperienceOnCachePayloadExtractProgress(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, TotalBytesTransferred.QuadPart, TotalFileSize.QuadPart, dwOverallPercentage); + ExitOnRootFailure(hr, "BA aborted extract container: %ls, payload: %ls", wzPackageOrContainerId, wzPayloadId); + break; + } + +LExit: + if (HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) == hr) + { + dwResult = PROGRESS_CANCEL; + pProgress->fCancel = TRUE; + } + else if (FAILED(hr)) + { + dwResult = PROGRESS_CANCEL; + pProgress->hrError = hr; + } + else + { + dwResult = PROGRESS_CONTINUE; + } + + return dwResult; +} + +static void DoRollbackCache( + __in BURN_USER_EXPERIENCE* /*pUX*/, + __in BURN_PLAN* pPlan, + __in HANDLE hPipe, + __in DWORD dwCheckpoint + ) +{ + HRESULT hr = S_OK; + DWORD iCheckpoint = 0; + + // Scan to last checkpoint. + for (DWORD i = 0; i < pPlan->cRollbackCacheActions; ++i) + { + BURN_CACHE_ACTION* pRollbackCacheAction = &pPlan->rgRollbackCacheActions[i]; + + if (BURN_CACHE_ACTION_TYPE_CHECKPOINT == pRollbackCacheAction->type && pRollbackCacheAction->checkpoint.dwId == dwCheckpoint) + { + iCheckpoint = i; + break; + } + } + + // Rollback cache actions. + if (iCheckpoint) + { + // i has to be a signed integer so it doesn't get decremented to 0xFFFFFFFF. + for (int i = iCheckpoint - 1; i >= 0; --i) + { + BURN_CACHE_ACTION* pRollbackCacheAction = &pPlan->rgRollbackCacheActions[i]; + + switch (pRollbackCacheAction->type) + { + case BURN_CACHE_ACTION_TYPE_CHECKPOINT: + break; + + case BURN_CACHE_ACTION_TYPE_ROLLBACK_PACKAGE: + hr = CleanPackage(hPipe, pRollbackCacheAction->rollbackPackage.pPackage); + break; + + default: + AssertSz(FALSE, "Invalid rollback cache action."); + break; + } + } + } +} + +static HRESULT DoExecuteAction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in_opt HANDLE hCacheThread, + __in BURN_EXECUTE_CONTEXT* pContext, + __inout BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary, + __inout BURN_EXECUTE_ACTION_CHECKPOINT** ppCheckpoint, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + Assert(!pExecuteAction->fDeleted); + + HRESULT hr = S_OK; + HANDLE rghWait[2] = { }; + BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE; + BOOL fRetry = FALSE; + BOOL fStopWusaService = FALSE; + BOOL fInsideMsiTransaction = FALSE; + + pContext->fRollback = FALSE; + + do + { + fInsideMsiTransaction = *ppRollbackBoundary && (*ppRollbackBoundary)->fActiveTransaction; + + switch (pExecuteAction->type) + { + case BURN_EXECUTE_ACTION_TYPE_CHECKPOINT: + *ppCheckpoint = &pExecuteAction->checkpoint; + break; + + case BURN_EXECUTE_ACTION_TYPE_WAIT_SYNCPOINT: + // wait for cache sync-point + rghWait[0] = pExecuteAction->syncpoint.hEvent; + rghWait[1] = hCacheThread; + switch (::WaitForMultipleObjects(rghWait[1] ? 2 : 1, rghWait, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + break; + + case WAIT_OBJECT_0 + 1: + if (!::GetExitCodeThread(hCacheThread, (DWORD*)&hr)) + { + ExitWithLastError(hr, "Failed to get cache thread exit code."); + } + + if (SUCCEEDED(hr)) + { + hr = E_UNEXPECTED; + } + ExitOnFailure(hr, "Cache thread exited unexpectedly."); + + case WAIT_FAILED: __fallthrough; + default: + ExitWithLastError(hr, "Failed to wait for cache check-point."); + } + break; + + case BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE: + hr = ExecuteExePackage(pEngineState, pExecuteAction, pContext, FALSE, &fRetry, pfSuspend, &restart); + ExitOnFailure(hr, "Failed to execute EXE package."); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE: + hr = ExecuteMsiPackage(pEngineState, pExecuteAction, pContext, fInsideMsiTransaction, FALSE, &fRetry, pfSuspend, &restart); + ExitOnFailure(hr, "Failed to execute MSI package."); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSP_TARGET: + hr = ExecuteMspPackage(pEngineState, pExecuteAction, pContext, fInsideMsiTransaction, FALSE, &fRetry, pfSuspend, &restart); + ExitOnFailure(hr, "Failed to execute MSP package."); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE: + hr = ExecuteMsuPackage(pEngineState, pExecuteAction, pContext, FALSE, fStopWusaService, &fRetry, pfSuspend, &restart); + fStopWusaService = fRetry; + ExitOnFailure(hr, "Failed to execute MSU package."); + break; + + case BURN_EXECUTE_ACTION_TYPE_PACKAGE_PROVIDER: + hr = ExecutePackageProviderAction(pEngineState, pExecuteAction, pContext); + ExitOnFailure(hr, "Failed to execute package provider registration action."); + break; + + case BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY: + hr = ExecuteDependencyAction(pEngineState, pExecuteAction, pContext); + ExitOnFailure(hr, "Failed to execute dependency action."); + break; + + break; + + case BURN_EXECUTE_ACTION_TYPE_ROLLBACK_BOUNDARY: + *ppRollbackBoundary = pExecuteAction->rollbackBoundary.pRollbackBoundary; + break; + + case BURN_EXECUTE_ACTION_TYPE_BEGIN_MSI_TRANSACTION: + hr = ExecuteMsiBeginTransaction(pEngineState, pExecuteAction->msiTransaction.pRollbackBoundary, pContext); + ExitOnFailure(hr, "Failed to execute begin MSI transaction action."); + break; + + case BURN_EXECUTE_ACTION_TYPE_COMMIT_MSI_TRANSACTION: + hr = ExecuteMsiCommitTransaction(pEngineState, pExecuteAction->msiTransaction.pRollbackBoundary, pContext); + ExitOnFailure(hr, "Failed to execute commit MSI transaction action."); + break; + + default: + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Invalid execute action."); + } + + if (*pRestart < restart) + { + *pRestart = restart; + } + } while (fRetry && *pRestart < BOOTSTRAPPER_APPLY_RESTART_INITIATED); + +LExit: + return hr; +} + +static HRESULT DoRollbackActions( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_CONTEXT* pContext, + __in DWORD dwCheckpoint, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + DWORD iCheckpoint = 0; + BOOL fRetryIgnored = FALSE; + BOOL fSuspendIgnored = FALSE; + + pContext->fRollback = TRUE; + + // scan to last checkpoint + for (DWORD i = 0; i < pEngineState->plan.cRollbackActions; ++i) + { + BURN_EXECUTE_ACTION* pRollbackAction = &pEngineState->plan.rgRollbackActions[i]; + if (pRollbackAction->fDeleted) + { + continue; + } + + if (BURN_EXECUTE_ACTION_TYPE_CHECKPOINT == pRollbackAction->type) + { + if (pRollbackAction->checkpoint.dwId == dwCheckpoint) + { + iCheckpoint = i; + break; + } + } + } + + // execute rollback actions + if (iCheckpoint) + { + // i has to be a signed integer so it doesn't get decremented to 0xFFFFFFFF. + for (int i = iCheckpoint - 1; i >= 0; --i) + { + BURN_EXECUTE_ACTION* pRollbackAction = &pEngineState->plan.rgRollbackActions[i]; + if (pRollbackAction->fDeleted) + { + continue; + } + + BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE; + switch (pRollbackAction->type) + { + case BURN_EXECUTE_ACTION_TYPE_CHECKPOINT: + break; + + case BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE: + hr = ExecuteExePackage(pEngineState, pRollbackAction, pContext, TRUE, &fRetryIgnored, &fSuspendIgnored, &restart); + IgnoreRollbackError(hr, "Failed to rollback EXE package."); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE: + hr = ExecuteMsiPackage(pEngineState, pRollbackAction, pContext, FALSE, TRUE, &fRetryIgnored, &fSuspendIgnored, &restart); + IgnoreRollbackError(hr, "Failed to rollback MSI package."); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSP_TARGET: + hr = ExecuteMspPackage(pEngineState, pRollbackAction, pContext, FALSE, TRUE, &fRetryIgnored, &fSuspendIgnored, &restart); + IgnoreRollbackError(hr, "Failed to rollback MSP package."); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE: + hr = ExecuteMsuPackage(pEngineState, pRollbackAction, pContext, TRUE, FALSE, &fRetryIgnored, &fSuspendIgnored, &restart); + IgnoreRollbackError(hr, "Failed to rollback MSU package."); + break; + + case BURN_EXECUTE_ACTION_TYPE_PACKAGE_PROVIDER: + hr = ExecutePackageProviderAction(pEngineState, pRollbackAction, pContext); + IgnoreRollbackError(hr, "Failed to rollback package provider action."); + break; + + case BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY: + hr = ExecuteDependencyAction(pEngineState, pRollbackAction, pContext); + IgnoreRollbackError(hr, "Failed to rollback dependency action."); + break; + + case BURN_EXECUTE_ACTION_TYPE_ROLLBACK_BOUNDARY: + ExitFunction1(hr = S_OK); + + case BURN_EXECUTE_ACTION_TYPE_UNCACHE_PACKAGE: + // TODO: This used to be skipped if the package was already cached. + // Need to figure out new logic for when (if?) to skip it. + hr = CleanPackage(pEngineState->companionConnection.hPipe, pRollbackAction->uncachePackage.pPackage); + IgnoreRollbackError(hr, "Failed to uncache package for rollback."); + break; + + default: + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Invalid rollback action: %d.", pRollbackAction->type); + } + + if (*pRestart < restart) + { + *pRestart = restart; + } + } + } + +LExit: + return hr; +} + +static HRESULT ExecuteExePackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __in BOOL fRollback, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + HRESULT hrExecute = S_OK; + GENERIC_EXECUTE_MESSAGE message = { }; + int nResult = 0; + BOOL fBeginCalled = FALSE; + BOOL fExecuted = FALSE; + BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage; + + if (FAILED(pPackage->hrCacheResult)) + { + LogId(REPORT_STANDARD, MSG_APPLY_SKIPPED_FAILED_CACHED_PACKAGE, pPackage->sczId, pPackage->hrCacheResult); + ExitFunction1(hr = S_OK); + } + + Assert(pContext->fRollback == fRollback); + pContext->pExecutingPackage = pPackage; + fBeginCalled = TRUE; + + // Send package execute begin to BA. + hr = UserExperienceOnExecutePackageBegin(&pEngineState->userExperience, pPackage->sczId, !fRollback, pExecuteAction->exePackage.action, INSTALLUILEVEL_NOCHANGE, FALSE); + ExitOnRootFailure(hr, "BA aborted execute EXE package begin."); + + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwAllowedResults = MB_OKCANCEL; + message.progress.dwPercentage = fRollback ? 100 : 0; + nResult = GenericExecuteMessageHandler(&message, pContext); + hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwAllowedResults, nResult); + ExitOnRootFailure(hr, "BA aborted EXE progress."); + + fExecuted = TRUE; + + // Execute package. + if (pPackage->fPerMachine) + { + hrExecute = ElevationExecuteExePackage(pEngineState->companionConnection.hPipe, pExecuteAction, &pEngineState->variables, fRollback, GenericExecuteMessageHandler, pContext, pRestart); + ExitOnFailure(hrExecute, "Failed to configure per-machine EXE package."); + } + else + { + hrExecute = ExeEngineExecutePackage(pExecuteAction, &pEngineState->variables, fRollback, GenericExecuteMessageHandler, pContext, pRestart); + ExitOnFailure(hrExecute, "Failed to configure per-user EXE package."); + } + + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwAllowedResults = MB_OKCANCEL; + message.progress.dwPercentage = fRollback ? 0 : 100; + nResult = GenericExecuteMessageHandler(&message, pContext); + hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwAllowedResults, nResult); + ExitOnRootFailure(hr, "BA aborted EXE progress."); + + pContext->cExecutedPackages += fRollback ? -1 : 1; + (*pContext->pcOverallProgressTicks) += fRollback ? -1 : 1; + + hr = ReportOverallProgressTicks(&pEngineState->userExperience, fRollback, pEngineState->plan.cOverallProgressTicksTotal, *pContext->pcOverallProgressTicks); + ExitOnRootFailure(hr, "BA aborted EXE package execute progress."); + +LExit: + if (fExecuted) + { + ExeEngineUpdateInstallRegistrationState(pExecuteAction, hrExecute); + } + + if (fBeginCalled) + { + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + } + + return hr; +} + +static HRESULT ExecuteMsiPackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __in BOOL fInsideMsiTransaction, + __in BOOL fRollback, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + HRESULT hrExecute = S_OK; + BOOL fBeginCalled = FALSE; + BOOL fExecuted = FALSE; + BURN_PACKAGE* pPackage = pExecuteAction->msiPackage.pPackage; + + if (FAILED(pPackage->hrCacheResult)) + { + LogId(REPORT_STANDARD, MSG_APPLY_SKIPPED_FAILED_CACHED_PACKAGE, pPackage->sczId, pPackage->hrCacheResult); + ExitFunction1(hr = S_OK); + } + + Assert(pContext->fRollback == fRollback); + pContext->pExecutingPackage = pPackage; + fBeginCalled = TRUE; + + // Send package execute begin to BA. + hr = UserExperienceOnExecutePackageBegin(&pEngineState->userExperience, pPackage->sczId, !fRollback, pExecuteAction->msiPackage.action, pExecuteAction->msiPackage.uiLevel, pExecuteAction->msiPackage.fDisableExternalUiHandler); + ExitOnRootFailure(hr, "BA aborted execute MSI package begin."); + + fExecuted = TRUE; + + // execute package + if (pPackage->fPerMachine) + { + hrExecute = ElevationExecuteMsiPackage(pEngineState->companionConnection.hPipe, pEngineState->userExperience.hwndApply, pExecuteAction, &pEngineState->variables, fRollback, MsiExecuteMessageHandler, pContext, pRestart); + ExitOnFailure(hrExecute, "Failed to configure per-machine MSI package."); + } + else + { + hrExecute = MsiEngineExecutePackage(pEngineState->userExperience.hwndApply, pExecuteAction, &pEngineState->variables, fRollback, MsiExecuteMessageHandler, pContext, pRestart); + ExitOnFailure(hrExecute, "Failed to configure per-user MSI package."); + } + + pContext->cExecutedPackages += fRollback ? -1 : 1; + (*pContext->pcOverallProgressTicks) += fRollback ? -1 : 1; + + hr = ReportOverallProgressTicks(&pEngineState->userExperience, fRollback, pEngineState->plan.cOverallProgressTicksTotal, *pContext->pcOverallProgressTicks); + ExitOnRootFailure(hr, "BA aborted MSI package execute progress."); + +LExit: + if (fExecuted) + { + MsiEngineUpdateInstallRegistrationState(pExecuteAction, fRollback, hrExecute, fInsideMsiTransaction); + } + + if (fBeginCalled) + { + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + } + + return hr; +} + +static HRESULT ExecuteMspPackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __in BOOL fInsideMsiTransaction, + __in BOOL fRollback, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + HRESULT hrExecute = S_OK; + BOOL fBeginCalled = FALSE; + BOOL fExecuted = FALSE; + BURN_PACKAGE* pPackage = pExecuteAction->mspTarget.pPackage; + + if (FAILED(pPackage->hrCacheResult)) + { + LogId(REPORT_STANDARD, MSG_APPLY_SKIPPED_FAILED_CACHED_PACKAGE, pPackage->sczId, pPackage->hrCacheResult); + ExitFunction1(hr = S_OK); + } + + Assert(pContext->fRollback == fRollback); + pContext->pExecutingPackage = pPackage; + fBeginCalled = TRUE; + + // Send package execute begin to BA. + hr = UserExperienceOnExecutePackageBegin(&pEngineState->userExperience, pPackage->sczId, !fRollback, pExecuteAction->mspTarget.action, pExecuteAction->mspTarget.uiLevel, pExecuteAction->mspTarget.fDisableExternalUiHandler); + ExitOnRootFailure(hr, "BA aborted execute MSP package begin."); + + // Now send all the patches that target this product code. + for (DWORD i = 0; i < pExecuteAction->mspTarget.cOrderedPatches; ++i) + { + BURN_PACKAGE* pMspPackage = pExecuteAction->mspTarget.rgOrderedPatches[i].pPackage; + + hr = UserExperienceOnExecutePatchTarget(&pEngineState->userExperience, pMspPackage->sczId, pExecuteAction->mspTarget.sczTargetProductCode); + ExitOnRootFailure(hr, "BA aborted execute MSP target."); + } + + fExecuted = TRUE; + + // execute package + if (pExecuteAction->mspTarget.fPerMachineTarget) + { + hrExecute = ElevationExecuteMspPackage(pEngineState->companionConnection.hPipe, pEngineState->userExperience.hwndApply, pExecuteAction, &pEngineState->variables, fRollback, MsiExecuteMessageHandler, pContext, pRestart); + ExitOnFailure(hrExecute, "Failed to configure per-machine MSP package."); + } + else + { + hrExecute = MspEngineExecutePackage(pEngineState->userExperience.hwndApply, pExecuteAction, &pEngineState->variables, fRollback, MsiExecuteMessageHandler, pContext, pRestart); + ExitOnFailure(hrExecute, "Failed to configure per-user MSP package."); + } + + pContext->cExecutedPackages += fRollback ? -1 : 1; + (*pContext->pcOverallProgressTicks) += fRollback ? -1 : 1; + + hr = ReportOverallProgressTicks(&pEngineState->userExperience, fRollback, pEngineState->plan.cOverallProgressTicksTotal, *pContext->pcOverallProgressTicks); + ExitOnRootFailure(hr, "BA aborted MSP package execute progress."); + +LExit: + if (fExecuted) + { + MspEngineUpdateInstallRegistrationState(pExecuteAction, hrExecute, fInsideMsiTransaction); + } + + if (fBeginCalled) + { + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + } + + return hr; +} + +static HRESULT ExecuteMsuPackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_EXECUTE_CONTEXT* pContext, + __in BOOL fRollback, + __in BOOL fStopWusaService, + __out BOOL* pfRetry, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + HRESULT hrExecute = S_OK; + GENERIC_EXECUTE_MESSAGE message = { }; + int nResult = 0; + BOOL fBeginCalled = FALSE; + BOOL fExecuted = FALSE; + BURN_PACKAGE* pPackage = pExecuteAction->msuPackage.pPackage; + + if (FAILED(pPackage->hrCacheResult)) + { + LogId(REPORT_STANDARD, MSG_APPLY_SKIPPED_FAILED_CACHED_PACKAGE, pPackage->sczId, pPackage->hrCacheResult); + ExitFunction1(hr = S_OK); + } + + Assert(pContext->fRollback == fRollback); + pContext->pExecutingPackage = pPackage; + fBeginCalled = TRUE; + + // Send package execute begin to BA. + hr = UserExperienceOnExecutePackageBegin(&pEngineState->userExperience, pPackage->sczId, !fRollback, pExecuteAction->msuPackage.action, INSTALLUILEVEL_NOCHANGE, FALSE); + ExitOnRootFailure(hr, "BA aborted execute MSU package begin."); + + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwAllowedResults = MB_OKCANCEL; + message.progress.dwPercentage = fRollback ? 100 : 0; + nResult = GenericExecuteMessageHandler(&message, pContext); + hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwAllowedResults, nResult); + ExitOnRootFailure(hr, "BA aborted MSU progress."); + + fExecuted = TRUE; + + // execute package + if (pPackage->fPerMachine) + { + hrExecute = ElevationExecuteMsuPackage(pEngineState->companionConnection.hPipe, pExecuteAction, fRollback, fStopWusaService, GenericExecuteMessageHandler, pContext, pRestart); + ExitOnFailure(hrExecute, "Failed to configure per-machine MSU package."); + } + else + { + hrExecute = E_UNEXPECTED; + ExitOnFailure(hr, "MSU packages cannot be per-user."); + } + + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwAllowedResults = MB_OKCANCEL; + message.progress.dwPercentage = fRollback ? 0 : 100; + nResult = GenericExecuteMessageHandler(&message, pContext); + hr = UserExperienceInterpretExecuteResult(&pEngineState->userExperience, fRollback, message.dwAllowedResults, nResult); + ExitOnRootFailure(hr, "BA aborted MSU progress."); + + pContext->cExecutedPackages += fRollback ? -1 : 1; + (*pContext->pcOverallProgressTicks) += fRollback ? -1 : 1; + + hr = ReportOverallProgressTicks(&pEngineState->userExperience, fRollback, pEngineState->plan.cOverallProgressTicksTotal, *pContext->pcOverallProgressTicks); + ExitOnRootFailure(hr, "BA aborted MSU package execute progress."); + +LExit: + if (fExecuted) + { + MsuEngineUpdateInstallRegistrationState(pExecuteAction, hrExecute); + } + + if (fBeginCalled) + { + hr = ExecutePackageComplete(&pEngineState->userExperience, &pEngineState->variables, pPackage, hr, hrExecute, fRollback, pRestart, pfRetry, pfSuspend); + } + + return hr; +} + +static HRESULT ExecutePackageProviderAction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pAction, + __in BURN_EXECUTE_CONTEXT* /*pContext*/ + ) +{ + HRESULT hr = S_OK; + + if (pAction->packageProvider.pPackage->fPerMachine) + { + hr = ElevationExecutePackageProviderAction(pEngineState->companionConnection.hPipe, pAction); + ExitOnFailure(hr, "Failed to register the package provider on per-machine package."); + } + else + { + hr = DependencyExecutePackageProviderAction(pAction); + ExitOnFailure(hr, "Failed to register the package provider on per-user package."); + } + +LExit: + return hr; +} + +static HRESULT ExecuteDependencyAction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_EXECUTE_ACTION* pAction, + __in BURN_EXECUTE_CONTEXT* /*pContext*/ + ) +{ + HRESULT hr = S_OK; + + if (pAction->packageDependency.pPackage->fPerMachine) + { + hr = ElevationExecutePackageDependencyAction(pEngineState->companionConnection.hPipe, pAction); + ExitOnFailure(hr, "Failed to register the dependency on per-machine package."); + } + else + { + hr = DependencyExecutePackageDependencyAction(FALSE, pAction); + ExitOnFailure(hr, "Failed to register the dependency on per-user package."); + } + + if (pAction->packageDependency.pPackage->fCanAffectRegistration) + { + if (BURN_DEPENDENCY_ACTION_REGISTER == pAction->packageDependency.action) + { + if (BURN_PACKAGE_REGISTRATION_STATE_IGNORED == pAction->packageDependency.pPackage->cacheRegistrationState) + { + pAction->packageDependency.pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + + if (BURN_PACKAGE_TYPE_MSP == pAction->packageDependency.pPackage->type) + { + for (DWORD i = 0; i < pAction->packageDependency.pPackage->Msp.cTargetProductCodes; ++i) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pAction->packageDependency.pPackage->Msp.rgTargetProducts + i; + + if (BURN_PACKAGE_REGISTRATION_STATE_IGNORED == pTargetProduct->registrationState) + { + pTargetProduct->registrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + } + } + else if (BURN_PACKAGE_REGISTRATION_STATE_IGNORED == pAction->packageDependency.pPackage->installRegistrationState) + { + pAction->packageDependency.pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + } + else if (BURN_DEPENDENCY_ACTION_UNREGISTER == pAction->packageDependency.action) + { + if (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pAction->packageDependency.pPackage->cacheRegistrationState) + { + pAction->packageDependency.pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_IGNORED; + } + + if (BURN_PACKAGE_TYPE_MSP == pAction->packageDependency.pPackage->type) + { + for (DWORD i = 0; i < pAction->packageDependency.pPackage->Msp.cTargetProductCodes; ++i) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pAction->packageDependency.pPackage->Msp.rgTargetProducts + i; + + if (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pTargetProduct->registrationState) + { + pTargetProduct->registrationState = BURN_PACKAGE_REGISTRATION_STATE_IGNORED; + } + } + } + else if (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pAction->packageDependency.pPackage->installRegistrationState) + { + pAction->packageDependency.pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_IGNORED; + } + } + } + +LExit: + return hr; +} + +static HRESULT ExecuteMsiBeginTransaction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary, + __in BURN_EXECUTE_CONTEXT* /*pContext*/ + ) +{ + HRESULT hr = S_OK; + BOOL fBeginCalled = FALSE; + + if (pRollbackBoundary->fActiveTransaction) + { + ExitFunction1(hr = E_INVALIDSTATE); + } + + fBeginCalled = TRUE; + hr = UserExperienceOnBeginMsiTransactionBegin(&pEngineState->userExperience, pRollbackBoundary->sczId); + ExitOnRootFailure(hr, "BA aborted execute begin MSI transaction."); + + if (pEngineState->plan.fPerMachine) + { + hr = ElevationMsiBeginTransaction(pEngineState->companionConnection.hPipe, pRollbackBoundary); + ExitOnFailure(hr, "Failed to begin an elevated MSI transaction."); + } + else + { + hr = MsiEngineBeginTransaction(pRollbackBoundary); + } + + if (SUCCEEDED(hr)) + { + pRollbackBoundary->fActiveTransaction = TRUE; + + ResetTransactionRegistrationState(pEngineState, FALSE); + } + +LExit: + if (fBeginCalled) + { + UserExperienceOnBeginMsiTransactionComplete(&pEngineState->userExperience, pRollbackBoundary->sczId, hr); + } + + return hr; +} + +static HRESULT ExecuteMsiCommitTransaction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary, + __in BURN_EXECUTE_CONTEXT* /*pContext*/ + ) +{ + HRESULT hr = S_OK; + BOOL fCommitBeginCalled = FALSE; + + if (!pRollbackBoundary->fActiveTransaction) + { + ExitFunction1(hr = E_INVALIDSTATE); + } + + fCommitBeginCalled = TRUE; + hr = UserExperienceOnCommitMsiTransactionBegin(&pEngineState->userExperience, pRollbackBoundary->sczId); + ExitOnRootFailure(hr, "BA aborted execute commit MSI transaction."); + + if (pEngineState->plan.fPerMachine) + { + hr = ElevationMsiCommitTransaction(pEngineState->companionConnection.hPipe, pRollbackBoundary); + ExitOnFailure(hr, "Failed to commit an elevated MSI transaction."); + } + else + { + hr = MsiEngineCommitTransaction(pRollbackBoundary); + } + + if (SUCCEEDED(hr)) + { + pRollbackBoundary->fActiveTransaction = FALSE; + + ResetTransactionRegistrationState(pEngineState, TRUE); + } + +LExit: + if (fCommitBeginCalled) + { + UserExperienceOnCommitMsiTransactionComplete(&pEngineState->userExperience, pRollbackBoundary->sczId, hr); + } + + return hr; +} + +static HRESULT ExecuteMsiRollbackTransaction( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary, + __in BURN_EXECUTE_CONTEXT* /*pContext*/ + ) +{ + HRESULT hr = S_OK; + BOOL fRollbackBeginCalled = FALSE; + + if (!pRollbackBoundary->fActiveTransaction) + { + ExitFunction(); + } + + fRollbackBeginCalled = TRUE; + UserExperienceOnRollbackMsiTransactionBegin(&pEngineState->userExperience, pRollbackBoundary->sczId); + + if (pEngineState->plan.fPerMachine) + { + hr = ElevationMsiRollbackTransaction(pEngineState->companionConnection.hPipe, pRollbackBoundary); + ExitOnFailure(hr, "Failed to rollback an elevated MSI transaction."); + } + else + { + hr = MsiEngineRollbackTransaction(pRollbackBoundary); + } + +LExit: + pRollbackBoundary->fActiveTransaction = FALSE; + + ResetTransactionRegistrationState(pEngineState, FALSE); + + if (fRollbackBeginCalled) + { + UserExperienceOnRollbackMsiTransactionComplete(&pEngineState->userExperience, pRollbackBoundary->sczId, hr); + } + + return hr; +} + +static void ResetTransactionRegistrationState( + __in BURN_ENGINE_STATE* pEngineState, + __in BOOL fCommit + ) +{ + for (DWORD i = 0; i < pEngineState->packages.cPackages; ++i) + { + BURN_PACKAGE* pPackage = pEngineState->packages.rgPackages + i; + + if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + for (DWORD j = 0; j < pPackage->Msp.cTargetProductCodes; ++j) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + j; + + if (fCommit && BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN != pTargetProduct->transactionRegistrationState) + { + pTargetProduct->registrationState = pTargetProduct->transactionRegistrationState; + } + + pTargetProduct->transactionRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + } + } + else if (fCommit && BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN != pPackage->transactionRegistrationState) + { + pPackage->installRegistrationState = pPackage->transactionRegistrationState; + } + + pPackage->transactionRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + } +} + +static HRESULT CleanPackage( + __in HANDLE hElevatedPipe, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + + if (pPackage->fPerMachine) + { + hr = ElevationCleanPackage(hElevatedPipe, pPackage); + } + else + { + hr = CacheRemovePackage(FALSE, pPackage->sczId, pPackage->sczCacheId); + } + + if (pPackage->fCanAffectRegistration) + { + pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + + return hr; +} + +static int GenericExecuteMessageHandler( + __in GENERIC_EXECUTE_MESSAGE* pMessage, + __in LPVOID pvContext + ) +{ + BURN_EXECUTE_CONTEXT* pContext = (BURN_EXECUTE_CONTEXT*)pvContext; + int nResult = IDNOACTION; + + switch (pMessage->type) + { + case GENERIC_EXECUTE_MESSAGE_PROGRESS: + { + DWORD dwOverallProgress = pContext->cExecutePackagesTotal ? (pContext->cExecutedPackages * 100 + pMessage->progress.dwPercentage) / (pContext->cExecutePackagesTotal) : 0; + UserExperienceOnExecuteProgress(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->progress.dwPercentage, dwOverallProgress, &nResult); // ignore return value. + } + break; + + case GENERIC_EXECUTE_MESSAGE_ERROR: + UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_EXE_PACKAGE, pContext->pExecutingPackage->sczId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwAllowedResults, 0, NULL, &nResult); // ignore return value. + break; + + case GENERIC_EXECUTE_MESSAGE_FILES_IN_USE: + UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->filesInUse.cFiles, pMessage->filesInUse.rgwzFiles, &nResult); // ignore return value. + break; + } + + nResult = UserExperienceCheckExecuteResult(pContext->pUX, pContext->fRollback, pMessage->dwAllowedResults, nResult); + return nResult; +} + +static int MsiExecuteMessageHandler( + __in WIU_MSI_EXECUTE_MESSAGE* pMessage, + __in_opt LPVOID pvContext + ) +{ + BURN_EXECUTE_CONTEXT* pContext = (BURN_EXECUTE_CONTEXT*)pvContext; + int nResult = IDNOACTION; + + switch (pMessage->type) + { + case WIU_MSI_EXECUTE_MESSAGE_PROGRESS: + { + DWORD dwOverallProgress = pContext->cExecutePackagesTotal ? (pContext->cExecutedPackages * 100 + pMessage->progress.dwPercentage) / (pContext->cExecutePackagesTotal) : 0; + UserExperienceOnExecuteProgress(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->progress.dwPercentage, dwOverallProgress, &nResult); // ignore return value. + } + break; + + case WIU_MSI_EXECUTE_MESSAGE_ERROR: + nResult = pMessage->nResultRecommendation; + UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_WINDOWS_INSTALLER, pContext->pExecutingPackage->sczId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwAllowedResults, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. + break; + + case WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE: + nResult = pMessage->nResultRecommendation; + UserExperienceOnExecuteMsiMessage(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->msiMessage.mt, pMessage->dwAllowedResults, pMessage->msiMessage.wzMessage, pMessage->cData, pMessage->rgwzData, &nResult); // ignore return value. + break; + + case WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE: + UserExperienceOnExecuteFilesInUse(pContext->pUX, pContext->pExecutingPackage->sczId, pMessage->msiFilesInUse.cFiles, pMessage->msiFilesInUse.rgwzFiles, &nResult); // ignore return value. + break; + } + + nResult = UserExperienceCheckExecuteResult(pContext->pUX, pContext->fRollback, pMessage->dwAllowedResults, nResult); + return nResult; +} + +static HRESULT ReportOverallProgressTicks( + __in BURN_USER_EXPERIENCE* pUX, + __in BOOL fRollback, + __in DWORD cOverallProgressTicksTotal, + __in DWORD cOverallProgressTicks + ) +{ + HRESULT hr = S_OK; + DWORD dwProgress = cOverallProgressTicksTotal ? (cOverallProgressTicks * 100 / cOverallProgressTicksTotal) : 0; + + // TODO: consider sending different progress numbers in the future. + hr = UserExperienceOnProgress(pUX, fRollback, dwProgress, dwProgress); + + return hr; +} + +static HRESULT ExecutePackageComplete( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_VARIABLES* pVariables, + __in BURN_PACKAGE* pPackage, + __in HRESULT hrOverall, + __in HRESULT hrExecute, + __in BOOL fRollback, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart, + __out BOOL* pfRetry, + __out BOOL* pfSuspend + ) +{ + HRESULT hr = FAILED(hrOverall) ? hrOverall : hrExecute; // if the overall function failed use that otherwise use the execution result. + BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION executePackageCompleteAction = FAILED(hrOverall) || SUCCEEDED(hrExecute) || pPackage->fVital ? BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_NONE : BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_IGNORE; + + // Send package execute complete to BA. + UserExperienceOnExecutePackageComplete(pUX, pPackage->sczId, hr, *pRestart, &executePackageCompleteAction); + if (BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_RESTART == executePackageCompleteAction) + { + *pRestart = BOOTSTRAPPER_APPLY_RESTART_INITIATED; + } + *pfRetry = (FAILED(hrExecute) && BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_RETRY == executePackageCompleteAction); // allow retry only on failures. + *pfSuspend = (BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_SUSPEND == executePackageCompleteAction); + + // Remember this package as the package that initiated the forced restart. + if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == *pRestart) + { + // Best effort to set the forced restart package variable. + VariableSetString(pVariables, BURN_BUNDLE_FORCED_RESTART_PACKAGE, pPackage->sczId, TRUE, FALSE); + } + + // If we're retrying, leave a message in the log file and say everything is okay. + if (*pfRetry) + { + LogId(REPORT_STANDARD, MSG_APPLY_RETRYING_PACKAGE, pPackage->sczId, hrExecute); + hr = S_OK; + } + else if (SUCCEEDED(hrOverall) && FAILED(hrExecute) && BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_IGNORE == executePackageCompleteAction && !pPackage->fVital) // If we *only* failed to execute and the BA ignored this *not-vital* package, say everything is okay. + { + LogId(REPORT_STANDARD, MSG_APPLY_CONTINUING_NONVITAL_PACKAGE, pPackage->sczId, hrExecute); + hr = S_OK; + } + else + { + LogId(REPORT_STANDARD, MSG_APPLY_COMPLETED_PACKAGE, LoggingRollbackOrExecute(fRollback), pPackage->sczId, hr, LoggingRestartToString(*pRestart)); + } + + return hr; +} diff --git a/src/burn/engine/apply.h b/src/burn/engine/apply.h new file mode 100644 index 00000000..548e147d --- /dev/null +++ b/src/burn/engine/apply.h @@ -0,0 +1,104 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#ifdef __cplusplus +extern "C" { +#endif + + +enum GENERIC_EXECUTE_MESSAGE_TYPE +{ + GENERIC_EXECUTE_MESSAGE_NONE, + GENERIC_EXECUTE_MESSAGE_ERROR, + GENERIC_EXECUTE_MESSAGE_PROGRESS, + GENERIC_EXECUTE_MESSAGE_FILES_IN_USE, +}; + +typedef struct _APPLY_AUTHENTICATION_REQUIRED_DATA +{ + BURN_USER_EXPERIENCE* pUX; + LPCWSTR wzPackageOrContainerId; + LPCWSTR wzPayloadId; +} APPLY_AUTHENTICATION_REQUIRED_DATA; + +typedef struct _GENERIC_EXECUTE_MESSAGE +{ + GENERIC_EXECUTE_MESSAGE_TYPE type; + DWORD dwAllowedResults; + + union + { + struct + { + DWORD dwErrorCode; + LPCWSTR wzMessage; + } error; + struct + { + DWORD dwPercentage; + } progress; + struct + { + DWORD cFiles; + LPCWSTR* rgwzFiles; + } filesInUse; + }; +} GENERIC_EXECUTE_MESSAGE; + + +typedef int (*PFN_GENERICMESSAGEHANDLER)( + __in GENERIC_EXECUTE_MESSAGE* pMessage, + __in LPVOID pvContext + ); + + +void ApplyInitialize(); +void ApplyUninitialize(); +HRESULT ApplySetVariables( + __in BURN_VARIABLES* pVariables + ); +void ApplyReset( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PACKAGES* pPackages + ); +HRESULT ApplyLock( + __in BOOL fPerMachine, + __out HANDLE* phLock + ); +HRESULT ApplyRegister( + __in BURN_ENGINE_STATE* pEngineState + ); +HRESULT ApplyUnregister( + __in BURN_ENGINE_STATE* pEngineState, + __in BOOL fFailedOrRollback, + __in BOOL fSuspend, + __in BOOTSTRAPPER_APPLY_RESTART restart + ); +HRESULT ApplyCache( + __in HANDLE hSourceEngineFile, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_VARIABLES* pVariables, + __in BURN_PLAN* pPlan, + __in HANDLE hPipe, + __inout DWORD* pcOverallProgressTicks, + __inout BOOL* pfRollback + ); +HRESULT ApplyExecute( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HANDLE hCacheThread, + __inout DWORD* pcOverallProgressTicks, + __out BOOL* pfRollback, + __out BOOL* pfSuspend, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +void ApplyClean( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PLAN* pPlan, + __in HANDLE hPipe + ); + + +#ifdef __cplusplus +} +#endif diff --git a/src/burn/engine/approvedexe.cpp b/src/burn/engine/approvedexe.cpp new file mode 100644 index 00000000..55518519 --- /dev/null +++ b/src/burn/engine/approvedexe.cpp @@ -0,0 +1,262 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// function definitions + +extern "C" HRESULT ApprovedExesParseFromXml( + __in BURN_APPROVED_EXES* pApprovedExes, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + LPWSTR scz = NULL; + + // select approved exe nodes + hr = XmlSelectNodes(pixnBundle, L"ApprovedExeForElevation", &pixnNodes); + ExitOnFailure(hr, "Failed to select approved exe nodes."); + + // get approved exe node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get approved exe node count."); + + if (!cNodes) + { + ExitFunction(); + } + + // allocate memory for approved exes + pApprovedExes->rgApprovedExes = (BURN_APPROVED_EXE*)MemAlloc(sizeof(BURN_APPROVED_EXE) * cNodes, TRUE); + ExitOnNull(pApprovedExes->rgApprovedExes, hr, E_OUTOFMEMORY, "Failed to allocate memory for approved exe structs."); + + pApprovedExes->cApprovedExes = cNodes; + + // parse approved exe elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_APPROVED_EXE* pApprovedExe = &pApprovedExes->rgApprovedExes[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &pApprovedExe->sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + // @Key + hr = XmlGetAttributeEx(pixnNode, L"Key", &pApprovedExe->sczKey); + ExitOnFailure(hr, "Failed to get @Key."); + + // @ValueName + hr = XmlGetAttributeEx(pixnNode, L"ValueName", &pApprovedExe->sczValueName); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @ValueName."); + } + + // @Win64 + hr = XmlGetYesNoAttribute(pixnNode, L"Win64", &pApprovedExe->fWin64); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Win64."); + } + + // prepare next iteration + ReleaseNullObject(pixnNode); + ReleaseNullStr(scz); + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(scz); + return hr; +} + +extern "C" void ApprovedExesUninitialize( + __in BURN_APPROVED_EXES* pApprovedExes + ) +{ + if (pApprovedExes->rgApprovedExes) + { + for (DWORD i = 0; i < pApprovedExes->cApprovedExes; ++i) + { + BURN_APPROVED_EXE* pApprovedExe = &pApprovedExes->rgApprovedExes[i]; + + ReleaseStr(pApprovedExe->sczId); + ReleaseStr(pApprovedExe->sczKey); + ReleaseStr(pApprovedExe->sczValueName); + } + MemFree(pApprovedExes->rgApprovedExes); + } +} + +extern "C" void ApprovedExesUninitializeLaunch( + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe + ) +{ + if (pLaunchApprovedExe) + { + ReleaseStr(pLaunchApprovedExe->sczArguments); + ReleaseStr(pLaunchApprovedExe->sczExecutablePath); + ReleaseStr(pLaunchApprovedExe->sczId); + MemFree(pLaunchApprovedExe); + } +} + +extern "C" HRESULT ApprovedExesFindById( + __in BURN_APPROVED_EXES* pApprovedExes, + __in_z LPCWSTR wzId, + __out BURN_APPROVED_EXE** ppApprovedExe + ) +{ + HRESULT hr = S_OK; + BURN_APPROVED_EXE* pApprovedExe = NULL; + + for (DWORD i = 0; i < pApprovedExes->cApprovedExes; ++i) + { + pApprovedExe = &pApprovedExes->rgApprovedExes[i]; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pApprovedExe->sczId, -1, wzId, -1)) + { + *ppApprovedExe = pApprovedExe; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} + +extern "C" HRESULT ApprovedExesLaunch( + __in BURN_VARIABLES* pVariables, + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe, + __out DWORD* pdwProcessId + ) +{ + HRESULT hr = S_OK; + LPWSTR sczArgumentsFormatted = NULL; + LPWSTR sczArgumentsObfuscated = NULL; + LPWSTR sczCommand = NULL; + LPWSTR sczCommandObfuscated = NULL; + LPWSTR sczExecutableDirectory = NULL; + STARTUPINFOW si = { }; + PROCESS_INFORMATION pi = { }; + + // build command + if (pLaunchApprovedExe->sczArguments && *pLaunchApprovedExe->sczArguments) + { + hr = VariableFormatString(pVariables, pLaunchApprovedExe->sczArguments, &sczArgumentsFormatted, NULL); + ExitOnFailure(hr, "Failed to format argument string."); + + hr = StrAllocFormattedSecure(&sczCommand, L"\"%ls\" %s", pLaunchApprovedExe->sczExecutablePath, sczArgumentsFormatted); + ExitOnFailure(hr, "Failed to create executable command."); + + hr = VariableFormatStringObfuscated(pVariables, pLaunchApprovedExe->sczArguments, &sczArgumentsObfuscated, NULL); + ExitOnFailure(hr, "Failed to format obfuscated argument string."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"\"%ls\" %s", pLaunchApprovedExe->sczExecutablePath, sczArgumentsObfuscated); + } + else + { + hr = StrAllocFormatted(&sczCommand, L"\"%ls\"", pLaunchApprovedExe->sczExecutablePath); + ExitOnFailure(hr, "Failed to create executable command."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"\"%ls\"", pLaunchApprovedExe->sczExecutablePath); + } + ExitOnFailure(hr, "Failed to create obfuscated executable command."); + + // Try to get the directory of the executable so we can set the current directory of the process to help those executables + // that expect stuff to be relative to them. Best effort only. + hr = PathGetDirectory(pLaunchApprovedExe->sczExecutablePath, &sczExecutableDirectory); + if (FAILED(hr)) + { + ReleaseNullStr(sczExecutableDirectory); + } + + LogId(REPORT_STANDARD, MSG_LAUNCHING_APPROVED_EXE, pLaunchApprovedExe->sczExecutablePath, sczCommandObfuscated); + + si.cb = sizeof(si); + if (!::CreateProcessW(pLaunchApprovedExe->sczExecutablePath, sczCommand, NULL, NULL, FALSE, CREATE_NEW_PROCESS_GROUP, NULL, sczExecutableDirectory, &si, &pi)) + { + ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", pLaunchApprovedExe->sczExecutablePath); + } + + *pdwProcessId = pi.dwProcessId; + + if (pLaunchApprovedExe->dwWaitForInputIdleTimeout) + { + ::WaitForInputIdle(pi.hProcess, pLaunchApprovedExe->dwWaitForInputIdleTimeout); + } + +LExit: + StrSecureZeroFreeString(sczArgumentsFormatted); + ReleaseStr(sczArgumentsObfuscated); + StrSecureZeroFreeString(sczCommand); + ReleaseStr(sczCommandObfuscated); + ReleaseStr(sczExecutableDirectory); + + ReleaseHandle(pi.hThread); + ReleaseHandle(pi.hProcess); + + return hr; +} + +extern "C" HRESULT ApprovedExesVerifySecureLocation( + __in BURN_VARIABLES* pVariables, + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe + ) +{ + HRESULT hr = S_OK; + LPWSTR scz = NULL; + + const LPCWSTR vrgSecureFolderVariables[] = { + L"ProgramFiles64Folder", + L"ProgramFilesFolder", + }; + + for (DWORD i = 0; i < countof(vrgSecureFolderVariables); ++i) + { + LPCWSTR wzSecureFolderVariable = vrgSecureFolderVariables[i]; + + hr = VariableGetString(pVariables, wzSecureFolderVariable, &scz); + if (SUCCEEDED(hr)) + { + hr = PathDirectoryContainsPath(scz, pLaunchApprovedExe->sczExecutablePath); + if (S_OK == hr) + { + ExitFunction(); + } + } + else if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get the variable: %ls", wzSecureFolderVariable); + } + } + + // The problem with using a Variable for the root package cache folder is that it might not have been secured yet. + // Getting it through CacheGetRootCompletedPath makes sure it has been secured. + hr = CacheGetRootCompletedPath(TRUE, TRUE, &scz); + ExitOnFailure(hr, "Failed to get the root package cache folder."); + + hr = PathDirectoryContainsPath(scz, pLaunchApprovedExe->sczExecutablePath); + if (S_OK == hr) + { + ExitFunction(); + } + + hr = S_FALSE; + +LExit: + ReleaseStr(scz); + + return hr; +} diff --git a/src/burn/engine/approvedexe.h b/src/burn/engine/approvedexe.h new file mode 100644 index 00000000..23f3b1bb --- /dev/null +++ b/src/burn/engine/approvedexe.h @@ -0,0 +1,67 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// structs + +typedef struct _BURN_APPROVED_EXE +{ + LPWSTR sczId; + LPWSTR sczKey; + LPWSTR sczValueName; + BOOL fWin64; +} BURN_APPROVED_EXE; + +typedef struct _BURN_APPROVED_EXES +{ + BURN_APPROVED_EXE* rgApprovedExes; + DWORD cApprovedExes; +} BURN_APPROVED_EXES; + +typedef struct _BURN_LAUNCH_APPROVED_EXE +{ + HWND hwndParent; + LPWSTR sczId; + LPWSTR sczExecutablePath; + LPWSTR sczArguments; + DWORD dwWaitForInputIdleTimeout; +} BURN_LAUNCH_APPROVED_EXE; + + +// function declarations + +HRESULT ApprovedExesParseFromXml( + __in BURN_APPROVED_EXES* pApprovedExes, + __in IXMLDOMNode* pixnBundle + ); + +void ApprovedExesUninitialize( + __in BURN_APPROVED_EXES* pApprovedExes + ); +void ApprovedExesUninitializeLaunch( + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe + ); +HRESULT ApprovedExesFindById( + __in BURN_APPROVED_EXES* pApprovedExes, + __in_z LPCWSTR wzId, + __out BURN_APPROVED_EXE** ppApprovedExe + ); +HRESULT ApprovedExesLaunch( + __in BURN_VARIABLES* pVariables, + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe, + __out DWORD* pdwProcessId + ); +HRESULT ApprovedExesVerifySecureLocation( + __in BURN_VARIABLES* pVariables, + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/burnextension.cpp b/src/burn/engine/burnextension.cpp new file mode 100644 index 00000000..475df1c5 --- /dev/null +++ b/src/burn/engine/burnextension.cpp @@ -0,0 +1,264 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +static HRESULT SendRequiredBextMessage( + __in BURN_EXTENSION* pExtension, + __in BUNDLE_EXTENSION_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ); + +// function definitions + +/******************************************************************* + BurnExtensionParseFromXml - + +*******************************************************************/ +EXTERN_C HRESULT BurnExtensionParseFromXml( + __in BURN_EXTENSIONS* pBurnExtensions, + __in BURN_PAYLOADS* pBaPayloads, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + + // Select BundleExtension nodes. + hr = XmlSelectNodes(pixnBundle, L"BundleExtension", &pixnNodes); + ExitOnFailure(hr, "Failed to select BundleExtension nodes."); + + // Get BundleExtension node count. + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get BundleExtension node count."); + + if (!cNodes) + { + ExitFunction(); + } + + // Allocate memory for BundleExtensions. + pBurnExtensions->rgExtensions = (BURN_EXTENSION*)MemAlloc(sizeof(BURN_EXTENSION) * cNodes, TRUE); + ExitOnNull(pBurnExtensions->rgExtensions, hr, E_OUTOFMEMORY, "Failed to allocate memory for BundleExtension structs."); + + pBurnExtensions->cExtensions = cNodes; + + // parse search elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_EXTENSION* pExtension = &pBurnExtensions->rgExtensions[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &pExtension->sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + // @EntryPayloadId + hr = XmlGetAttributeEx(pixnNode, L"EntryPayloadId", &pExtension->sczEntryPayloadId); + ExitOnFailure(hr, "Failed to get @EntryPayloadId."); + + hr = PayloadFindById(pBaPayloads, pExtension->sczEntryPayloadId, &pExtension->pEntryPayload); + ExitOnFailure(hr, "Failed to find BundleExtension EntryPayload '%ls'.", pExtension->sczEntryPayloadId); + + // prepare next iteration + ReleaseNullObject(pixnNode); + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNode); + ReleaseObject(pixnNodes); + + return hr; +} + +/******************************************************************* + BurnExtensionUninitialize - + +*******************************************************************/ +EXTERN_C void BurnExtensionUninitialize( + __in BURN_EXTENSIONS* pBurnExtensions + ) +{ + if (pBurnExtensions->rgExtensions) + { + for (DWORD i = 0; i < pBurnExtensions->cExtensions; ++i) + { + BURN_EXTENSION* pExtension = &pBurnExtensions->rgExtensions[i]; + + ReleaseStr(pExtension->sczEntryPayloadId); + ReleaseStr(pExtension->sczId); + } + MemFree(pBurnExtensions->rgExtensions); + } + + // clear struct + memset(pBurnExtensions, 0, sizeof(BURN_EXTENSIONS)); +} + +/******************************************************************* + BurnExtensionLoad - + +*******************************************************************/ +EXTERN_C HRESULT BurnExtensionLoad( + __in BURN_EXTENSIONS * pBurnExtensions, + __in BURN_EXTENSION_ENGINE_CONTEXT* pEngineContext + ) +{ + HRESULT hr = S_OK; + LPWSTR sczBundleExtensionDataPath = NULL; + BUNDLE_EXTENSION_CREATE_ARGS args = { }; + BUNDLE_EXTENSION_CREATE_RESULTS results = { }; + + if (!pBurnExtensions->rgExtensions || !pBurnExtensions->cExtensions) + { + ExitFunction(); + } + + hr = PathConcat(pEngineContext->pEngineState->userExperience.sczTempDirectory, L"BundleExtensionData.xml", &sczBundleExtensionDataPath); + ExitOnFailure(hr, "Failed to get BundleExtensionDataPath."); + + for (DWORD i = 0; i < pBurnExtensions->cExtensions; ++i) + { + BURN_EXTENSION* pExtension = &pBurnExtensions->rgExtensions[i]; + + memset(&args, 0, sizeof(BUNDLE_EXTENSION_CREATE_ARGS)); + memset(&results, 0, sizeof(BUNDLE_EXTENSION_CREATE_RESULTS)); + + args.cbSize = sizeof(BUNDLE_EXTENSION_CREATE_ARGS); + args.pfnBundleExtensionEngineProc = EngineForExtensionProc; + args.pvBundleExtensionEngineProcContext = pEngineContext; + args.qwEngineAPIVersion = MAKEQWORDVERSION(2021, 4, 27, 0); + args.wzBootstrapperWorkingFolder = pEngineContext->pEngineState->userExperience.sczTempDirectory; + args.wzBundleExtensionDataPath = sczBundleExtensionDataPath; + args.wzExtensionId = pExtension->sczId; + + results.cbSize = sizeof(BUNDLE_EXTENSION_CREATE_RESULTS); + + // Load BundleExtension DLL. + pExtension->hBextModule = ::LoadLibraryExW(pExtension->pEntryPayload->sczLocalFilePath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + ExitOnNullWithLastError(pExtension->hBextModule, hr, "Failed to load BundleExtension DLL '%ls': '%ls'.", pExtension->sczId, pExtension->pEntryPayload->sczLocalFilePath); + + // Get BundleExtensionCreate entry-point. + PFN_BUNDLE_EXTENSION_CREATE pfnCreate = (PFN_BUNDLE_EXTENSION_CREATE)::GetProcAddress(pExtension->hBextModule, "BundleExtensionCreate"); + ExitOnNullWithLastError(pfnCreate, hr, "Failed to get BundleExtensionCreate entry-point '%ls'.", pExtension->sczId); + + // Create BundleExtension. + hr = pfnCreate(&args, &results); + ExitOnFailure(hr, "Failed to create BundleExtension '%ls'.", pExtension->sczId); + + pExtension->pfnBurnExtensionProc = results.pfnBundleExtensionProc; + pExtension->pvBurnExtensionProcContext = results.pvBundleExtensionProcContext; + } + +LExit: + ReleaseStr(sczBundleExtensionDataPath); + + return hr; +} + +/******************************************************************* + BurnExtensionUnload - + +*******************************************************************/ +EXTERN_C void BurnExtensionUnload( + __in BURN_EXTENSIONS * pBurnExtensions + ) +{ + HRESULT hr = S_OK; + + if (pBurnExtensions->rgExtensions) + { + for (DWORD i = 0; i < pBurnExtensions->cExtensions; ++i) + { + BURN_EXTENSION* pExtension = &pBurnExtensions->rgExtensions[i]; + + if (pExtension->hBextModule) + { + // Get BundleExtensionDestroy entry-point and call it if it exists. + PFN_BUNDLE_EXTENSION_DESTROY pfnDestroy = (PFN_BUNDLE_EXTENSION_DESTROY)::GetProcAddress(pExtension->hBextModule, "BundleExtensionDestroy"); + if (pfnDestroy) + { + pfnDestroy(); + } + + // Free BundleExtension DLL. + if (!::FreeLibrary(pExtension->hBextModule)) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + TraceError(hr, "Failed to unload BundleExtension DLL."); + } + pExtension->hBextModule = NULL; + } + } + } +} + +EXTERN_C HRESULT BurnExtensionFindById( + __in BURN_EXTENSIONS* pBurnExtensions, + __in_z LPCWSTR wzId, + __out BURN_EXTENSION** ppExtension + ) +{ + HRESULT hr = S_OK; + BURN_EXTENSION* pExtension = NULL; + + for (DWORD i = 0; i < pBurnExtensions->cExtensions; ++i) + { + pExtension = &pBurnExtensions->rgExtensions[i]; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pExtension->sczId, -1, wzId, -1)) + { + *ppExtension = pExtension; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} + +EXTERN_C BEEAPI BurnExtensionPerformSearch( + __in BURN_EXTENSION* pExtension, + __in LPWSTR wzSearchId, + __in LPWSTR wzVariable + ) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_SEARCH_ARGS args = { }; + BUNDLE_EXTENSION_SEARCH_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzId = wzSearchId; + args.wzVariable = wzVariable; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_SEARCH, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' Search '%ls' failed.", pExtension->sczId, wzSearchId); + +LExit: + return hr; +} + +static HRESULT SendRequiredBextMessage( + __in BURN_EXTENSION* pExtension, + __in BUNDLE_EXTENSION_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + + hr = pExtension->pfnBurnExtensionProc(message, pvArgs, pvResults, pExtension->pvBurnExtensionProcContext); + + return hr; +} diff --git a/src/burn/engine/burnextension.h b/src/burn/engine/burnextension.h new file mode 100644 index 00000000..370ddd2d --- /dev/null +++ b/src/burn/engine/burnextension.h @@ -0,0 +1,61 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#define BEEAPI HRESULT __stdcall + +#if defined(__cplusplus) +extern "C" { +#endif + +// structs + +typedef struct _BURN_EXTENSION_ENGINE_CONTEXT BURN_EXTENSION_ENGINE_CONTEXT; + +typedef struct _BURN_EXTENSION +{ + LPWSTR sczEntryPayloadId; + LPWSTR sczId; + + BURN_PAYLOAD* pEntryPayload; + + HMODULE hBextModule; + PFN_BUNDLE_EXTENSION_PROC pfnBurnExtensionProc; + LPVOID pvBurnExtensionProcContext; +} BURN_EXTENSION; + +typedef struct _BURN_EXTENSIONS +{ + BURN_EXTENSION* rgExtensions; + DWORD cExtensions; +} BURN_EXTENSIONS; + +// functions + +HRESULT BurnExtensionParseFromXml( + __in BURN_EXTENSIONS* pBurnExtensions, + __in BURN_PAYLOADS* pBaPayloads, + __in IXMLDOMNode* pixnBundle + ); +void BurnExtensionUninitialize( + __in BURN_EXTENSIONS* pBurnExtensions + ); +HRESULT BurnExtensionLoad( + __in BURN_EXTENSIONS* pBurnExtensions, + __in BURN_EXTENSION_ENGINE_CONTEXT* pEngineContext + ); +void BurnExtensionUnload( + __in BURN_EXTENSIONS* pBurnExtensions + ); +HRESULT BurnExtensionFindById( + __in BURN_EXTENSIONS* pBurnExtensions, + __in_z LPCWSTR wzId, + __out BURN_EXTENSION** ppExtension + ); +BEEAPI BurnExtensionPerformSearch( + __in BURN_EXTENSION* pExtension, + __in LPWSTR wzSearchId, + __in LPWSTR wzVariable + ); +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/cabextract.cpp b/src/burn/engine/cabextract.cpp new file mode 100644 index 00000000..5a02ff8a --- /dev/null +++ b/src/burn/engine/cabextract.cpp @@ -0,0 +1,974 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +#include + +#define ARRAY_GROWTH_SIZE 2 + +const LPSTR INVALID_CAB_NAME = ".cab"; + +// structs + +typedef struct _BURN_CAB_CONTEXT +{ + HANDLE hFile; + DWORD64 qwOffset; + DWORD64 qwSize; + + HANDLE hThread; + HANDLE hBeginOperationEvent; + HANDLE hOperationCompleteEvent; + + BURN_CAB_OPERATION operation; + HRESULT hrError; + + LPWSTR* psczStreamName; + LPCWSTR wzTargetFile; + HANDLE hTargetFile; + BYTE* pbTargetBuffer; + DWORD cbTargetBuffer; + DWORD iTargetBuffer; +} BURN_CAB_CONTEXT; + + +// internal function declarations + +static HRESULT BeginAndWaitForOperation( + __in BURN_CONTAINER_CONTEXT* pContext + ); +static HRESULT WaitForOperation( + __in BURN_CONTAINER_CONTEXT* pContext + ); +static DWORD WINAPI ExtractThreadProc( + __in LPVOID lpThreadParameter + ); +static INT_PTR DIAMONDAPI CabNotifyCallback( + __in FDINOTIFICATIONTYPE iNotification, + __inout FDINOTIFICATION *pFDINotify + ); +static INT_PTR CopyFileCallback( + __in BURN_CONTAINER_CONTEXT* pContext, + __inout FDINOTIFICATION *pFDINotify + ); +static INT_PTR CloseFileInfoCallback( + __in BURN_CONTAINER_CONTEXT* pContext, + __inout FDINOTIFICATION *pFDINotify + ); +static LPVOID DIAMONDAPI CabAlloc( + __in DWORD dwSize + ); +static void DIAMONDAPI CabFree( + __in LPVOID pvData + ); +static INT_PTR FAR DIAMONDAPI CabOpen( + __in char FAR *pszFile, + __in int /* oflag */, + __in int /* pmode */ + ); +static UINT FAR DIAMONDAPI CabRead( + __in INT_PTR hf, + __out void FAR *pv, + __in UINT cb + ); +static UINT FAR DIAMONDAPI CabWrite( + __in INT_PTR hf, + __in void FAR *pv, + __in UINT cb + ); +static long FAR DIAMONDAPI CabSeek( + __in INT_PTR hf, + __in long dist, + __in int seektype + ); +static int FAR DIAMONDAPI CabClose( + __in INT_PTR hf + ); +static HRESULT AddVirtualFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile, + __in LONGLONG llInitialFilePointer + ); +static HRESULT ReadIfVirtualFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile, + __in DWORD cbRead + ); +static BOOL SetIfVirtualFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile, + __in LONGLONG llDistance, + __out LONGLONG* pllNewPostion, + __in DWORD dwSeekType + ); +static HRESULT CloseIfVirturalFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile + ); +static BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER* GetVirtualFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile + ); + + +// internal variables + +__declspec(thread) static BURN_CONTAINER_CONTEXT* vpContext; + + +// function definitions + +extern "C" void CabExtractInitialize() +{ +} + +extern "C" HRESULT CabExtractOpen( + __in BURN_CONTAINER_CONTEXT* pContext, + __in LPCWSTR wzFilePath + ) +{ + HRESULT hr = S_OK; + + // initialize context + pContext->Cabinet.hTargetFile = INVALID_HANDLE_VALUE; + + hr = StrAllocString(&pContext->Cabinet.sczFile, wzFilePath, 0); + ExitOnFailure(hr, "Failed to copy file name."); + + // create events + pContext->Cabinet.hBeginOperationEvent = ::CreateEventW(NULL, TRUE, FALSE, NULL); + ExitOnNullWithLastError(pContext->Cabinet.hBeginOperationEvent, hr, "Failed to create begin operation event."); + + pContext->Cabinet.hOperationCompleteEvent = ::CreateEventW(NULL, TRUE, FALSE, NULL); + ExitOnNullWithLastError(pContext->Cabinet.hOperationCompleteEvent, hr, "Failed to create operation complete event."); + + // create extraction thread + pContext->Cabinet.hThread = ::CreateThread(NULL, 0, ExtractThreadProc, pContext, 0, NULL); + ExitOnNullWithLastError(pContext->Cabinet.hThread, hr, "Failed to create extraction thread."); + + // wait for operation to complete + hr = WaitForOperation(pContext); + ExitOnFailure(hr, "Failed to wait for operation complete."); + +LExit: + return hr; +} + +extern "C" HRESULT CabExtractNextStream( + __in BURN_CONTAINER_CONTEXT* pContext, + __inout_z LPWSTR* psczStreamName + ) +{ + HRESULT hr = S_OK; + + // set operation to move to next stream + pContext->Cabinet.operation = BURN_CAB_OPERATION_NEXT_STREAM; + pContext->Cabinet.psczStreamName = psczStreamName; + + // begin operation and wait + hr = BeginAndWaitForOperation(pContext); + if (E_ABORT != hr && E_NOMOREITEMS != hr) + { + ExitOnFailure(hr, "Failed to begin and wait for operation."); + } + +LExit: + return hr; +} + +extern "C" HRESULT CabExtractStreamToFile( + __in BURN_CONTAINER_CONTEXT* pContext, + __in_z LPCWSTR wzFileName + ) +{ + HRESULT hr = S_OK; + + // set operation to move to next stream + pContext->Cabinet.operation = BURN_CAB_OPERATION_STREAM_TO_FILE; + pContext->Cabinet.wzTargetFile = wzFileName; + + // begin operation and wait + hr = BeginAndWaitForOperation(pContext); + ExitOnFailure(hr, "Failed to begin and wait for operation."); + + // clear file name + pContext->Cabinet.wzTargetFile = NULL; + +LExit: + return hr; +} + +extern "C" HRESULT CabExtractStreamToBuffer( + __in BURN_CONTAINER_CONTEXT* pContext, + __out BYTE** ppbBuffer, + __out SIZE_T* pcbBuffer + ) +{ + HRESULT hr = S_OK; + + // set operation to move to next stream + pContext->Cabinet.operation = BURN_CAB_OPERATION_STREAM_TO_BUFFER; + + // begin operation and wait + hr = BeginAndWaitForOperation(pContext); + ExitOnFailure(hr, "Failed to begin and wait for operation."); + + // return values + *ppbBuffer = pContext->Cabinet.pbTargetBuffer; + *pcbBuffer = pContext->Cabinet.cbTargetBuffer; + + // clear buffer variables + pContext->Cabinet.pbTargetBuffer = NULL; + pContext->Cabinet.cbTargetBuffer = 0; + pContext->Cabinet.iTargetBuffer = 0; + +LExit: + return hr; +} + +extern "C" HRESULT CabExtractSkipStream( + __in BURN_CONTAINER_CONTEXT* pContext + ) +{ + HRESULT hr = S_OK; + + // set operation to move to next stream + pContext->Cabinet.operation = BURN_CAB_OPERATION_SKIP_STREAM; + + // begin operation and wait + hr = BeginAndWaitForOperation(pContext); + ExitOnFailure(hr, "Failed to begin and wait for operation."); + +LExit: + return hr; +} + +extern "C" HRESULT CabExtractClose( + __in BURN_CONTAINER_CONTEXT* pContext + ) +{ + HRESULT hr = S_OK; + + // terminate worker thread + if (pContext->Cabinet.hThread) + { + // set operation to move to close + pContext->Cabinet.operation = BURN_CAB_OPERATION_CLOSE; + + // set begin operation event + if (!::SetEvent(pContext->Cabinet.hBeginOperationEvent)) + { + ExitWithLastError(hr, "Failed to set begin operation event."); + } + + // wait for thread to terminate + if (WAIT_OBJECT_0 != ::WaitForSingleObject(pContext->Cabinet.hThread, INFINITE)) + { + ExitWithLastError(hr, "Failed to wait for thread to terminate."); + } + } + +LExit: + ReleaseHandle(pContext->Cabinet.hThread); + ReleaseHandle(pContext->Cabinet.hBeginOperationEvent); + ReleaseHandle(pContext->Cabinet.hOperationCompleteEvent); + ReleaseMem(pContext->Cabinet.rgVirtualFilePointers); + ReleaseStr(pContext->Cabinet.sczFile); + + return hr; +} + + +// internal helper functions + +static HRESULT BeginAndWaitForOperation( + __in BURN_CONTAINER_CONTEXT* pContext + ) +{ + HRESULT hr = S_OK; + + // set begin operation event + if (!::SetEvent(pContext->Cabinet.hBeginOperationEvent)) + { + ExitWithLastError(hr, "Failed to set begin operation event."); + } + + // wait for operation to complete + hr = WaitForOperation(pContext); + +LExit: + return hr; +} + +static HRESULT WaitForOperation( + __in BURN_CONTAINER_CONTEXT* pContext + ) +{ + HRESULT hr = S_OK; + HANDLE rghWait[2] = { }; + + // wait for operation complete event + rghWait[0] = pContext->Cabinet.hOperationCompleteEvent; + rghWait[1] = pContext->Cabinet.hThread; + switch (::WaitForMultipleObjects(countof(rghWait), rghWait, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + if (!::ResetEvent(pContext->Cabinet.hOperationCompleteEvent)) + { + ExitWithLastError(hr, "Failed to reset operation complete event."); + } + break; + + case WAIT_OBJECT_0 + 1: + if (!::GetExitCodeThread(pContext->Cabinet.hThread, (DWORD*)&hr)) + { + ExitWithLastError(hr, "Failed to get extraction thread exit code."); + } + ExitFunction(); + + case WAIT_FAILED: __fallthrough; + default: + ExitWithLastError(hr, "Failed to wait for operation complete event."); + } + + // clear operation + pContext->Cabinet.operation = BURN_CAB_OPERATION_NONE; + +LExit: + return hr; +} + +static DWORD WINAPI ExtractThreadProc( + __in LPVOID lpThreadParameter + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER_CONTEXT* pContext = (BURN_CONTAINER_CONTEXT*)lpThreadParameter; + BOOL fComInitialized = FALSE; + HFDI hfdi = NULL; + ERF erf = { }; + + // initialize COM + hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); + ExitOnFailure(hr, "Failed to initialize COM."); + fComInitialized = TRUE; + + // save context in TLS storage + vpContext = pContext; + + // create FDI context + hfdi = ::FDICreate(CabAlloc, CabFree, CabOpen, CabRead, CabWrite, CabClose, CabSeek, cpuUNKNOWN, &erf); + ExitOnNull(hfdi, hr, E_FAIL, "Failed to initialize cabinet.dll."); + + // begin CAB extraction + if (!::FDICopy(hfdi, INVALID_CAB_NAME, "", 0, CabNotifyCallback, NULL, NULL)) + { + hr = pContext->Cabinet.hrError; + if (E_ABORT == hr || E_NOMOREITEMS == hr) + { + ExitFunction(); + } + else if (SUCCEEDED(hr)) + { + if (ERROR_SUCCESS != erf.erfType) + { + hr = HRESULT_FROM_WIN32(erf.erfType); + } + else + { + switch (erf.erfOper) + { + case FDIERROR_NONE: + hr = E_UNEXPECTED; + break; + case FDIERROR_CABINET_NOT_FOUND: + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + break; + case FDIERROR_NOT_A_CABINET: + hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + break; + case FDIERROR_UNKNOWN_CABINET_VERSION: + hr = HRESULT_FROM_WIN32(ERROR_VERSION_PARSE_ERROR); + break; + case FDIERROR_CORRUPT_CABINET: + hr = HRESULT_FROM_WIN32(ERROR_FILE_CORRUPT); + break; + case FDIERROR_ALLOC_FAIL: + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + break; + case FDIERROR_BAD_COMPR_TYPE: + hr = HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_COMPRESSION); + break; + case FDIERROR_MDI_FAIL: + hr = HRESULT_FROM_WIN32(ERROR_BAD_COMPRESSION_BUFFER); + break; + case FDIERROR_TARGET_FILE: + hr = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT); + break; + case FDIERROR_RESERVE_MISMATCH: + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + break; + case FDIERROR_WRONG_CABINET: + hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH); + break; + case FDIERROR_USER_ABORT: + hr = E_ABORT; + break; + default: + hr = E_FAIL; + break; + } + } + } + ExitOnFailure(hr, "Failed to extract all files from container, erf: %d:%X:%d", erf.fError, erf.erfOper, erf.erfType); + } + + // set operation complete event + if (!::SetEvent(pContext->Cabinet.hOperationCompleteEvent)) + { + ExitWithLastError(hr, "Failed to set operation complete event."); + } + + // wait for begin operation event + if (WAIT_FAILED == ::WaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE)) + { + ExitWithLastError(hr, "Failed to wait for begin operation event."); + } + + if (!::ResetEvent(pContext->Cabinet.hBeginOperationEvent)) + { + ExitWithLastError(hr, "Failed to reset begin operation event."); + } + + // read operation + switch (pContext->Cabinet.operation) + { + case BURN_CAB_OPERATION_NEXT_STREAM: + ExitFunction1(hr = E_NOMOREITEMS); + break; + + case BURN_CAB_OPERATION_CLOSE: + ExitFunction1(hr = S_OK); + + default: + hr = E_INVALIDSTATE; + ExitOnRootFailure(hr, "Invalid operation for this state."); + } + +LExit: + if (hfdi) + { + ::FDIDestroy(hfdi); + } + if (fComInitialized) + { + ::CoUninitialize(); + } + + return (DWORD)hr; +} + +static INT_PTR DIAMONDAPI CabNotifyCallback( + __in FDINOTIFICATIONTYPE iNotification, + __inout FDINOTIFICATION *pFDINotify + ) +{ + BURN_CONTAINER_CONTEXT* pContext = vpContext; + INT_PTR ipResult = 0; // result to return on success + + switch (iNotification) + { + case fdintCOPY_FILE: + ipResult = CopyFileCallback(pContext, pFDINotify); + break; + + case fdintCLOSE_FILE_INFO: // resource extraction complete + ipResult = CloseFileInfoCallback(pContext, pFDINotify); + break; + + case fdintPARTIAL_FILE: __fallthrough; // no action needed for these messages + case fdintNEXT_CABINET: __fallthrough; + case fdintENUMERATE: __fallthrough; + case fdintCABINET_INFO: + break; + + default: + AssertSz(FALSE, "CabExtractCallback() - unknown FDI notification command"); + }; + +//LExit: + return ipResult; +} + +static INT_PTR CopyFileCallback( + __in BURN_CONTAINER_CONTEXT* pContext, + __inout FDINOTIFICATION* pFDINotify + ) +{ + HRESULT hr = S_OK; + INT_PTR ipResult = 1; // result to return on success + LPWSTR pwzPath = NULL; + LARGE_INTEGER li = { }; + + // set operation complete event + if (!::SetEvent(pContext->Cabinet.hOperationCompleteEvent)) + { + ExitWithLastError(hr, "Failed to set operation complete event."); + } + + // wait for begin operation event + if (WAIT_FAILED == ::WaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE)) + { + ExitWithLastError(hr, "Failed to wait for begin operation event."); + } + + if (!::ResetEvent(pContext->Cabinet.hBeginOperationEvent)) + { + ExitWithLastError(hr, "Failed to reset begin operation event."); + } + + // read operation + switch (pContext->Cabinet.operation) + { + case BURN_CAB_OPERATION_NEXT_STREAM: + break; + + case BURN_CAB_OPERATION_CLOSE: + ExitFunction1(hr = E_ABORT); + + default: + hr = E_INVALIDSTATE; + ExitOnRootFailure(hr, "Invalid operation for this state."); + } + + // copy stream name + hr = StrAllocStringAnsi(pContext->Cabinet.psczStreamName, pFDINotify->psz1, 0, CP_UTF8); + ExitOnFailure(hr, "Failed to copy stream name: %hs", pFDINotify->psz1); + + // set operation complete event + if (!::SetEvent(pContext->Cabinet.hOperationCompleteEvent)) + { + ExitWithLastError(hr, "Failed to set operation complete event."); + } + + // wait for begin operation event + if (WAIT_FAILED == ::WaitForSingleObject(pContext->Cabinet.hBeginOperationEvent, INFINITE)) + { + ExitWithLastError(hr, "Failed to wait for begin operation event."); + } + + if (!::ResetEvent(pContext->Cabinet.hBeginOperationEvent)) + { + ExitWithLastError(hr, "Failed to reset begin operation event."); + } + + // read operation + switch (pContext->Cabinet.operation) + { + case BURN_CAB_OPERATION_STREAM_TO_FILE: + // create file + pContext->Cabinet.hTargetFile = ::CreateFileW(pContext->Cabinet.wzTargetFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE == pContext->Cabinet.hTargetFile) + { + ExitWithLastError(hr, "Failed to create file: %ls", pContext->Cabinet.wzTargetFile); + } + + // set file size + li.QuadPart = pFDINotify->cb; + if (!::SetFilePointerEx(pContext->Cabinet.hTargetFile, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to set file pointer to end of file."); + } + + if (!::SetEndOfFile(pContext->Cabinet.hTargetFile)) + { + ExitWithLastError(hr, "Failed to set end of file."); + } + + li.QuadPart = 0; + if (!::SetFilePointerEx(pContext->Cabinet.hTargetFile, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to set file pointer to beginning of file."); + } + + break; + + case BURN_CAB_OPERATION_STREAM_TO_BUFFER: + // allocate buffer for stream + pContext->Cabinet.pbTargetBuffer = (BYTE*)MemAlloc(pFDINotify->cb, TRUE); + ExitOnNull(pContext->Cabinet.pbTargetBuffer, hr, E_OUTOFMEMORY, "Failed to allocate buffer for stream."); + + // set buffer size and write position + pContext->Cabinet.cbTargetBuffer = pFDINotify->cb; + pContext->Cabinet.iTargetBuffer = 0; + + break; + + case BURN_CAB_OPERATION_SKIP_STREAM: + ipResult = 0; + break; + + case BURN_CAB_OPERATION_CLOSE: + ExitFunction1(hr = E_ABORT); + + default: + hr = E_INVALIDSTATE; + ExitOnRootFailure(hr, "Invalid operation for this state."); + } + +LExit: + ReleaseStr(pwzPath); + + pContext->Cabinet.hrError = hr; + return SUCCEEDED(hr) ? ipResult : -1; +} + +static INT_PTR CloseFileInfoCallback( + __in BURN_CONTAINER_CONTEXT* pContext, + __inout FDINOTIFICATION *pFDINotify + ) +{ + HRESULT hr = S_OK; + INT_PTR ipResult = 1; // result to return on success + FILETIME ftLocal = { }; + FILETIME ft = { }; + + // read operation + switch (pContext->Cabinet.operation) + { + case BURN_CAB_OPERATION_STREAM_TO_FILE: + // Make a best effort to set the time on the new file before + // we close it. + if (::DosDateTimeToFileTime(pFDINotify->date, pFDINotify->time, &ftLocal)) + { + if (::LocalFileTimeToFileTime(&ftLocal, &ft)) + { + ::SetFileTime(pContext->Cabinet.hTargetFile, &ft, &ft, &ft); + } + } + + // close file + ReleaseFile(pContext->Cabinet.hTargetFile); + break; + + case BURN_CAB_OPERATION_STREAM_TO_BUFFER: + break; + + case BURN_CAB_OPERATION_CLOSE: + ExitFunction1(hr = E_ABORT); + + default: + hr = E_INVALIDSTATE; + ExitOnRootFailure(hr, "Invalid operation for this state."); + } + + //if (pContext->pfnProgress) + //{ + // hr = StrAllocFormatted(&pwzPath, L"%s%ls", pContext->wzRootPath, pFDINotify->psz1); + // ExitOnFailure(hr, "Failed to calculate file path from: %ls and %s", pContext->wzRootPath, pFDINotify->psz1); + // if (SUCCEEDED(hr)) + // { + // hr = pContext->pfnProgress(BOX_PROGRESS_DECOMPRESSION_END, pwzPath, 0, pContext->pvContext); + // if (S_OK != hr) + // { + // pContext->hrError = hr; + // ExitFunction(); + // } + // } + //} + +LExit: + pContext->Cabinet.hrError = hr; + return SUCCEEDED(hr) ? ipResult : -1; +} + +static LPVOID DIAMONDAPI CabAlloc( + __in DWORD dwSize + ) +{ + return MemAlloc(dwSize, FALSE); +} + +static void DIAMONDAPI CabFree( + __in LPVOID pvData + ) +{ + MemFree(pvData); +} + +static INT_PTR FAR DIAMONDAPI CabOpen( + __in char FAR * pszFile, + __in int /* oflag */, + __in int /* pmode */ + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER_CONTEXT* pContext = vpContext; + HANDLE hFile = INVALID_HANDLE_VALUE; + + // If this is the invalid cab name, use our file handle. + if (CSTR_EQUAL == ::CompareStringA(LOCALE_NEUTRAL, 0, INVALID_CAB_NAME, -1, pszFile, -1)) + { + if (!::DuplicateHandle(::GetCurrentProcess(), pContext->hFile, ::GetCurrentProcess(), &hFile, 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + ExitWithLastError(hr, "Failed to duplicate handle to cab container."); + } + + // Use a virtual file pointer since duplicated file handles share their file pointer. Seek to container offset + // to start. + hr = AddVirtualFilePointer(&pContext->Cabinet, hFile, pContext->qwOffset); + ExitOnFailure(hr, "Failed to add virtual file pointer for cab container."); + } + else // open file requested. This is used in the rare cases where the CAB API wants to create a temp file. + { + hFile = ::CreateFileA(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + ExitOnInvalidHandleWithLastError(hFile, hr, "Failed to open cabinet file: %hs", pszFile); + } + +LExit: + pContext->Cabinet.hrError = hr; + return FAILED(hr) ? -1 : (INT_PTR)hFile; +} + +static UINT FAR DIAMONDAPI CabRead( + __in INT_PTR hf, + __out void FAR *pv, + __in UINT cb + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER_CONTEXT* pContext = vpContext; + HANDLE hFile = (HANDLE)hf; + DWORD cbRead = 0; + + ReadIfVirtualFilePointer(&pContext->Cabinet, hFile, cb); + + if (!::ReadFile(hFile, pv, cb, &cbRead, NULL)) + { + ExitWithLastError(hr, "Failed to read during cabinet extraction."); + } + +LExit: + pContext->Cabinet.hrError = hr; + return FAILED(hr) ? -1 : cbRead; +} + +static UINT FAR DIAMONDAPI CabWrite( + __in INT_PTR /* hf */, + __in void FAR *pv, + __in UINT cb + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER_CONTEXT* pContext = vpContext; + DWORD cbWrite = 0; + + switch (pContext->Cabinet.operation) + { + case BURN_CAB_OPERATION_STREAM_TO_FILE: + // write file + if (!::WriteFile(pContext->Cabinet.hTargetFile, pv, cb, &cbWrite, NULL)) + { + ExitWithLastError(hr, "Failed to write during cabinet extraction."); + } + break; + + case BURN_CAB_OPERATION_STREAM_TO_BUFFER: + // copy to target buffer + memcpy_s(pContext->Cabinet.pbTargetBuffer + pContext->Cabinet.iTargetBuffer, pContext->Cabinet.cbTargetBuffer - pContext->Cabinet.iTargetBuffer, pv, cb); + pContext->Cabinet.iTargetBuffer += cb; + + cbWrite = cb; + break; + + default: + hr = E_INVALIDSTATE; + ExitOnFailure(hr, "Unexpected call to CabWrite()."); + } + +LExit: + pContext->Cabinet.hrError = hr; + return FAILED(hr) ? -1 : cbWrite; +} + +static long FAR DIAMONDAPI CabSeek( + __in INT_PTR hf, + __in long dist, + __in int seektype + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER_CONTEXT* pContext = vpContext; + HANDLE hFile = (HANDLE)hf; + LARGE_INTEGER liDistance = { }; + LARGE_INTEGER liNewPointer = { }; + DWORD dwSeekType = 0; + + // We assume that CabSeek() will only be called to seek the + // cabinet itself so we have to offset the seek operations to + // where the internal cabinet starts. + switch (seektype) + { + case FILE_BEGIN: + liDistance.QuadPart = pContext->qwOffset + dist; + dwSeekType = FILE_BEGIN; + break; + + case FILE_CURRENT: + liDistance.QuadPart = dist; + dwSeekType = FILE_CURRENT; + break; + + case FILE_END: + liDistance.QuadPart = pContext->qwOffset + pContext->qwSize + dist; + dwSeekType = FILE_BEGIN; + break; + + default: + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid seek type.");; + } + + if (SetIfVirtualFilePointer(&pContext->Cabinet, hFile, liDistance.QuadPart, &liNewPointer.QuadPart, seektype)) + { + // set file pointer + if (!::SetFilePointerEx(hFile, liDistance, &liNewPointer, seektype)) + { + ExitWithLastError(hr, "Failed to move file pointer 0x%x bytes.", dist); + } + } + + liNewPointer.QuadPart -= pContext->qwOffset; + +LExit: + pContext->Cabinet.hrError = hr; + return FAILED(hr) ? -1 : liNewPointer.LowPart; +} + +static int FAR DIAMONDAPI CabClose( + __in INT_PTR hf + ) +{ + BURN_CONTAINER_CONTEXT* pContext = vpContext; + HANDLE hFile = (HANDLE)hf; + + CloseIfVirturalFilePointer(&pContext->Cabinet, hFile); + ReleaseFileHandle(hFile); + + return 0; +} + +static HRESULT AddVirtualFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile, + __in LONGLONG llInitialFilePointer + ) +{ + HRESULT hr = S_OK; + + hr = MemEnsureArraySize(reinterpret_cast(&pCabinetContext->rgVirtualFilePointers), pCabinetContext->cVirtualFilePointers, sizeof(BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER), ARRAY_GROWTH_SIZE); + ExitOnFailure(hr, "Failed to allocate memory for the virtual file pointer array."); + + pCabinetContext->rgVirtualFilePointers[pCabinetContext->cVirtualFilePointers].hFile = hFile; + pCabinetContext->rgVirtualFilePointers[pCabinetContext->cVirtualFilePointers].liPosition.QuadPart = llInitialFilePointer; + ++pCabinetContext->cVirtualFilePointers; + +LExit: + return hr; +} + +static HRESULT ReadIfVirtualFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile, + __in DWORD cbRead + ) +{ + HRESULT hr = E_NOTFOUND; + + BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER* pVfp = GetVirtualFilePointer(pCabinetContext, hFile); + if (pVfp) + { + // Set the file handle to the virtual file pointer. + if (!::SetFilePointerEx(hFile, pVfp->liPosition, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to move to virtual file pointer."); + } + + pVfp->liPosition.QuadPart += cbRead; // add the amount that will be read to advance the pointer. + hr = S_OK; + } + +LExit: + return hr; +} + +static BOOL SetIfVirtualFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile, + __in LONGLONG llDistance, + __out LONGLONG* pllNewPostion, + __in DWORD dwSeekType + ) +{ + BOOL fFound = FALSE; + + BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER* pVfp = GetVirtualFilePointer(pCabinetContext, hFile); + if (pVfp) + { + switch (dwSeekType) + { + case FILE_BEGIN: + pVfp->liPosition.QuadPart = llDistance; + break; + + case FILE_CURRENT: + pVfp->liPosition.QuadPart += llDistance; + break; + + case FILE_END: __fallthrough; + default: + AssertSz(FALSE, "Unsupported seek type."); + break; + } + + *pllNewPostion = pVfp->liPosition.QuadPart; + fFound = TRUE; + } + + return fFound; +} + +static HRESULT CloseIfVirturalFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile + ) +{ + HRESULT hr = E_NOTFOUND; + + BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER* pVfp = GetVirtualFilePointer(pCabinetContext, hFile); + if (pVfp) + { + pVfp->hFile = INVALID_HANDLE_VALUE; + pVfp->liPosition.QuadPart = 0; + hr = S_OK; + } + + return hr; +} + +static BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER* GetVirtualFilePointer( + __in BURN_CONTAINER_CONTEXT_CABINET* pCabinetContext, + __in HANDLE hFile + ) +{ + for (DWORD i = 0; i < pCabinetContext->cVirtualFilePointers; ++i) + { + BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER* pVfp = pCabinetContext->rgVirtualFilePointers + i; + if (pVfp->hFile == hFile) + { + return pVfp; + } + } + + return NULL; +} diff --git a/src/burn/engine/cabextract.h b/src/burn/engine/cabextract.h new file mode 100644 index 00000000..31667f2b --- /dev/null +++ b/src/burn/engine/cabextract.h @@ -0,0 +1,40 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// function declarations + +void CabExtractInitialize(); +HRESULT CabExtractOpen( + __in BURN_CONTAINER_CONTEXT* pContext, + __in LPCWSTR wzFilePath + ); +HRESULT CabExtractNextStream( + __in BURN_CONTAINER_CONTEXT* pContext, + __inout_z LPWSTR* psczStreamName + ); +HRESULT CabExtractStreamToFile( + __in BURN_CONTAINER_CONTEXT* pContext, + __in_z LPCWSTR wzFileName + ); +HRESULT CabExtractStreamToBuffer( + __in BURN_CONTAINER_CONTEXT* pContext, + __out BYTE** ppbBuffer, + __out SIZE_T* pcbBuffer + ); +HRESULT CabExtractSkipStream( + __in BURN_CONTAINER_CONTEXT* pContext + ); +HRESULT CabExtractClose( + __in BURN_CONTAINER_CONTEXT* pContext + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/cache.cpp b/src/burn/engine/cache.cpp new file mode 100644 index 00000000..59daf139 --- /dev/null +++ b/src/burn/engine/cache.cpp @@ -0,0 +1,2052 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +static const LPCWSTR BUNDLE_CLEAN_ROOM_WORKING_FOLDER_NAME = L".cr"; +static const LPCWSTR BUNDLE_WORKING_FOLDER_NAME = L".be"; +static const LPCWSTR UNVERIFIED_CACHE_FOLDER_NAME = L".unverified"; +static const LPCWSTR PACKAGE_CACHE_FOLDER_NAME = L"Package Cache"; +static const DWORD FILE_OPERATION_RETRY_COUNT = 3; +static const DWORD FILE_OPERATION_RETRY_WAIT = 2000; + +static BOOL vfInitializedCache = FALSE; +static BOOL vfRunningFromCache = FALSE; +static LPWSTR vsczSourceProcessFolder = NULL; +static LPWSTR vsczWorkingFolder = NULL; +static LPWSTR vsczDefaultUserPackageCache = NULL; +static LPWSTR vsczDefaultMachinePackageCache = NULL; +static LPWSTR vsczCurrentMachinePackageCache = NULL; + +static HRESULT CalculateWorkingFolder( + __in_z LPCWSTR wzBundleId, + __deref_out_z LPWSTR* psczWorkingFolder + ); +static HRESULT GetLastUsedSourceFolder( + __in BURN_VARIABLES* pVariables, + __out_z LPWSTR* psczLastSource + ); +static HRESULT CreateCompletedPath( + __in BOOL fPerMachine, + __in LPCWSTR wzCacheId, + __out LPWSTR* psczCacheDirectory + ); +static HRESULT CreateUnverifiedPath( + __in BOOL fPerMachine, + __in_z LPCWSTR wzPayloadId, + __out_z LPWSTR* psczUnverifiedPayloadPath + ); +static HRESULT GetRootPath( + __in BOOL fPerMachine, + __in BOOL fAllowRedirect, + __deref_out_z LPWSTR* psczRootPath + ); +static HRESULT VerifyThenTransferContainer( + __in BURN_CONTAINER* pContainer, + __in_z LPCWSTR wzCachedPath, + __in_z LPCWSTR wzUnverifiedContainerPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +static HRESULT VerifyThenTransferPayload( + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzCachedPath, + __in_z LPCWSTR wzUnverifiedPayloadPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +static HRESULT CacheTransferFileWithRetry( + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzDestinationPath, + __in BOOL fMove, + __in BURN_CACHE_STEP cacheStep, + __in DWORD64 qwFileSize, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +static HRESULT VerifyFileAgainstContainer( + __in BURN_CONTAINER* pContainer, + __in_z LPCWSTR wzVerifyPath, + __in BOOL fAlreadyCached, + __in BURN_CACHE_STEP cacheStep, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +static HRESULT VerifyFileAgainstPayload( + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzVerifyPath, + __in BOOL fAlreadyCached, + __in BURN_CACHE_STEP cacheStep, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +static HRESULT ResetPathPermissions( + __in BOOL fPerMachine, + __in_z LPCWSTR wzPath + ); +static HRESULT SecurePath( + __in LPCWSTR wzPath + ); +static HRESULT CopyEngineToWorkingFolder( + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzWorkingFolderName, + __in_z LPCWSTR wzExecutableName, + __in BURN_SECTION* pSection, + __deref_out_z_opt LPWSTR* psczEngineWorkingPath + ); +static HRESULT CopyEngineWithSignatureFixup( + __in HANDLE hEngineFile, + __in_z LPCWSTR wzEnginePath, + __in_z LPCWSTR wzTargetPath, + __in BURN_SECTION* pSection + ); +static HRESULT RemoveBundleOrPackage( + __in BOOL fBundle, + __in BOOL fPerMachine, + __in_z LPCWSTR wzBundleOrPackageId, + __in_z LPCWSTR wzCacheId + ); +static HRESULT VerifyHash( + __in BYTE* pbHash, + __in DWORD cbHash, + __in DWORD64 qwFileSize, + __in_z LPCWSTR wzUnverifiedPayloadPath, + __in HANDLE hFile, + __in BURN_CACHE_STEP cacheStep, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +static HRESULT SendCacheBeginMessage( + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPVOID pContext, + __in BURN_CACHE_STEP cacheStep + ); +static HRESULT SendCacheSuccessMessage( + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPVOID pContext, + __in DWORD64 qwFileSize + ); +static HRESULT SendCacheCompleteMessage( + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPVOID pContext, + __in HRESULT hrStatus + ); + + +extern "C" HRESULT CacheInitialize( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in_z_opt LPCWSTR wzSourceProcessPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCurrentPath = NULL; + LPWSTR sczCompletedFolder = NULL; + LPWSTR sczCompletedPath = NULL; + LPWSTR sczOriginalSource = NULL; + LPWSTR sczOriginalSourceFolder = NULL; + int nCompare = 0; + + if (!vfInitializedCache) + { + hr = PathForCurrentProcess(&sczCurrentPath, NULL); + ExitOnFailure(hr, "Failed to get current process path."); + + // Determine if we are running from the package cache or not. + hr = CacheGetCompletedPath(pRegistration->fPerMachine, pRegistration->sczId, &sczCompletedFolder); + ExitOnFailure(hr, "Failed to get completed path for bundle."); + + hr = PathConcat(sczCompletedFolder, pRegistration->sczExecutableName, &sczCompletedPath); + ExitOnFailure(hr, "Failed to combine working path with engine file name."); + + hr = PathCompare(sczCurrentPath, sczCompletedPath, &nCompare); + ExitOnFailure(hr, "Failed to compare current path for bundle: %ls", sczCurrentPath); + + vfRunningFromCache = (CSTR_EQUAL == nCompare); + + // If a source process path was not provided (e.g. we are not being + // run in a clean room) then use the current process path as the + // source process path. + if (!wzSourceProcessPath) + { + wzSourceProcessPath = sczCurrentPath; + } + + hr = PathGetDirectory(wzSourceProcessPath, &vsczSourceProcessFolder); + ExitOnFailure(hr, "Failed to initialize cache source folder."); + + // If we're not running from the cache, ensure the original source is set. + if (!vfRunningFromCache) + { + // If the original source has not been set already then set it where the bundle is + // running from right now. This value will be persisted and we'll use it when launched + // from the clean room or package cache since none of our packages will be relative to + // those locations. + hr = VariableGetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE, &sczOriginalSource); + if (E_NOTFOUND == hr) + { + hr = VariableSetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE, wzSourceProcessPath, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set original source variable."); + + hr = StrAllocString(&sczOriginalSource, wzSourceProcessPath, 0); + ExitOnFailure(hr, "Failed to copy current path to original source."); + } + + hr = VariableGetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER, &sczOriginalSourceFolder); + if (E_NOTFOUND == hr) + { + hr = PathGetDirectory(sczOriginalSource, &sczOriginalSourceFolder); + ExitOnFailure(hr, "Failed to get directory from original source path."); + + hr = VariableSetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER, sczOriginalSourceFolder, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set original source directory variable."); + } + } + + vfInitializedCache = TRUE; + } + +LExit: + ReleaseStr(sczCurrentPath); + ReleaseStr(sczCompletedFolder); + ReleaseStr(sczCompletedPath); + ReleaseStr(sczOriginalSource); + ReleaseStr(sczOriginalSourceFolder); + + return hr; +} + +extern "C" HRESULT CacheEnsureWorkingFolder( + __in_z_opt LPCWSTR wzBundleId, + __deref_out_z_opt LPWSTR* psczWorkingFolder + ) +{ + HRESULT hr = S_OK; + LPWSTR sczWorkingFolder = NULL; + + hr = CalculateWorkingFolder(wzBundleId, &sczWorkingFolder); + ExitOnFailure(hr, "Failed to calculate working folder to ensure it exists."); + + hr = DirEnsureExists(sczWorkingFolder, NULL); + ExitOnFailure(hr, "Failed create working folder."); + + // Best effort to ensure our working folder is not encrypted. + ::DecryptFileW(sczWorkingFolder, 0); + + if (psczWorkingFolder) + { + hr = StrAllocString(psczWorkingFolder, sczWorkingFolder, 0); + ExitOnFailure(hr, "Failed to copy working folder."); + } + +LExit: + ReleaseStr(sczWorkingFolder); + + return hr; +} + +extern "C" HRESULT CacheCalculateBundleWorkingPath( + __in_z LPCWSTR wzBundleId, + __in LPCWSTR wzExecutableName, + __deref_out_z LPWSTR* psczWorkingPath + ) +{ + Assert(vfInitializedCache); + + HRESULT hr = S_OK; + LPWSTR sczWorkingFolder = NULL; + + // If the bundle is running out of the package cache then we use that as the + // working folder since we feel safe in the package cache. + if (vfRunningFromCache) + { + hr = PathForCurrentProcess(psczWorkingPath, NULL); + ExitOnFailure(hr, "Failed to get current process path."); + } + else // Otherwise, use the real working folder. + { + hr = CalculateWorkingFolder(wzBundleId, &sczWorkingFolder); + ExitOnFailure(hr, "Failed to get working folder for bundle."); + + hr = StrAllocFormatted(psczWorkingPath, L"%ls%ls\\%ls", sczWorkingFolder, BUNDLE_WORKING_FOLDER_NAME, wzExecutableName); + ExitOnFailure(hr, "Failed to calculate the bundle working path."); + } + +LExit: + ReleaseStr(sczWorkingFolder); + + return hr; +} + +extern "C" HRESULT CacheCalculateBundleLayoutWorkingPath( + __in_z LPCWSTR wzBundleId, + __deref_out_z LPWSTR* psczWorkingPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczWorkingFolder = NULL; + + hr = CalculateWorkingFolder(wzBundleId, psczWorkingPath); + ExitOnFailure(hr, "Failed to get working folder for bundle layout."); + + hr = StrAllocConcat(psczWorkingPath, wzBundleId, 0); + ExitOnFailure(hr, "Failed to append bundle id for bundle layout working path."); + +LExit: + ReleaseStr(sczWorkingFolder); + + return hr; +} + +extern "C" HRESULT CacheCalculatePayloadWorkingPath( + __in_z LPCWSTR wzBundleId, + __in BURN_PAYLOAD* pPayload, + __deref_out_z LPWSTR* psczWorkingPath + ) +{ + HRESULT hr = S_OK; + + hr = CalculateWorkingFolder(wzBundleId, psczWorkingPath); + ExitOnFailure(hr, "Failed to get working folder for payload."); + + hr = StrAllocConcat(psczWorkingPath, pPayload->sczKey, 0); + ExitOnFailure(hr, "Failed to append Id as payload unverified path."); + +LExit: + return hr; +} + +extern "C" HRESULT CacheCalculateContainerWorkingPath( + __in_z LPCWSTR wzBundleId, + __in BURN_CONTAINER* pContainer, + __deref_out_z LPWSTR* psczWorkingPath + ) +{ + HRESULT hr = S_OK; + + hr = CalculateWorkingFolder(wzBundleId, psczWorkingPath); + ExitOnFailure(hr, "Failed to get working folder for container."); + + hr = StrAllocConcat(psczWorkingPath, pContainer->sczHash, 0); + ExitOnFailure(hr, "Failed to append hash as container unverified path."); + +LExit: + return hr; +} + +extern "C" HRESULT CacheGetRootCompletedPath( + __in BOOL fPerMachine, + __in BOOL fForceInitialize, + __deref_out_z LPWSTR* psczRootCompletedPath + ) +{ + HRESULT hr = S_OK; + + if (fForceInitialize) + { + hr = CreateCompletedPath(fPerMachine, L"", psczRootCompletedPath); + } + else + { + hr = GetRootPath(fPerMachine, TRUE, psczRootCompletedPath); + } + + return hr; +} + +extern "C" HRESULT CacheGetCompletedPath( + __in BOOL fPerMachine, + __in_z LPCWSTR wzCacheId, + __deref_out_z LPWSTR* psczCompletedPath + ) +{ + HRESULT hr = S_OK; + BOOL fRedirected = FALSE; + LPWSTR sczRootPath = NULL; + LPWSTR sczCurrentCompletedPath = NULL; + LPWSTR sczDefaultCompletedPath = NULL; + + hr = GetRootPath(fPerMachine, TRUE, &sczRootPath); + ExitOnFailure(hr, "Failed to get %hs package cache root directory.", fPerMachine ? "per-machine" : "per-user"); + + // GetRootPath returns S_FALSE if the package cache is redirected elsewhere. + fRedirected = S_FALSE == hr; + + hr = PathConcat(sczRootPath, wzCacheId, &sczCurrentCompletedPath); + ExitOnFailure(hr, "Failed to construct cache path."); + + hr = PathBackslashTerminate(&sczCurrentCompletedPath); + ExitOnFailure(hr, "Failed to ensure cache path was backslash terminated."); + + // Return the old package cache directory if the new directory does not exist but the old directory does. + // If neither package cache directory exists return the (possibly) redirected package cache directory. + if (fRedirected && !DirExists(sczCurrentCompletedPath, NULL)) + { + hr = GetRootPath(fPerMachine, FALSE, &sczRootPath); + ExitOnFailure(hr, "Failed to get old %hs package cache root directory.", fPerMachine ? "per-machine" : "per-user"); + + hr = PathConcat(sczRootPath, wzCacheId, &sczDefaultCompletedPath); + ExitOnFailure(hr, "Failed to construct cache path."); + + hr = PathBackslashTerminate(&sczDefaultCompletedPath); + ExitOnFailure(hr, "Failed to ensure cache path was backslash terminated."); + + if (DirExists(sczDefaultCompletedPath, NULL)) + { + *psczCompletedPath = sczDefaultCompletedPath; + sczDefaultCompletedPath = NULL; + + ExitFunction(); + } + } + + *psczCompletedPath = sczCurrentCompletedPath; + sczCurrentCompletedPath = NULL; + +LExit: + ReleaseNullStr(sczDefaultCompletedPath); + ReleaseNullStr(sczCurrentCompletedPath); + ReleaseNullStr(sczRootPath); + + return hr; +} + +extern "C" HRESULT CacheGetResumePath( + __in_z LPCWSTR wzPayloadWorkingPath, + __deref_out_z LPWSTR* psczResumePath + ) +{ + HRESULT hr = S_OK; + + hr = StrAllocFormatted(psczResumePath, L"%ls.R", wzPayloadWorkingPath); + ExitOnFailure(hr, "Failed to create resume path."); + +LExit: + return hr; +} + +extern "C" HRESULT CacheGetLocalSourcePaths( + __in_z LPCWSTR wzRelativePath, + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzDestinationPath, + __in_z_opt LPCWSTR wzLayoutDirectory, + __in BURN_VARIABLES* pVariables, + __inout LPWSTR** prgSearchPaths, + __out DWORD* pcSearchPaths, + __out DWORD* pdwLikelySearchPath, + __out DWORD* pdwDestinationSearchPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCurrentPath = NULL; + LPWSTR sczLastSourceFolder = NULL; + LPWSTR* psczPath = NULL; + BOOL fPreferSourcePathLocation = FALSE; + BOOL fTryLastFolder = FALSE; + BOOL fTryRelativePath = FALSE; + BOOL fSourceIsAbsolute = FALSE; + DWORD cSearchPaths = 0; + DWORD dwLikelySearchPath = 0; + DWORD dwDestinationSearchPath = 0; + + AssertSz(vfInitializedCache, "Cache wasn't initialized"); + + hr = GetLastUsedSourceFolder(pVariables, &sczLastSourceFolder); + fPreferSourcePathLocation = !vfRunningFromCache || FAILED(hr); + fTryLastFolder = SUCCEEDED(hr) && sczLastSourceFolder && *sczLastSourceFolder && CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, vsczSourceProcessFolder, -1, sczLastSourceFolder, -1); + fTryRelativePath = CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, wzSourcePath, -1, wzRelativePath, -1); + fSourceIsAbsolute = PathIsAbsolute(wzSourcePath); + + // If the source path provided is a full path, try that first. + if (fSourceIsAbsolute) + { + hr = MemEnsureArraySize(reinterpret_cast(prgSearchPaths), cSearchPaths + 1, sizeof(LPWSTR), BURN_CACHE_MAX_SEARCH_PATHS); + ExitOnFailure(hr, "Failed to ensure size for search paths array."); + + psczPath = *prgSearchPaths + cSearchPaths; + ++cSearchPaths; + + hr = StrAllocString(psczPath, wzSourcePath, 0); + ExitOnFailure(hr, "Failed to copy absolute source path."); + } + else + { + // If none of the paths exist, then most BAs will want to prompt the user with a possible path. + // The destination path is a temporary location and so not really a possible path. + dwLikelySearchPath = 1; + } + + // Try the destination path next. + hr = MemEnsureArraySize(reinterpret_cast(prgSearchPaths), cSearchPaths + 1, sizeof(LPWSTR), BURN_CACHE_MAX_SEARCH_PATHS); + ExitOnFailure(hr, "Failed to ensure size for search paths array."); + + dwDestinationSearchPath = cSearchPaths; + psczPath = *prgSearchPaths + cSearchPaths; + ++cSearchPaths; + + hr = StrAllocString(psczPath, wzDestinationPath, 0); + ExitOnFailure(hr, "Failed to copy absolute source path."); + + if (!fSourceIsAbsolute) + { + // Calculate the source path location. + // In the case where we are in the bundle's package cache and + // couldn't find a last used source that will be the package cache path + // which isn't likely to have what we are looking for. + hr = MemEnsureArraySize(reinterpret_cast(prgSearchPaths), cSearchPaths + 1, sizeof(LPWSTR), BURN_CACHE_MAX_SEARCH_PATHS); + ExitOnFailure(hr, "Failed to ensure size for search paths array."); + + hr = PathConcat(vsczSourceProcessFolder, wzSourcePath, &sczCurrentPath); + ExitOnFailure(hr, "Failed to combine source process folder with source."); + + // If we're not running from cache or we couldn't get the last source, + // try the source path location next. + if (fPreferSourcePathLocation) + { + (*prgSearchPaths)[cSearchPaths] = sczCurrentPath; + ++cSearchPaths; + sczCurrentPath = NULL; + } + + // If we have a last used source and it is not the source path location, + // add the last used source to the search path next. + if (fTryLastFolder) + { + hr = MemEnsureArraySize(reinterpret_cast(prgSearchPaths), cSearchPaths + 1, sizeof(LPWSTR), BURN_CACHE_MAX_SEARCH_PATHS); + ExitOnFailure(hr, "Failed to ensure size for search paths array."); + + psczPath = *prgSearchPaths + cSearchPaths; + ++cSearchPaths; + + hr = PathConcat(sczLastSourceFolder, wzSourcePath, psczPath); + ExitOnFailure(hr, "Failed to combine last source with source."); + } + + if (!fPreferSourcePathLocation) + { + (*prgSearchPaths)[cSearchPaths] = sczCurrentPath; + ++cSearchPaths; + sczCurrentPath = NULL; + } + + // Also consider the layout directory if doing Layout. + if (wzLayoutDirectory) + { + hr = MemEnsureArraySize(reinterpret_cast(prgSearchPaths), cSearchPaths + 1, sizeof(LPWSTR), BURN_CACHE_MAX_SEARCH_PATHS); + ExitOnFailure(hr, "Failed to ensure size for search paths array."); + + psczPath = *prgSearchPaths + cSearchPaths; + ++cSearchPaths; + + hr = PathConcat(wzLayoutDirectory, wzSourcePath, psczPath); + ExitOnFailure(hr, "Failed to combine layout source with source."); + } + } + + if (fTryRelativePath) + { + hr = MemEnsureArraySize(reinterpret_cast(prgSearchPaths), cSearchPaths + 1, sizeof(LPWSTR), BURN_CACHE_MAX_SEARCH_PATHS); + ExitOnFailure(hr, "Failed to ensure size for search paths array."); + + hr = PathConcat(vsczSourceProcessFolder, wzRelativePath, &sczCurrentPath); + ExitOnFailure(hr, "Failed to combine source process folder with relative."); + + if (fPreferSourcePathLocation) + { + (*prgSearchPaths)[cSearchPaths] = sczCurrentPath; + ++cSearchPaths; + sczCurrentPath = NULL; + } + + if (fTryLastFolder) + { + hr = MemEnsureArraySize(reinterpret_cast(prgSearchPaths), cSearchPaths + 1, sizeof(LPWSTR), BURN_CACHE_MAX_SEARCH_PATHS); + ExitOnFailure(hr, "Failed to ensure size for search paths array."); + + psczPath = *prgSearchPaths + cSearchPaths; + ++cSearchPaths; + + hr = PathConcat(sczLastSourceFolder, wzRelativePath, psczPath); + ExitOnFailure(hr, "Failed to combine last source with relative."); + } + + if (!fPreferSourcePathLocation) + { + (*prgSearchPaths)[cSearchPaths] = sczCurrentPath; + ++cSearchPaths; + sczCurrentPath = NULL; + } + + if (wzLayoutDirectory) + { + hr = MemEnsureArraySize(reinterpret_cast(prgSearchPaths), cSearchPaths + 1, sizeof(LPWSTR), BURN_CACHE_MAX_SEARCH_PATHS); + ExitOnFailure(hr, "Failed to ensure size for search paths array."); + + psczPath = *prgSearchPaths + cSearchPaths; + ++cSearchPaths; + + hr = PathConcat(wzLayoutDirectory, wzSourcePath, psczPath); + ExitOnFailure(hr, "Failed to combine layout source with relative."); + } + } + +LExit: + ReleaseStr(sczCurrentPath); + ReleaseStr(sczLastSourceFolder); + + AssertSz(cSearchPaths <= BURN_CACHE_MAX_SEARCH_PATHS, "Got more than BURN_CACHE_MAX_SEARCH_PATHS search paths"); + *pcSearchPaths = cSearchPaths; + *pdwLikelySearchPath = dwLikelySearchPath; + *pdwDestinationSearchPath = dwDestinationSearchPath; + + return hr; +} + +extern "C" HRESULT CacheSetLastUsedSource( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzRelativePath + ) +{ + HRESULT hr = S_OK; + size_t cchSourcePath = 0; + size_t cchRelativePath = 0; + size_t iSourceRelativePath = 0; + LPWSTR sczSourceFolder = NULL; + LPWSTR sczLastSourceFolder = NULL; + int nCompare = 0; + + hr = ::StringCchLengthW(wzSourcePath, STRSAFE_MAX_CCH, &cchSourcePath); + ExitOnFailure(hr, "Failed to determine length of source path."); + + hr = ::StringCchLengthW(wzRelativePath, STRSAFE_MAX_CCH, &cchRelativePath); + ExitOnFailure(hr, "Failed to determine length of relative path."); + + // If the source path is smaller than the relative path (plus space for "X:\") then we know they + // are not relative to each other. + if (cchSourcePath < cchRelativePath + 3) + { + ExitFunction(); + } + + // If the source path ends with the relative path then this source could be a new path. + iSourceRelativePath = cchSourcePath - cchRelativePath; + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, wzSourcePath + iSourceRelativePath, -1, wzRelativePath, -1)) + { + hr = StrAllocString(&sczSourceFolder, wzSourcePath, iSourceRelativePath); + ExitOnFailure(hr, "Failed to trim source folder."); + + hr = VariableGetString(pVariables, BURN_BUNDLE_LAST_USED_SOURCE, &sczLastSourceFolder); + if (SUCCEEDED(hr)) + { + nCompare = ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczSourceFolder, -1, sczLastSourceFolder, -1); + } + else if (E_NOTFOUND == hr) + { + nCompare = CSTR_GREATER_THAN; + hr = S_OK; + } + + if (CSTR_EQUAL != nCompare) + { + hr = VariableSetString(pVariables, BURN_BUNDLE_LAST_USED_SOURCE, sczSourceFolder, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set last source."); + } + } + +LExit: + ReleaseStr(sczLastSourceFolder); + ReleaseStr(sczSourceFolder); + + return hr; +} + +extern "C" HRESULT CacheSendProgressCallback( + __in DOWNLOAD_CACHE_CALLBACK* pCallback, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in HANDLE hDestinationFile + ) +{ + static LARGE_INTEGER LARGE_INTEGER_ZERO = { }; + + HRESULT hr = S_OK; + DWORD dwResult = PROGRESS_CONTINUE; + LARGE_INTEGER liTotalSize = { }; + LARGE_INTEGER liTotalTransferred = { }; + + if (pCallback->pfnProgress) + { + liTotalSize.QuadPart = dw64Total; + liTotalTransferred.QuadPart = dw64Progress; + + dwResult = (*pCallback->pfnProgress)(liTotalSize, liTotalTransferred, LARGE_INTEGER_ZERO, LARGE_INTEGER_ZERO, 1, CALLBACK_CHUNK_FINISHED, INVALID_HANDLE_VALUE, hDestinationFile, pCallback->pv); + switch (dwResult) + { + case PROGRESS_CONTINUE: + hr = S_OK; + break; + + case PROGRESS_CANCEL: __fallthrough; // TODO: should cancel and stop be treated differently? + case PROGRESS_STOP: + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + ExitOnRootFailure(hr, "UX aborted on download progress."); + + case PROGRESS_QUIET: // Not actually an error, just an indication to the caller to stop requesting progress. + pCallback->pfnProgress = NULL; + hr = S_OK; + break; + + default: + hr = E_UNEXPECTED; + ExitOnRootFailure(hr, "Invalid return code from progress routine."); + } + } + +LExit: + return hr; +} + +extern "C" void CacheSendErrorCallback( + __in DOWNLOAD_CACHE_CALLBACK* pCallback, + __in HRESULT hrError, + __in_z_opt LPCWSTR wzError, + __out_opt BOOL* pfRetry + ) +{ + if (pfRetry) + { + *pfRetry = FALSE; + } + + if (pCallback->pfnCancel) + { + int nResult = (*pCallback->pfnCancel)(hrError, wzError, pfRetry != NULL, pCallback->pv); + if (pfRetry && IDRETRY == nResult) + { + *pfRetry = TRUE; + } + } +} + +extern "C" BOOL CacheBundleRunningFromCache() +{ + return vfRunningFromCache; +} + +extern "C" HRESULT CacheBundleToCleanRoom( + __in BURN_SECTION* pSection, + __deref_out_z_opt LPWSTR* psczCleanRoomBundlePath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczSourcePath = NULL; + LPWSTR wzExecutableName = NULL; + + hr = PathForCurrentProcess(&sczSourcePath, NULL); + ExitOnFailure(hr, "Failed to get current path for process to cache to clean room."); + + wzExecutableName = PathFile(sczSourcePath); + + hr = CopyEngineToWorkingFolder(sczSourcePath, BUNDLE_CLEAN_ROOM_WORKING_FOLDER_NAME, wzExecutableName, pSection, psczCleanRoomBundlePath); + ExitOnFailure(hr, "Failed to cache bundle to clean room."); + +LExit: + ReleaseStr(sczSourcePath); + + return hr; +} + +extern "C" HRESULT CacheBundleToWorkingDirectory( + __in_z LPCWSTR /*wzBundleId*/, + __in_z LPCWSTR wzExecutableName, + __in BURN_SECTION* pSection, + __deref_out_z_opt LPWSTR* psczEngineWorkingPath + ) +{ + Assert(vfInitializedCache); + + HRESULT hr = S_OK; + LPWSTR sczSourcePath = NULL; + + // Initialize the source. + hr = PathForCurrentProcess(&sczSourcePath, NULL); + ExitOnFailure(hr, "Failed to get current process path."); + + // If the bundle is running out of the package cache then we don't need to copy it to + // the working folder since we feel safe in the package cache and will run from there. + if (vfRunningFromCache) + { + hr = StrAllocString(psczEngineWorkingPath, sczSourcePath, 0); + ExitOnFailure(hr, "Failed to use current process path as target path."); + } + else // otherwise, carry on putting the bundle in the working folder. + { + hr = CopyEngineToWorkingFolder(sczSourcePath, BUNDLE_WORKING_FOLDER_NAME, wzExecutableName, pSection, psczEngineWorkingPath); + ExitOnFailure(hr, "Failed to copy engine to working folder."); + } + +LExit: + ReleaseStr(sczSourcePath); + + return hr; +} + +extern "C" HRESULT CacheLayoutBundle( + __in_z LPCWSTR wzExecutableName, + __in_z LPCWSTR wzLayoutDirectory, + __in_z LPCWSTR wzSourceBundlePath, + __in DWORD64 qwBundleSize, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + LPWSTR sczTargetPath = NULL; + + hr = PathConcat(wzLayoutDirectory, wzExecutableName, &sczTargetPath); + ExitOnFailure(hr, "Failed to combine completed path with engine file name for layout."); + + LogStringLine(REPORT_STANDARD, "Layout bundle from: '%ls' to: '%ls'", wzSourceBundlePath, sczTargetPath); + + hr = CacheTransferFileWithRetry(wzSourceBundlePath, sczTargetPath, TRUE, BURN_CACHE_STEP_FINALIZE, qwBundleSize, pfnCacheMessageHandler, pfnProgress, pContext); + ExitOnFailure(hr, "Failed to layout bundle from: '%ls' to '%ls'", wzSourceBundlePath, sczTargetPath); + +LExit: + ReleaseStr(sczTargetPath); + + return hr; +} + +extern "C" HRESULT CacheCompleteBundle( + __in BOOL fPerMachine, + __in_z LPCWSTR wzExecutableName, + __in_z LPCWSTR wzBundleId, + __in_z LPCWSTR wzSourceBundlePath +#ifdef DEBUG + , __in_z LPCWSTR wzExecutablePath +#endif + ) +{ + HRESULT hr = S_OK; + int nCompare = 0; + LPWSTR sczTargetDirectory = NULL; + LPWSTR sczTargetPath = NULL; + LPWSTR sczSourceDirectory = NULL; + LPWSTR sczPayloadSourcePath = NULL; + + hr = CreateCompletedPath(fPerMachine, wzBundleId, &sczTargetDirectory); + ExitOnFailure(hr, "Failed to create completed cache path for bundle."); + + hr = PathConcat(sczTargetDirectory, wzExecutableName, &sczTargetPath); + ExitOnFailure(hr, "Failed to combine completed path with engine file name."); + + // We can't just use wzExecutablePath because we needed to call CreateCompletedPath to ensure that the destination was secured. + Assert(CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, wzExecutablePath, -1, sczTargetPath, -1)); + + // If the bundle is running out of the package cache then we don't need to copy it there + // (and don't want to since it'll be in use) so bail. + hr = PathCompare(wzSourceBundlePath, sczTargetPath, &nCompare); + ExitOnFailure(hr, "Failed to compare completed cache path for bundle: %ls", wzSourceBundlePath); + + if (CSTR_EQUAL == nCompare) + { + ExitFunction(); + } + + // Otherwise, carry on putting the bundle in the cache. + LogStringLine(REPORT_STANDARD, "Caching bundle from: '%ls' to: '%ls'", wzSourceBundlePath, sczTargetPath); + + FileRemoveFromPendingRename(sczTargetPath); // best effort to ensure bundle is not deleted from cache post restart. + + hr = FileEnsureCopyWithRetry(wzSourceBundlePath, sczTargetPath, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT); + ExitOnFailure(hr, "Failed to cache bundle from: '%ls' to '%ls'", wzSourceBundlePath, sczTargetPath); + + // Reset the path permissions in the cache. + hr = ResetPathPermissions(fPerMachine, sczTargetPath); + ExitOnFailure(hr, "Failed to reset permissions on cached bundle: '%ls'", sczTargetPath); + + hr = PathGetDirectory(wzSourceBundlePath, &sczSourceDirectory); + ExitOnFailure(hr, "Failed to get directory from engine working path: %ls", wzSourceBundlePath); + +LExit: + ReleaseStr(sczPayloadSourcePath); + ReleaseStr(sczSourceDirectory); + ReleaseStr(sczTargetPath); + ReleaseStr(sczTargetDirectory); + + return hr; +} + +extern "C" HRESULT CacheLayoutContainer( + __in BURN_CONTAINER* pContainer, + __in_z_opt LPCWSTR wzLayoutDirectory, + __in_z LPCWSTR wzUnverifiedContainerPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCachedPath = NULL; + + hr = PathConcat(wzLayoutDirectory, pContainer->sczFilePath, &sczCachedPath); + ExitOnFailure(hr, "Failed to concat complete cached path."); + + hr = VerifyThenTransferContainer(pContainer, sczCachedPath, wzUnverifiedContainerPath, fMove, pfnCacheMessageHandler, pfnProgress, pContext); + ExitOnFailure(hr, "Failed to layout container from cached path: %ls", sczCachedPath); + +LExit: + ReleaseStr(sczCachedPath); + + return hr; +} + +extern "C" HRESULT CacheLayoutPayload( + __in BURN_PAYLOAD* pPayload, + __in_z_opt LPCWSTR wzLayoutDirectory, + __in_z LPCWSTR wzUnverifiedPayloadPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCachedPath = NULL; + + hr = PathConcat(wzLayoutDirectory, pPayload->sczFilePath, &sczCachedPath); + ExitOnFailure(hr, "Failed to concat complete cached path."); + + hr = VerifyThenTransferPayload(pPayload, sczCachedPath, wzUnverifiedPayloadPath, fMove, pfnCacheMessageHandler, pfnProgress, pContext); + ExitOnFailure(hr, "Failed to layout payload from cached payload: %ls", sczCachedPath); + +LExit: + ReleaseStr(sczCachedPath); + + return hr; +} + +extern "C" HRESULT CacheCompletePayload( + __in BOOL fPerMachine, + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzCacheId, + __in_z LPCWSTR wzWorkingPayloadPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCachedDirectory = NULL; + LPWSTR sczCachedPath = NULL; + LPWSTR sczUnverifiedPayloadPath = NULL; + + hr = CreateCompletedPath(fPerMachine, wzCacheId, &sczCachedDirectory); + ExitOnFailure(hr, "Failed to get cached path for package with cache id: %ls", wzCacheId); + + hr = PathConcat(sczCachedDirectory, pPayload->sczFilePath, &sczCachedPath); + ExitOnFailure(hr, "Failed to concat complete cached path."); + + // If the cached file matches what we expected, we're good. + hr = VerifyFileAgainstPayload(pPayload, sczCachedPath, TRUE, BURN_CACHE_STEP_HASH_TO_SKIP_VERIFY, pfnCacheMessageHandler, pfnProgress, pContext); + if (SUCCEEDED(hr)) + { + ExitFunction(); + } + + hr = CreateUnverifiedPath(fPerMachine, pPayload->sczKey, &sczUnverifiedPayloadPath); + ExitOnFailure(hr, "Failed to create unverified path."); + + // If the working path exists, let's get it into the unverified path so we can reset the ACLs and verify the file. + if (FileExistsEx(wzWorkingPayloadPath, NULL)) + { + hr = CacheTransferFileWithRetry(wzWorkingPayloadPath, sczUnverifiedPayloadPath, fMove, BURN_CACHE_STEP_STAGE, pPayload->qwFileSize, pfnCacheMessageHandler, pfnProgress, pContext); + ExitOnFailure(hr, "Failed to transfer working path to unverified path for payload: %ls.", pPayload->sczKey); + } + else if (FileExistsEx(sczUnverifiedPayloadPath, NULL)) + { + // Make sure the staging progress is sent even though there was nothing to do. + hr = SendCacheBeginMessage(pfnCacheMessageHandler, pContext, BURN_CACHE_STEP_STAGE); + if (SUCCEEDED(hr)) + { + hr = SendCacheSuccessMessage(pfnCacheMessageHandler, pContext, pPayload->qwFileSize); + } + SendCacheCompleteMessage(pfnCacheMessageHandler, pContext, hr); + ExitOnFailure(hr, "Aborted transferring working path to unverified path for payload: %ls.", pPayload->sczKey); + } + else // if the working path and unverified path do not exist, nothing we can do. + { + hr = E_FILENOTFOUND; + ExitOnFailure(hr, "Failed to find payload: %ls in working path: %ls and unverified path: %ls", pPayload->sczKey, wzWorkingPayloadPath, sczUnverifiedPayloadPath); + } + + hr = ResetPathPermissions(fPerMachine, sczUnverifiedPayloadPath); + ExitOnFailure(hr, "Failed to reset permissions on unverified cached payload: %ls", pPayload->sczKey); + + hr = VerifyFileAgainstPayload(pPayload, sczUnverifiedPayloadPath, FALSE, BURN_CACHE_STEP_HASH, pfnCacheMessageHandler, pfnProgress, pContext); + LogExitOnFailure(hr, MSG_FAILED_VERIFY_PAYLOAD, "Failed to verify payload: %ls at path: %ls", pPayload->sczKey, sczUnverifiedPayloadPath, NULL); + + LogId(REPORT_STANDARD, MSG_VERIFIED_ACQUIRED_PAYLOAD, pPayload->sczKey, sczUnverifiedPayloadPath, fMove ? "moving" : "copying", sczCachedPath); + + hr = CacheTransferFileWithRetry(sczUnverifiedPayloadPath, sczCachedPath, TRUE, BURN_CACHE_STEP_FINALIZE, pPayload->qwFileSize, pfnCacheMessageHandler, pfnProgress, pContext); + ExitOnFailure(hr, "Failed to move verified file to complete payload path: %ls", sczCachedPath); + + ::DecryptFileW(sczCachedPath, 0); // Let's try to make sure it's not encrypted. + +LExit: + ReleaseStr(sczUnverifiedPayloadPath); + ReleaseStr(sczCachedPath); + ReleaseStr(sczCachedDirectory); + + return hr; +} + +extern "C" HRESULT CacheVerifyContainer( + __in BURN_CONTAINER* pContainer, + __in_z LPCWSTR wzCachedDirectory, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCachedPath = NULL; + + hr = PathConcat(wzCachedDirectory, pContainer->sczFilePath, &sczCachedPath); + ExitOnFailure(hr, "Failed to concat complete cached path."); + + hr = VerifyFileAgainstContainer(pContainer, sczCachedPath, TRUE, BURN_CACHE_STEP_HASH_TO_SKIP_ACQUIRE, pfnCacheMessageHandler, pfnProgress, pContext); + +LExit: + ReleaseStr(sczCachedPath); + + return hr; +} + +extern "C" HRESULT CacheVerifyPayload( + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzCachedDirectory, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCachedPath = NULL; + + hr = PathConcat(wzCachedDirectory, pPayload->sczFilePath, &sczCachedPath); + ExitOnFailure(hr, "Failed to concat complete cached path."); + + hr = VerifyFileAgainstPayload(pPayload, sczCachedPath, TRUE, BURN_CACHE_STEP_HASH_TO_SKIP_ACQUIRE, pfnCacheMessageHandler, pfnProgress, pContext); + +LExit: + ReleaseStr(sczCachedPath); + + return hr; +} + +extern "C" HRESULT CacheRemoveWorkingFolder( + __in_z_opt LPCWSTR wzBundleId + ) +{ + HRESULT hr = S_OK; + LPWSTR sczWorkingFolder = NULL; + + if (vfInitializedCache) + { + hr = CalculateWorkingFolder(wzBundleId, &sczWorkingFolder); + ExitOnFailure(hr, "Failed to calculate the working folder to remove it."); + + // Try to clean out everything in the working folder. + hr = DirEnsureDeleteEx(sczWorkingFolder, DIR_DELETE_FILES | DIR_DELETE_RECURSE | DIR_DELETE_SCHEDULE); + TraceError(hr, "Could not delete bundle engine working folder."); + } + +LExit: + ReleaseStr(sczWorkingFolder); + + return hr; +} + +extern "C" HRESULT CacheRemoveBundle( + __in BOOL fPerMachine, + __in_z LPCWSTR wzBundleId + ) +{ + HRESULT hr = S_OK; + + hr = RemoveBundleOrPackage(TRUE, fPerMachine, wzBundleId, wzBundleId); + ExitOnFailure(hr, "Failed to remove bundle id: %ls.", wzBundleId); + +LExit: + return hr; +} + +extern "C" HRESULT CacheRemovePackage( + __in BOOL fPerMachine, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCacheId + ) +{ + HRESULT hr = S_OK; + + hr = RemoveBundleOrPackage(FALSE, fPerMachine, wzPackageId, wzCacheId); + ExitOnFailure(hr, "Failed to remove package id: %ls.", wzPackageId); + +LExit: + return hr; +} + +extern "C" void CacheCleanup( + __in BOOL fPerMachine, + __in_z LPCWSTR wzBundleId + ) +{ + HRESULT hr = S_OK; + LPWSTR sczFolder = NULL; + LPWSTR sczFiles = NULL; + LPWSTR sczDelete = NULL; + HANDLE hFind = INVALID_HANDLE_VALUE; + WIN32_FIND_DATAW wfd = { }; + size_t cchFileName = 0; + + hr = CacheGetCompletedPath(fPerMachine, UNVERIFIED_CACHE_FOLDER_NAME, &sczFolder); + if (SUCCEEDED(hr)) + { + hr = DirEnsureDeleteEx(sczFolder, DIR_DELETE_FILES | DIR_DELETE_RECURSE | DIR_DELETE_SCHEDULE); + } + + if (!fPerMachine) + { + hr = CalculateWorkingFolder(wzBundleId, &sczFolder); + if (SUCCEEDED(hr)) + { + hr = PathConcat(sczFolder, L"*.*", &sczFiles); + if (SUCCEEDED(hr)) + { + hFind = ::FindFirstFileW(sczFiles, &wfd); + if (INVALID_HANDLE_VALUE != hFind) + { + do + { + // Skip directories. + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + continue; + } + + // Skip resume files (they end with ".R"). + hr = ::StringCchLengthW(wfd.cFileName, MAX_PATH, &cchFileName); + if (FAILED(hr) || + 2 < cchFileName && L'.' == wfd.cFileName[cchFileName - 2] && (L'R' == wfd.cFileName[cchFileName - 1] || L'r' == wfd.cFileName[cchFileName - 1])) + { + continue; + } + + hr = PathConcatCch(sczFolder, 0, wfd.cFileName, cchFileName, &sczDelete); + if (SUCCEEDED(hr)) + { + hr = FileEnsureDelete(sczDelete); + } + } while (::FindNextFileW(hFind, &wfd)); + } + } + } + } + + if (INVALID_HANDLE_VALUE != hFind) + { + ::FindClose(hFind); + } + + ReleaseStr(sczDelete); + ReleaseStr(sczFiles); + ReleaseStr(sczFolder); +} + +extern "C" void CacheUninitialize() +{ + ReleaseNullStr(vsczCurrentMachinePackageCache); + ReleaseNullStr(vsczDefaultMachinePackageCache); + ReleaseNullStr(vsczDefaultUserPackageCache); + ReleaseNullStr(vsczWorkingFolder); + ReleaseNullStr(vsczSourceProcessFolder); + + vfRunningFromCache = FALSE; + vfInitializedCache = FALSE; +} + +// Internal functions. + +static HRESULT CalculateWorkingFolder( + __in_z_opt LPCWSTR /*wzBundleId*/, + __deref_out_z LPWSTR* psczWorkingFolder + ) +{ + HRESULT hr = S_OK; + RPC_STATUS rs = RPC_S_OK; + BOOL fElevated = FALSE; + WCHAR wzTempPath[MAX_PATH] = { }; + UUID guid = {}; + WCHAR wzGuid[39]; + + if (!vsczWorkingFolder) + { + ProcElevated(::GetCurrentProcess(), &fElevated); + + if (fElevated) + { + if (!::GetWindowsDirectoryW(wzTempPath, countof(wzTempPath))) + { + ExitWithLastError(hr, "Failed to get windows path for working folder."); + } + + hr = PathFixedBackslashTerminate(wzTempPath, countof(wzTempPath)); + ExitOnFailure(hr, "Failed to ensure windows path for working folder ended in backslash."); + + hr = ::StringCchCatW(wzTempPath, countof(wzTempPath), L"Temp\\"); + ExitOnFailure(hr, "Failed to concat Temp directory on windows path for working folder."); + } + else if (0 == ::GetTempPathW(countof(wzTempPath), wzTempPath)) + { + ExitWithLastError(hr, "Failed to get temp path for working folder."); + } + + rs = ::UuidCreate(&guid); + hr = HRESULT_FROM_RPC(rs); + ExitOnFailure(hr, "Failed to create working folder guid."); + + if (!::StringFromGUID2(guid, wzGuid, countof(wzGuid))) + { + hr = E_OUTOFMEMORY; + ExitOnRootFailure(hr, "Failed to convert working folder guid into string."); + } + + hr = StrAllocFormatted(&vsczWorkingFolder, L"%ls%ls\\", wzTempPath, wzGuid); + ExitOnFailure(hr, "Failed to append bundle id on to temp path for working folder."); + } + + hr = StrAllocString(psczWorkingFolder, vsczWorkingFolder, 0); + ExitOnFailure(hr, "Failed to copy working folder path."); + +LExit: + return hr; +} + +static HRESULT GetRootPath( + __in BOOL fPerMachine, + __in BOOL fAllowRedirect, + __deref_out_z LPWSTR* psczRootPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczAppData = NULL; + int nCompare = 0; + + // Cache paths are initialized once so they cannot be changed while the engine is caching payloads. + if (fPerMachine) + { + // Always construct the default machine package cache path so we can determine if we're redirected. + if (!vsczDefaultMachinePackageCache) + { + hr = PathGetKnownFolder(CSIDL_COMMON_APPDATA, &sczAppData); + ExitOnFailure(hr, "Failed to find local %hs appdata directory.", "per-machine"); + + hr = PathConcat(sczAppData, PACKAGE_CACHE_FOLDER_NAME, &vsczDefaultMachinePackageCache); + ExitOnFailure(hr, "Failed to construct %hs package cache directory name.", "per-machine"); + + hr = PathBackslashTerminate(&vsczDefaultMachinePackageCache); + ExitOnFailure(hr, "Failed to backslash terminate default %hs package cache directory name.", "per-machine"); + } + + if (!vsczCurrentMachinePackageCache) + { + hr = PolcReadString(POLICY_BURN_REGISTRY_PATH, L"PackageCache", NULL, &vsczCurrentMachinePackageCache); + ExitOnFailure(hr, "Failed to read PackageCache policy directory."); + + if (vsczCurrentMachinePackageCache) + { + hr = PathBackslashTerminate(&vsczCurrentMachinePackageCache); + ExitOnFailure(hr, "Failed to backslash terminate redirected per-machine package cache directory name."); + } + else + { + hr = StrAllocString(&vsczCurrentMachinePackageCache, vsczDefaultMachinePackageCache, 0); + ExitOnFailure(hr, "Failed to copy default package cache directory to current package cache directory."); + } + } + + hr = StrAllocString(psczRootPath, fAllowRedirect ? vsczCurrentMachinePackageCache : vsczDefaultMachinePackageCache, 0); + ExitOnFailure(hr, "Failed to copy %hs package cache root directory.", "per-machine"); + + hr = PathCompare(vsczDefaultMachinePackageCache, *psczRootPath, &nCompare); + ExitOnFailure(hr, "Failed to compare default and current package cache directories."); + + // Return S_FALSE if the current location is not the default location (redirected). + hr = CSTR_EQUAL == nCompare ? S_OK : S_FALSE; + } + else + { + if (!vsczDefaultUserPackageCache) + { + hr = PathGetKnownFolder(CSIDL_LOCAL_APPDATA, &sczAppData); + ExitOnFailure(hr, "Failed to find local %hs appdata directory.", "per-user"); + + hr = PathConcat(sczAppData, PACKAGE_CACHE_FOLDER_NAME, &vsczDefaultUserPackageCache); + ExitOnFailure(hr, "Failed to construct %hs package cache directory name.", "per-user"); + + hr = PathBackslashTerminate(&vsczDefaultUserPackageCache); + ExitOnFailure(hr, "Failed to backslash terminate default %hs package cache directory name.", "per-user"); + } + + hr = StrAllocString(psczRootPath, vsczDefaultUserPackageCache, 0); + ExitOnFailure(hr, "Failed to copy %hs package cache root directory.", "per-user"); + } + +LExit: + ReleaseStr(sczAppData); + + return hr; +} + +static HRESULT GetLastUsedSourceFolder( + __in BURN_VARIABLES* pVariables, + __out_z LPWSTR* psczLastSource + ) +{ + HRESULT hr = S_OK; + + hr = VariableGetString(pVariables, BURN_BUNDLE_LAST_USED_SOURCE, psczLastSource); + if (E_NOTFOUND == hr) + { + // Try the original source folder. + hr = VariableGetString(pVariables, BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER, psczLastSource); + } + + return hr; +} + +static HRESULT CreateCompletedPath( + __in BOOL fPerMachine, + __in LPCWSTR wzId, + __out LPWSTR* psczCacheDirectory + ) +{ + static BOOL fPerMachineCacheRootVerified = FALSE; + + HRESULT hr = S_OK; + LPWSTR sczCacheDirectory = NULL; + + // If we are doing a permachine install but have not yet verified that the root cache folder + // was created with the correct ACLs yet, do that now. + if (fPerMachine && !fPerMachineCacheRootVerified) + { + hr = GetRootPath(fPerMachine, TRUE, &sczCacheDirectory); + ExitOnFailure(hr, "Failed to get cache directory."); + + hr = DirEnsureExists(sczCacheDirectory, NULL); + ExitOnFailure(hr, "Failed to create cache directory: %ls", sczCacheDirectory); + + hr = SecurePath(sczCacheDirectory); + ExitOnFailure(hr, "Failed to secure cache directory: %ls", sczCacheDirectory); + + fPerMachineCacheRootVerified = TRUE; + } + + // Get the cache completed path, ensure it exists, and reset any permissions people + // might have tried to set on the directory so we inherit the (correct!) security + // permissions from the parent directory. + hr = CacheGetCompletedPath(fPerMachine, wzId, &sczCacheDirectory); + ExitOnFailure(hr, "Failed to get cache directory."); + + hr = DirEnsureExists(sczCacheDirectory, NULL); + ExitOnFailure(hr, "Failed to create cache directory: %ls", sczCacheDirectory); + + ResetPathPermissions(fPerMachine, sczCacheDirectory); + + *psczCacheDirectory = sczCacheDirectory; + sczCacheDirectory = NULL; + +LExit: + ReleaseStr(sczCacheDirectory); + return hr; +} + +static HRESULT CreateUnverifiedPath( + __in BOOL fPerMachine, + __in_z LPCWSTR wzPayloadId, + __out_z LPWSTR* psczUnverifiedPayloadPath + ) +{ + static BOOL fUnverifiedCacheFolderCreated = FALSE; + + HRESULT hr = S_OK; + LPWSTR sczUnverifiedCacheFolder = NULL; + + hr = CacheGetCompletedPath(fPerMachine, UNVERIFIED_CACHE_FOLDER_NAME, &sczUnverifiedCacheFolder); + ExitOnFailure(hr, "Failed to get cache directory."); + + if (!fUnverifiedCacheFolderCreated) + { + hr = DirEnsureExists(sczUnverifiedCacheFolder, NULL); + ExitOnFailure(hr, "Failed to create unverified cache directory: %ls", sczUnverifiedCacheFolder); + + ResetPathPermissions(fPerMachine, sczUnverifiedCacheFolder); + } + + hr = PathConcat(sczUnverifiedCacheFolder, wzPayloadId, psczUnverifiedPayloadPath); + ExitOnFailure(hr, "Failed to concat payload id to unverified folder path."); + +LExit: + ReleaseStr(sczUnverifiedCacheFolder); + + return hr; +} + +static HRESULT VerifyThenTransferContainer( + __in BURN_CONTAINER* pContainer, + __in_z LPCWSTR wzCachedPath, + __in_z LPCWSTR wzUnverifiedContainerPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + HANDLE hFile = INVALID_HANDLE_VALUE; + + // Get the container on disk actual hash. + hFile = ::CreateFileW(wzUnverifiedContainerPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (INVALID_HANDLE_VALUE == hFile) + { + ExitWithLastError(hr, "Failed to open container in working path: %ls", wzUnverifiedContainerPath); + } + + // Container should have a hash we can use to verify with. + if (pContainer->pbHash) + { + hr = VerifyHash(pContainer->pbHash, pContainer->cbHash, pContainer->qwFileSize, wzUnverifiedContainerPath, hFile, BURN_CACHE_STEP_HASH, pfnCacheMessageHandler, pfnProgress, pContext); + ExitOnFailure(hr, "Failed to verify container hash: %ls", wzCachedPath); + } + + LogStringLine(REPORT_STANDARD, "%ls container from working path '%ls' to path '%ls'", fMove ? L"Moving" : L"Copying", wzUnverifiedContainerPath, wzCachedPath); + + hr = CacheTransferFileWithRetry(wzUnverifiedContainerPath, wzCachedPath, fMove, BURN_CACHE_STEP_FINALIZE, pContainer->qwFileSize, pfnCacheMessageHandler, pfnProgress, pContext); + +LExit: + ReleaseFileHandle(hFile); + + return hr; +} + +static HRESULT VerifyThenTransferPayload( + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzCachedPath, + __in_z LPCWSTR wzUnverifiedPayloadPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + HANDLE hFile = INVALID_HANDLE_VALUE; + + // Get the payload on disk actual hash. + hFile = ::CreateFileW(wzUnverifiedPayloadPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (INVALID_HANDLE_VALUE == hFile) + { + ExitWithLastError(hr, "Failed to open payload in working path: %ls", wzUnverifiedPayloadPath); + } + + if (pPayload->pbHash) // the payload should have a hash we can use to verify it. + { + hr = VerifyHash(pPayload->pbHash, pPayload->cbHash, pPayload->qwFileSize, wzUnverifiedPayloadPath, hFile, BURN_CACHE_STEP_HASH, pfnCacheMessageHandler, pfnProgress, pContext); + ExitOnFailure(hr, "Failed to verify payload hash: %ls", wzCachedPath); + } + + LogStringLine(REPORT_STANDARD, "%ls payload from working path '%ls' to path '%ls'", fMove ? L"Moving" : L"Copying", wzUnverifiedPayloadPath, wzCachedPath); + + hr = CacheTransferFileWithRetry(wzUnverifiedPayloadPath, wzCachedPath, fMove, BURN_CACHE_STEP_FINALIZE, pPayload->qwFileSize, pfnCacheMessageHandler, pfnProgress, pContext); + +LExit: + ReleaseFileHandle(hFile); + + return hr; +} + +static HRESULT CacheTransferFileWithRetry( + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzDestinationPath, + __in BOOL fMove, + __in BURN_CACHE_STEP cacheStep, + __in DWORD64 qwFileSize, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE /*pfnProgress*/, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + + hr = SendCacheBeginMessage(pfnCacheMessageHandler, pContext, cacheStep); + ExitOnFailure(hr, "Aborted cache file transfer begin."); + + // TODO: send progress during the file transfer. + if (fMove) + { + hr = FileEnsureMoveWithRetry(wzSourcePath, wzDestinationPath, TRUE, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT); + ExitOnFailure(hr, "Failed to move %ls to %ls", wzSourcePath, wzDestinationPath); + } + else + { + hr = FileEnsureCopyWithRetry(wzSourcePath, wzDestinationPath, TRUE, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT); + ExitOnFailure(hr, "Failed to copy %ls to %ls", wzSourcePath, wzDestinationPath); + } + + hr = SendCacheSuccessMessage(pfnCacheMessageHandler, pContext, qwFileSize); + +LExit: + SendCacheCompleteMessage(pfnCacheMessageHandler, pContext, hr); + + return hr; +} + +static HRESULT VerifyFileAgainstContainer( + __in BURN_CONTAINER* pContainer, + __in_z LPCWSTR wzVerifyPath, + __in BOOL fAlreadyCached, + __in BURN_CACHE_STEP cacheStep, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + HANDLE hFile = INVALID_HANDLE_VALUE; + + // Get the container on disk actual hash. + hFile = ::CreateFileW(wzVerifyPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (INVALID_HANDLE_VALUE == hFile) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + if (E_PATHNOTFOUND == hr || E_FILENOTFOUND == hr) + { + ExitFunction(); // do not log error when the file was not found. + } + ExitOnRootFailure(hr, "Failed to open container at path: %ls", wzVerifyPath); + } + + if (pContainer->pbHash) // the container should have a hash we can use to verify it. + { + hr = VerifyHash(pContainer->pbHash, pContainer->cbHash, pContainer->qwFileSize, wzVerifyPath, hFile, cacheStep, pfnCacheMessageHandler, pfnProgress, pContext); + ExitOnFailure(hr, "Failed to verify hash of container: %ls", pContainer->sczId); + } + + if (fAlreadyCached) + { + LogId(REPORT_STANDARD, MSG_VERIFIED_EXISTING_CONTAINER, pContainer->sczId, wzVerifyPath); + ::DecryptFileW(wzVerifyPath, 0); // Let's try to make sure it's not encrypted. + } + +LExit: + ReleaseFileHandle(hFile); + + if (FAILED(hr) && E_PATHNOTFOUND != hr && E_FILENOTFOUND != hr) + { + if (fAlreadyCached) + { + LogErrorId(hr, MSG_FAILED_VERIFY_CONTAINER, pContainer->sczId, wzVerifyPath, NULL); + } + + FileEnsureDelete(wzVerifyPath); // if the file existed but did not verify correctly, make it go away. + } + + return hr; +} + +static HRESULT VerifyFileAgainstPayload( + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzVerifyPath, + __in BOOL fAlreadyCached, + __in BURN_CACHE_STEP cacheStep, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + HANDLE hFile = INVALID_HANDLE_VALUE; + + // Get the payload on disk actual hash. + hFile = ::CreateFileW(wzVerifyPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (INVALID_HANDLE_VALUE == hFile) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + if (E_PATHNOTFOUND == hr || E_FILENOTFOUND == hr) + { + ExitFunction(); // do not log error when the file was not found. + } + ExitOnRootFailure(hr, "Failed to open payload at path: %ls", wzVerifyPath); + } + + if (pPayload->pbHash) // the payload should have a hash we can use to verify it. + { + hr = VerifyHash(pPayload->pbHash, pPayload->cbHash, pPayload->qwFileSize, wzVerifyPath, hFile, cacheStep, pfnCacheMessageHandler, pfnProgress, pContext); + ExitOnFailure(hr, "Failed to verify hash of payload: %ls", pPayload->sczKey); + } + + if (fAlreadyCached) + { + LogId(REPORT_STANDARD, MSG_VERIFIED_EXISTING_PAYLOAD, pPayload->sczKey, wzVerifyPath); + ::DecryptFileW(wzVerifyPath, 0); // Let's try to make sure it's not encrypted. + } + +LExit: + ReleaseFileHandle(hFile); + + if (FAILED(hr) && E_PATHNOTFOUND != hr && E_FILENOTFOUND != hr) + { + if (fAlreadyCached) + { + LogErrorId(hr, MSG_FAILED_VERIFY_PAYLOAD, pPayload->sczKey, wzVerifyPath, NULL); + } + + FileEnsureDelete(wzVerifyPath); // if the file existed but did not verify correctly, make it go away. + } + + return hr; +} + +static HRESULT AllocateSid( + __in WELL_KNOWN_SID_TYPE type, + __out PSID* ppSid + ) +{ + HRESULT hr = S_OK; + PSID pAllocSid = NULL; + DWORD cbSid = SECURITY_MAX_SID_SIZE; + + pAllocSid = static_cast(MemAlloc(cbSid, TRUE)); + ExitOnNull(pAllocSid, hr, E_OUTOFMEMORY, "Failed to allocate memory for well known SID."); + + if (!::CreateWellKnownSid(type, NULL, pAllocSid, &cbSid)) + { + ExitWithLastError(hr, "Failed to create well known SID."); + } + + *ppSid = pAllocSid; + pAllocSid = NULL; + +LExit: + ReleaseMem(pAllocSid); + return hr; +} + + +static HRESULT ResetPathPermissions( + __in BOOL fPerMachine, + __in_z LPCWSTR wzPath + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + DWORD dwSetSecurity = DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION; + ACL acl = { }; + PSID pSid = NULL; + + if (fPerMachine) + { + hr = AllocateSid(WinBuiltinAdministratorsSid, &pSid); + ExitOnFailure(hr, "Failed to allocate administrator SID."); + + // Create an empty (not NULL!) ACL to reset the permissions on the file to purely inherit from parent. + if (!::InitializeAcl(&acl, sizeof(acl), ACL_REVISION)) + { + ExitWithLastError(hr, "Failed to initialize ACL."); + } + + dwSetSecurity |= OWNER_SECURITY_INFORMATION; + } + + hr = AclSetSecurityWithRetry(wzPath, SE_FILE_OBJECT, dwSetSecurity, pSid, NULL, &acl, NULL, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT); + ExitOnWin32Error(er, hr, "Failed to reset the ACL on cached file: %ls", wzPath); + + ::SetFileAttributesW(wzPath, FILE_ATTRIBUTE_NORMAL); // Let's try to reset any possible read-only/system bits. + +LExit: + ReleaseMem(pSid); + return hr; +} + + +static HRESULT GrantAccessAndAllocateSid( + __in WELL_KNOWN_SID_TYPE type, + __in DWORD dwGrantAccess, + __in EXPLICIT_ACCESS* pAccess + ) +{ + HRESULT hr = S_OK; + + hr = AllocateSid(type, reinterpret_cast(&pAccess->Trustee.ptstrName)); + ExitOnFailure(hr, "Failed to allocate SID to grate access."); + + pAccess->grfAccessMode = GRANT_ACCESS; + pAccess->grfAccessPermissions = dwGrantAccess; + pAccess->grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + pAccess->Trustee.TrusteeForm = TRUSTEE_IS_SID; + pAccess->Trustee.TrusteeType = TRUSTEE_IS_GROUP; + +LExit: + return hr; +} + + +static HRESULT SecurePath( + __in LPCWSTR wzPath + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + EXPLICIT_ACCESSW access[4] = { }; + PACL pAcl = NULL; + + // Administrators must be the first one in the array so we can reuse the allocated SID below. + hr = GrantAccessAndAllocateSid(WinBuiltinAdministratorsSid, FILE_ALL_ACCESS, &access[0]); + ExitOnFailure(hr, "Failed to allocate access for Administrators group to path: %ls", wzPath); + + hr = GrantAccessAndAllocateSid(WinLocalSystemSid, FILE_ALL_ACCESS, &access[1]); + ExitOnFailure(hr, "Failed to allocate access for SYSTEM group to path: %ls", wzPath); + + hr = GrantAccessAndAllocateSid(WinWorldSid, GENERIC_READ | GENERIC_EXECUTE, &access[2]); + ExitOnFailure(hr, "Failed to allocate access for Everyone group to path: %ls", wzPath); + + hr = GrantAccessAndAllocateSid(WinBuiltinUsersSid, GENERIC_READ | GENERIC_EXECUTE, &access[3]); + ExitOnFailure(hr, "Failed to allocate access for Users group to path: %ls", wzPath); + + er = ::SetEntriesInAclW(countof(access), access, NULL, &pAcl); + ExitOnWin32Error(er, hr, "Failed to create ACL to secure cache path: %ls", wzPath); + + // Set the ACL and ensure the Administrators group ends up the owner + hr = AclSetSecurityWithRetry(wzPath, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, + reinterpret_cast(access[0].Trustee.ptstrName), NULL, pAcl, NULL, FILE_OPERATION_RETRY_COUNT, FILE_OPERATION_RETRY_WAIT); + ExitOnFailure(hr, "Failed to secure cache path: %ls", wzPath); + +LExit: + if (pAcl) + { + ::LocalFree(pAcl); + } + + for (DWORD i = 0; i < countof(access); ++i) + { + ReleaseMem(access[i].Trustee.ptstrName); + } + + return hr; +} + + +static HRESULT CopyEngineToWorkingFolder( + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzWorkingFolderName, + __in_z LPCWSTR wzExecutableName, + __in BURN_SECTION* pSection, + __deref_out_z_opt LPWSTR* psczEngineWorkingPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczWorkingFolder = NULL; + LPWSTR sczTargetDirectory = NULL; + LPWSTR sczTargetPath = NULL; + LPWSTR sczSourceDirectory = NULL; + LPWSTR sczPayloadSourcePath = NULL; + LPWSTR sczPayloadTargetPath = NULL; + + hr = CacheEnsureWorkingFolder(NULL, &sczWorkingFolder); + ExitOnFailure(hr, "Failed to create working path to copy engine."); + + hr = PathConcat(sczWorkingFolder, wzWorkingFolderName, &sczTargetDirectory); + ExitOnFailure(hr, "Failed to calculate the bundle working folder target name."); + + hr = DirEnsureExists(sczTargetDirectory, NULL); + ExitOnFailure(hr, "Failed create bundle working folder."); + + hr = PathConcat(sczTargetDirectory, wzExecutableName, &sczTargetPath); + ExitOnFailure(hr, "Failed to combine working path with engine file name."); + + // Copy the engine without any attached containers to the working path. + hr = CopyEngineWithSignatureFixup(pSection->hEngineFile, wzSourcePath, sczTargetPath, pSection); + ExitOnFailure(hr, "Failed to copy engine: '%ls' to working path: %ls", wzSourcePath, sczTargetPath); + + if (psczEngineWorkingPath) + { + hr = StrAllocString(psczEngineWorkingPath, sczTargetPath, 0); + ExitOnFailure(hr, "Failed to copy target path for engine working path."); + } + +LExit: + ReleaseStr(sczPayloadTargetPath); + ReleaseStr(sczPayloadSourcePath); + ReleaseStr(sczSourceDirectory); + ReleaseStr(sczTargetPath); + ReleaseStr(sczTargetDirectory); + ReleaseStr(sczWorkingFolder); + + return hr; +} + + +static HRESULT CopyEngineWithSignatureFixup( + __in HANDLE hEngineFile, + __in_z LPCWSTR wzEnginePath, + __in_z LPCWSTR wzTargetPath, + __in BURN_SECTION* pSection + ) +{ + HRESULT hr = S_OK; + HANDLE hTarget = INVALID_HANDLE_VALUE; + LARGE_INTEGER li = { }; + DWORD dwZeroOriginals[3] = { }; + + hTarget = ::CreateFileW(wzTargetPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (INVALID_HANDLE_VALUE == hTarget) + { + ExitWithLastError(hr, "Failed to create engine file at path: %ls", wzTargetPath); + } + + hr = FileSetPointer(hEngineFile, 0, NULL, FILE_BEGIN); + ExitOnFailure(hr, "Failed to seek to beginning of engine file: %ls", wzEnginePath); + + hr = FileCopyUsingHandles(hEngineFile, hTarget, pSection->cbEngineSize, NULL); + ExitOnFailure(hr, "Failed to copy engine from: %ls to: %ls", wzEnginePath, wzTargetPath); + + // If the original executable was signed, let's put back the checksum and signature. + if (pSection->dwOriginalSignatureOffset) + { + // Fix up the checksum. + li.QuadPart = pSection->dwChecksumOffset; + if (!::SetFilePointerEx(hTarget, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to seek to checksum in exe header."); + } + + hr = FileWriteHandle(hTarget, reinterpret_cast(&pSection->dwOriginalChecksum), sizeof(pSection->dwOriginalChecksum)); + ExitOnFailure(hr, "Failed to update signature offset."); + + // Fix up the signature information. + li.QuadPart = pSection->dwCertificateTableOffset; + if (!::SetFilePointerEx(hTarget, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to seek to signature table in exe header."); + } + + hr = FileWriteHandle(hTarget, reinterpret_cast(&pSection->dwOriginalSignatureOffset), sizeof(pSection->dwOriginalSignatureOffset)); + ExitOnFailure(hr, "Failed to update signature offset."); + + hr = FileWriteHandle(hTarget, reinterpret_cast(&pSection->dwOriginalSignatureSize), sizeof(pSection->dwOriginalSignatureSize)); + ExitOnFailure(hr, "Failed to update signature offset."); + + // Zero out the original information since that is how it was when the file was originally signed. + li.QuadPart = pSection->dwOriginalChecksumAndSignatureOffset; + if (!::SetFilePointerEx(hTarget, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to seek to original data in exe burn section header."); + } + + hr = FileWriteHandle(hTarget, reinterpret_cast(&dwZeroOriginals), sizeof(dwZeroOriginals)); + ExitOnFailure(hr, "Failed to zero out original data offset."); + } + +LExit: + ReleaseFileHandle(hTarget); + + return hr; +} + + +static HRESULT RemoveBundleOrPackage( + __in BOOL fBundle, + __in BOOL fPerMachine, + __in_z LPCWSTR wzBundleOrPackageId, + __in_z LPCWSTR wzCacheId + ) +{ + HRESULT hr = S_OK; + LPWSTR sczRootCacheDirectory = NULL; + LPWSTR sczDirectory = NULL; + + hr = CacheGetCompletedPath(fPerMachine, wzCacheId, &sczDirectory); + ExitOnFailure(hr, "Failed to calculate cache path."); + + LogId(REPORT_STANDARD, fBundle ? MSG_UNCACHE_BUNDLE : MSG_UNCACHE_PACKAGE, wzBundleOrPackageId, sczDirectory); + + // Try really hard to remove the cache directory. + hr = E_FAIL; + for (DWORD iRetry = 0; FAILED(hr) && iRetry < FILE_OPERATION_RETRY_COUNT; ++iRetry) + { + if (0 < iRetry) + { + ::Sleep(FILE_OPERATION_RETRY_WAIT); + } + + hr = DirEnsureDeleteEx(sczDirectory, DIR_DELETE_FILES | DIR_DELETE_RECURSE | DIR_DELETE_SCHEDULE); + if (E_PATHNOTFOUND == hr) + { + break; + } + } + + if (E_PATHNOTFOUND != hr && FAILED(hr)) + { + LogId(REPORT_STANDARD, fBundle ? MSG_UNABLE_UNCACHE_BUNDLE : MSG_UNABLE_UNCACHE_PACKAGE, wzBundleOrPackageId, sczDirectory, hr); + hr = S_OK; + } + else + { + // Try to remove root package cache in the off chance it is now empty. + hr = GetRootPath(fPerMachine, TRUE, &sczRootCacheDirectory); + ExitOnFailure(hr, "Failed to get %hs package cache root directory.", fPerMachine ? "per-machine" : "per-user"); + DirEnsureDeleteEx(sczRootCacheDirectory, DIR_DELETE_SCHEDULE); + + // GetRootPath returns S_FALSE if the package cache is redirected elsewhere. + if (S_FALSE == hr) + { + hr = GetRootPath(fPerMachine, FALSE, &sczRootCacheDirectory); + ExitOnFailure(hr, "Failed to get old %hs package cache root directory.", fPerMachine ? "per-machine" : "per-user"); + DirEnsureDeleteEx(sczRootCacheDirectory, DIR_DELETE_SCHEDULE); + } + } + +LExit: + ReleaseStr(sczDirectory); + ReleaseStr(sczRootCacheDirectory); + + return hr; +} + +static HRESULT VerifyHash( + __in BYTE* pbHash, + __in DWORD cbHash, + __in DWORD64 qwFileSize, + __in_z LPCWSTR wzUnverifiedPayloadPath, + __in HANDLE hFile, + __in BURN_CACHE_STEP cacheStep, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE /*pfnProgress*/, + __in LPVOID pContext + ) +{ + UNREFERENCED_PARAMETER(wzUnverifiedPayloadPath); + + HRESULT hr = S_OK; + BYTE rgbActualHash[SHA512_HASH_LEN] = { }; + DWORD64 qwHashedBytes = 0; + LONGLONG llSize = 0; + LPWSTR pszExpected = NULL; + LPWSTR pszActual = NULL; + + hr = SendCacheBeginMessage(pfnCacheMessageHandler, pContext, cacheStep); + ExitOnFailure(hr, "Aborted cache verify hash begin."); + + hr = FileSizeByHandle(hFile, &llSize); + ExitOnFailure(hr, "Failed to get file size for path: %ls", wzUnverifiedPayloadPath); + + if (static_cast(llSize) != qwFileSize) + { + ExitOnFailure(hr = ERROR_FILE_CORRUPT, "File size mismatch for path: %ls, expected: %llu, actual: %lld", wzUnverifiedPayloadPath, qwFileSize, llSize); + } + + // TODO: create a cryp hash file that sends progress. + hr = CrypHashFileHandle(hFile, PROV_RSA_AES, CALG_SHA_512, rgbActualHash, sizeof(rgbActualHash), &qwHashedBytes); + ExitOnFailure(hr, "Failed to calculate hash for path: %ls", wzUnverifiedPayloadPath); + + // Compare hashes. + if (cbHash != sizeof(rgbActualHash) || 0 != memcmp(pbHash, rgbActualHash, sizeof(rgbActualHash))) + { + hr = CRYPT_E_HASH_VALUE; + + // Best effort to log the expected and actual hash value strings. + if (SUCCEEDED(StrAllocHexEncode(pbHash, cbHash, &pszExpected)) && + SUCCEEDED(StrAllocHexEncode(rgbActualHash, sizeof(rgbActualHash), &pszActual))) + { + ExitOnFailure(hr, "Hash mismatch for path: %ls, expected: %ls, actual: %ls", wzUnverifiedPayloadPath, pszExpected, pszActual); + } + else + { + ExitOnFailure(hr, "Hash mismatch for path: %ls", wzUnverifiedPayloadPath); + } + } + + hr = SendCacheSuccessMessage(pfnCacheMessageHandler, pContext, qwFileSize); + +LExit: + SendCacheCompleteMessage(pfnCacheMessageHandler, pContext, hr); + + ReleaseStr(pszActual); + ReleaseStr(pszExpected); + + return hr; +} + +static HRESULT SendCacheBeginMessage( + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPVOID pContext, + __in BURN_CACHE_STEP cacheStep + ) +{ + HRESULT hr = S_OK; + BURN_CACHE_MESSAGE message = { }; + + message.type = BURN_CACHE_MESSAGE_BEGIN; + message.begin.cacheStep = cacheStep; + + hr = pfnCacheMessageHandler(&message, pContext); + + return hr; +} + +static HRESULT SendCacheSuccessMessage( + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPVOID pContext, + __in DWORD64 qwFileSize + ) +{ + HRESULT hr = S_OK; + BURN_CACHE_MESSAGE message = { }; + + message.type = BURN_CACHE_MESSAGE_SUCCESS; + message.success.qwFileSize = qwFileSize; + + hr = pfnCacheMessageHandler(&message, pContext); + + return hr; +} + +static HRESULT SendCacheCompleteMessage( + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPVOID pContext, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BURN_CACHE_MESSAGE message = { }; + + message.type = BURN_CACHE_MESSAGE_COMPLETE; + message.complete.hrStatus = hrStatus; + + hr = pfnCacheMessageHandler(&message, pContext); + + return hr; +} diff --git a/src/burn/engine/cache.h b/src/burn/engine/cache.h new file mode 100644 index 00000000..0152d33b --- /dev/null +++ b/src/burn/engine/cache.h @@ -0,0 +1,216 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#define BURN_CACHE_MAX_SEARCH_PATHS 7 + +#ifdef __cplusplus +extern "C" { +#endif + + +enum BURN_CACHE_MESSAGE_TYPE +{ + BURN_CACHE_MESSAGE_BEGIN, + BURN_CACHE_MESSAGE_SUCCESS, + BURN_CACHE_MESSAGE_COMPLETE, +}; + +enum BURN_CACHE_STEP +{ + BURN_CACHE_STEP_HASH_TO_SKIP_ACQUIRE, + BURN_CACHE_STEP_HASH_TO_SKIP_VERIFY, + BURN_CACHE_STEP_STAGE, + BURN_CACHE_STEP_HASH, + BURN_CACHE_STEP_FINALIZE, +}; + +typedef struct _BURN_CACHE_MESSAGE +{ + BURN_CACHE_MESSAGE_TYPE type; + + union + { + struct + { + BURN_CACHE_STEP cacheStep; + } begin; + struct + { + DWORD64 qwFileSize; + } success; + struct + { + HRESULT hrStatus; + } complete; + }; +} BURN_CACHE_MESSAGE; + +typedef HRESULT(CALLBACK* PFN_BURNCACHEMESSAGEHANDLER)( + __in BURN_CACHE_MESSAGE* pMessage, + __in LPVOID pvContext + ); + +// functions + +HRESULT CacheInitialize( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in_z_opt LPCWSTR wzSourceProcessPath + ); +HRESULT CacheEnsureWorkingFolder( + __in_z_opt LPCWSTR wzBundleId, + __deref_out_z_opt LPWSTR* psczWorkingFolder + ); +HRESULT CacheCalculateBundleWorkingPath( + __in_z LPCWSTR wzBundleId, + __in LPCWSTR wzExecutableName, + __deref_out_z LPWSTR* psczWorkingPath + ); +HRESULT CacheCalculateBundleLayoutWorkingPath( + __in_z LPCWSTR wzBundleId, + __deref_out_z LPWSTR* psczWorkingPath + ); +HRESULT CacheCalculatePayloadWorkingPath( + __in_z LPCWSTR wzBundleId, + __in BURN_PAYLOAD* pPayload, + __deref_out_z LPWSTR* psczWorkingPath + ); +HRESULT CacheCalculateContainerWorkingPath( + __in_z LPCWSTR wzBundleId, + __in BURN_CONTAINER* pContainer, + __deref_out_z LPWSTR* psczWorkingPath + ); +HRESULT CacheGetRootCompletedPath( + __in BOOL fPerMachine, + __in BOOL fForceInitialize, + __deref_out_z LPWSTR* psczRootCompletedPath + ); +HRESULT CacheGetCompletedPath( + __in BOOL fPerMachine, + __in_z LPCWSTR wzCacheId, + __deref_out_z LPWSTR* psczCompletedPath + ); +HRESULT CacheGetResumePath( + __in_z LPCWSTR wzPayloadWorkingPath, + __deref_out_z LPWSTR* psczResumePath + ); +HRESULT CacheGetLocalSourcePaths( + __in_z LPCWSTR wzRelativePath, + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzDestinationPath, + __in_z_opt LPCWSTR wzLayoutDirectory, + __in BURN_VARIABLES* pVariables, + __inout LPWSTR** prgSearchPaths, + __out DWORD* pcSearchPaths, + __out DWORD* pdwLikelySearchPath, + __out DWORD* pdwDestinationSearchPath + ); +HRESULT CacheSetLastUsedSource( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzSourcePath, + __in_z LPCWSTR wzRelativePath + ); +HRESULT CacheSendProgressCallback( + __in DOWNLOAD_CACHE_CALLBACK* pCallback, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in HANDLE hDestinationFile + ); +void CacheSendErrorCallback( + __in DOWNLOAD_CACHE_CALLBACK* pCallback, + __in HRESULT hrError, + __in_z_opt LPCWSTR wzError, + __out_opt BOOL* pfRetry + ); +BOOL CacheBundleRunningFromCache(); +HRESULT CacheBundleToCleanRoom( + __in BURN_SECTION* pSection, + __deref_out_z_opt LPWSTR* psczCleanRoomBundlePath + ); +HRESULT CacheBundleToWorkingDirectory( + __in_z LPCWSTR wzBundleId, + __in_z LPCWSTR wzExecutableName, + __in BURN_SECTION* pSection, + __deref_out_z_opt LPWSTR* psczEngineWorkingPath + ); +HRESULT CacheLayoutBundle( + __in_z LPCWSTR wzExecutableName, + __in_z LPCWSTR wzLayoutDirectory, + __in_z LPCWSTR wzSourceBundlePath, + __in DWORD64 qwBundleSize, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +HRESULT CacheCompleteBundle( + __in BOOL fPerMachine, + __in_z LPCWSTR wzExecutableName, + __in_z LPCWSTR wzBundleId, + __in_z LPCWSTR wzSourceBundlePath +#ifdef DEBUG + , __in_z LPCWSTR wzExecutablePath +#endif + ); +HRESULT CacheLayoutContainer( + __in BURN_CONTAINER* pContainer, + __in_z_opt LPCWSTR wzLayoutDirectory, + __in_z LPCWSTR wzUnverifiedContainerPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +HRESULT CacheLayoutPayload( + __in BURN_PAYLOAD* pPayload, + __in_z_opt LPCWSTR wzLayoutDirectory, + __in_z LPCWSTR wzUnverifiedPayloadPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +HRESULT CacheCompletePayload( + __in BOOL fPerMachine, + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzCacheId, + __in_z LPCWSTR wzUnverifiedPayloadPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +HRESULT CacheVerifyContainer( + __in BURN_CONTAINER* pContainer, + __in_z LPCWSTR wzCachedDirectory, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +HRESULT CacheVerifyPayload( + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzCachedDirectory, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +HRESULT CacheRemoveWorkingFolder( + __in_z_opt LPCWSTR wzBundleId + ); +HRESULT CacheRemoveBundle( + __in BOOL fPerMachine, + __in_z LPCWSTR wzPackageId + ); +HRESULT CacheRemovePackage( + __in BOOL fPerMachine, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzCacheId + ); +void CacheCleanup( + __in BOOL fPerMachine, + __in_z LPCWSTR wzBundleId + ); +void CacheUninitialize(); + +#ifdef __cplusplus +} +#endif diff --git a/src/burn/engine/condition.cpp b/src/burn/engine/condition.cpp new file mode 100644 index 00000000..b7cd7413 --- /dev/null +++ b/src/burn/engine/condition.cpp @@ -0,0 +1,1057 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// +// parse rules +// +// value variable | literal | integer | version +// comparison-operator < | > | <= | >= | = | <> | >< | << | >> +// term value | value comparison-operator value | ( expression ) +// boolean-factor term | NOT term +// boolean-term boolean-factor | boolean-factor AND boolean-term +// expression boolean-term | boolean-term OR expression +// + + +// constants + +#define COMPARISON 0x00010000 +#define INSENSITIVE 0x00020000 + +enum BURN_SYMBOL_TYPE +{ + // terminals + BURN_SYMBOL_TYPE_NONE = 0, + BURN_SYMBOL_TYPE_END = 1, + BURN_SYMBOL_TYPE_OR = 2, // OR + BURN_SYMBOL_TYPE_AND = 3, // AND + BURN_SYMBOL_TYPE_NOT = 4, // NOT + BURN_SYMBOL_TYPE_LT = 5 | COMPARISON, // < + BURN_SYMBOL_TYPE_GT = 6 | COMPARISON, // > + BURN_SYMBOL_TYPE_LE = 7 | COMPARISON, // <= + BURN_SYMBOL_TYPE_GE = 8 | COMPARISON, // >= + BURN_SYMBOL_TYPE_EQ = 9 | COMPARISON, // = + BURN_SYMBOL_TYPE_NE = 10 | COMPARISON, // <> + BURN_SYMBOL_TYPE_BAND = 11 | COMPARISON, // >< + BURN_SYMBOL_TYPE_HIEQ = 12 | COMPARISON, // << + BURN_SYMBOL_TYPE_LOEQ = 13 | COMPARISON, // >> + BURN_SYMBOL_TYPE_LT_I = 5 | COMPARISON | INSENSITIVE, // ~< + BURN_SYMBOL_TYPE_GT_I = 6 | COMPARISON | INSENSITIVE, // ~> + BURN_SYMBOL_TYPE_LE_I = 7 | COMPARISON | INSENSITIVE, // ~<= + BURN_SYMBOL_TYPE_GE_I = 8 | COMPARISON | INSENSITIVE, // ~>= + BURN_SYMBOL_TYPE_EQ_I = 9 | COMPARISON | INSENSITIVE, // ~= + BURN_SYMBOL_TYPE_NE_I = 10 | COMPARISON | INSENSITIVE, // ~<> + BURN_SYMBOL_TYPE_BAND_I = 11 | COMPARISON | INSENSITIVE, // ~>< + BURN_SYMBOL_TYPE_HIEQ_I = 12 | COMPARISON | INSENSITIVE, // ~<< + BURN_SYMBOL_TYPE_LOEQ_I = 13 | COMPARISON | INSENSITIVE, // ~>> + BURN_SYMBOL_TYPE_LPAREN = 14, // ( + BURN_SYMBOL_TYPE_RPAREN = 15, // ) + BURN_SYMBOL_TYPE_NUMBER = 16, + BURN_SYMBOL_TYPE_IDENTIFIER = 17, + BURN_SYMBOL_TYPE_LITERAL = 18, + BURN_SYMBOL_TYPE_VERSION = 19, +}; + + +// structs + +struct BURN_SYMBOL +{ + BURN_SYMBOL_TYPE Type; + DWORD iPosition; + BURN_VARIANT Value; +}; + +struct BURN_CONDITION_PARSE_CONTEXT +{ + BURN_VARIABLES* pVariables; + LPCWSTR wzCondition; + LPCWSTR wzRead; + BURN_SYMBOL NextSymbol; + BOOL fError; +}; + +struct BURN_CONDITION_OPERAND +{ + BOOL fHidden; + BURN_VARIANT Value; +}; + + +// internal function declarations + +static HRESULT ParseExpression( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BOOL* pf + ); +static HRESULT ParseBooleanTerm( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BOOL* pf + ); +static HRESULT ParseBooleanFactor( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BOOL* pf + ); +static HRESULT ParseTerm( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BOOL* pf + ); +static HRESULT ParseOperand( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BURN_CONDITION_OPERAND* pOperand + ); +static HRESULT Expect( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __in BURN_SYMBOL_TYPE symbolType + ); +static HRESULT NextSymbol( + __in BURN_CONDITION_PARSE_CONTEXT* pContext + ); +static HRESULT CompareOperands( + __in BURN_SYMBOL_TYPE comparison, + __in BURN_CONDITION_OPERAND* pLeftOperand, + __in BURN_CONDITION_OPERAND* pRightOperand, + __out BOOL* pfResult + ); +static HRESULT CompareStringValues( + __in BURN_SYMBOL_TYPE comparison, + __in_z LPCWSTR wzLeftOperand, + __in_z LPCWSTR wzRightOperand, + __out BOOL* pfResult + ); +static HRESULT CompareIntegerValues( + __in BURN_SYMBOL_TYPE comparison, + __in LONGLONG llLeftOperand, + __in LONGLONG llRightOperand, + __out BOOL* pfResult + ); +static HRESULT CompareVersionValues( + __in BURN_SYMBOL_TYPE comparison, + __in VERUTIL_VERSION* pLeftOperand, + __in VERUTIL_VERSION* pRightOperand, + __out BOOL* pfResult + ); + + +// function definitions + +extern "C" HRESULT ConditionEvaluate( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzCondition, + __out BOOL* pf + ) +{ + HRESULT hr = S_OK; + BURN_CONDITION_PARSE_CONTEXT context = { }; + BOOL f = FALSE; + + context.pVariables = pVariables; + context.wzCondition = wzCondition; + context.wzRead = wzCondition; + + hr = NextSymbol(&context); + ExitOnFailure(hr, "Failed to read next symbol."); + + hr = ParseExpression(&context, &f); + ExitOnFailure(hr, "Failed to parse expression."); + + hr = Expect(&context, BURN_SYMBOL_TYPE_END); + ExitOnFailure(hr, "Failed to expect end symbol."); + + LogId(REPORT_VERBOSE, MSG_CONDITION_RESULT, wzCondition, LoggingTrueFalseToString(f)); + + *pf = f; + hr = S_OK; + +LExit: + if (context.fError) + { + Assert(FAILED(hr)); + LogErrorId(hr, MSG_FAILED_PARSE_CONDITION, wzCondition, NULL, NULL); + } + + return hr; +} + +extern "C" HRESULT ConditionGlobalCheck( + __in BURN_VARIABLES* pVariables, + __in BURN_CONDITION* pCondition, + __in BOOTSTRAPPER_DISPLAY display, + __in_z LPCWSTR wzBundleName, + __out DWORD *pdwExitCode, + __out BOOL *pfContinueExecution + ) +{ + HRESULT hr = S_OK; + BOOL fSuccess = TRUE; + HRESULT hrError = HRESULT_FROM_WIN32(ERROR_OLD_WIN_VERSION); + + // Only run on Windows Vista SP2 or newer, or Windows Server 2008 SP2 or newer. + if (!::IsWindowsVistaSP2OrGreater()) + { + fSuccess = FALSE; + } + else + { + if (NULL != pCondition->sczConditionString) + { + hr = ConditionEvaluate(pVariables, pCondition->sczConditionString, &fSuccess); + ExitOnFailure(hr, "Failed to evaluate condition: %ls", pCondition->sczConditionString); + } + } + + if (!fSuccess) + { + // Display the error messagebox, as long as we're in an appropriate display mode + hr = SplashScreenDisplayError(display, wzBundleName, hrError); + ExitOnFailure(hr, "Failed to display error dialog"); + + *pdwExitCode = static_cast(hrError); + *pfContinueExecution = FALSE; + } + +LExit: + return hr; +} + +HRESULT ConditionGlobalParseFromXml( + __in BURN_CONDITION* pCondition, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNode* pixnNode = NULL; + BSTR bstrExpression = NULL; + + // select variable nodes + hr = XmlSelectSingleNode(pixnBundle, L"Condition", &pixnNode); + if (S_FALSE == hr) + { + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to select condition node."); + + // @Condition + hr = XmlGetText(pixnNode, &bstrExpression); + ExitOnFailure(hr, "Failed to get Condition inner text."); + + hr = StrAllocString(&pCondition->sczConditionString, bstrExpression, 0); + ExitOnFailure(hr, "Failed to copy condition string from BSTR"); + +LExit: + ReleaseBSTR(bstrExpression); + ReleaseObject(pixnNode); + + return hr; +} + + +// internal function definitions + +static HRESULT ParseExpression( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BOOL* pf + ) +{ + HRESULT hr = S_OK; + BOOL fFirst = FALSE; + BOOL fSecond = FALSE; + + hr = ParseBooleanTerm(pContext, &fFirst); + ExitOnFailure(hr, "Failed to parse boolean-term."); + + if (BURN_SYMBOL_TYPE_OR == pContext->NextSymbol.Type) + { + hr = NextSymbol(pContext); + ExitOnFailure(hr, "Failed to read next symbol."); + + hr = ParseExpression(pContext, &fSecond); + ExitOnFailure(hr, "Failed to parse expression."); + + *pf = fFirst || fSecond; + } + else + { + *pf = fFirst; + } + +LExit: + return hr; +} + +static HRESULT ParseBooleanTerm( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BOOL* pf + ) +{ + HRESULT hr = S_OK; + BOOL fFirst = FALSE; + BOOL fSecond = FALSE; + + hr = ParseBooleanFactor(pContext, &fFirst); + ExitOnFailure(hr, "Failed to parse boolean-factor."); + + if (BURN_SYMBOL_TYPE_AND == pContext->NextSymbol.Type) + { + hr = NextSymbol(pContext); + ExitOnFailure(hr, "Failed to read next symbol."); + + hr = ParseBooleanTerm(pContext, &fSecond); + ExitOnFailure(hr, "Failed to parse boolean-term."); + + *pf = fFirst && fSecond; + } + else + { + *pf = fFirst; + } + +LExit: + return hr; +} + +static HRESULT ParseBooleanFactor( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BOOL* pf + ) +{ + HRESULT hr = S_OK; + BOOL fNot = FALSE; + BOOL f = FALSE; + + if (BURN_SYMBOL_TYPE_NOT == pContext->NextSymbol.Type) + { + hr = NextSymbol(pContext); + ExitOnFailure(hr, "Failed to read next symbol."); + + fNot = TRUE; + } + + hr = ParseTerm(pContext, &f); + ExitOnFailure(hr, "Failed to parse term."); + + *pf = fNot ? !f : f; + +LExit: + return hr; +} + +static HRESULT ParseTerm( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BOOL* pf + ) +{ + HRESULT hr = S_OK; + BURN_CONDITION_OPERAND firstOperand = { }; + BURN_CONDITION_OPERAND secondOperand = { }; + + if (BURN_SYMBOL_TYPE_LPAREN == pContext->NextSymbol.Type) + { + hr = NextSymbol(pContext); + ExitOnFailure(hr, "Failed to read next symbol."); + + hr = ParseExpression(pContext, pf); + ExitOnFailure(hr, "Failed to parse expression."); + + hr = Expect(pContext, BURN_SYMBOL_TYPE_RPAREN); + ExitOnFailure(hr, "Failed to expect right parenthesis."); + + ExitFunction1(hr = S_OK); + } + + hr = ParseOperand(pContext, &firstOperand); + ExitOnFailure(hr, "Failed to parse operand."); + + if (COMPARISON & pContext->NextSymbol.Type) + { + BURN_SYMBOL_TYPE comparison = pContext->NextSymbol.Type; + + hr = NextSymbol(pContext); + ExitOnFailure(hr, "Failed to read next symbol."); + + hr = ParseOperand(pContext, &secondOperand); + ExitOnFailure(hr, "Failed to parse operand."); + + hr = CompareOperands(comparison, &firstOperand, &secondOperand, pf); + ExitOnFailure(hr, "Failed to compare operands."); + } + else + { + LONGLONG llValue = 0; + LPWSTR sczValue = NULL; + VERUTIL_VERSION* pVersion = NULL; + switch (firstOperand.Value.Type) + { + case BURN_VARIANT_TYPE_NONE: + *pf = FALSE; + break; + case BURN_VARIANT_TYPE_STRING: + hr = BVariantGetString(&firstOperand.Value, &sczValue); + if (SUCCEEDED(hr)) + { + *pf = sczValue && *sczValue; + } + StrSecureZeroFreeString(sczValue); + break; + case BURN_VARIANT_TYPE_NUMERIC: + hr = BVariantGetNumeric(&firstOperand.Value, &llValue); + if (SUCCEEDED(hr)) + { + *pf = 0 != llValue; + } + SecureZeroMemory(&llValue, sizeof(llValue)); + break; + case BURN_VARIANT_TYPE_VERSION: + hr = BVariantGetVersionHidden(&firstOperand.Value, firstOperand.fHidden, &pVersion); + if (SUCCEEDED(hr)) + { + *pf = 0 != *pVersion->sczVersion; + } + ReleaseVerutilVersion(pVersion); + break; + default: + ExitFunction1(hr = E_UNEXPECTED); + } + } + +LExit: + BVariantUninitialize(&firstOperand.Value); + BVariantUninitialize(&secondOperand.Value); + return hr; +} + +static HRESULT ParseOperand( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __out BURN_CONDITION_OPERAND* pOperand + ) +{ + HRESULT hr = S_OK; + LPWSTR sczFormatted = NULL; + + switch (pContext->NextSymbol.Type) + { + case BURN_SYMBOL_TYPE_IDENTIFIER: + Assert(BURN_VARIANT_TYPE_STRING == pContext->NextSymbol.Value.Type); + + // find variable + hr = VariableGetVariant(pContext->pVariables, pContext->NextSymbol.Value.sczValue, &pOperand->Value); + if (E_NOTFOUND != hr) + { + ExitOnRootFailure(hr, "Failed to find variable."); + + hr = VariableIsHidden(pContext->pVariables, pContext->NextSymbol.Value.sczValue, &pOperand->fHidden); + ExitOnRootFailure(hr, "Failed to get if variable is hidden."); + } + + if (BURN_VARIANT_TYPE_FORMATTED == pOperand->Value.Type) + { + hr = VariableGetFormatted(pContext->pVariables, pContext->NextSymbol.Value.sczValue, &sczFormatted, &pOperand->fHidden); + ExitOnRootFailure(hr, "Failed to format variable '%ls' for condition '%ls'", pContext->NextSymbol.Value.sczValue, pContext->wzCondition); + + hr = BVariantSetString(&pOperand->Value, sczFormatted, 0, FALSE); + ExitOnRootFailure(hr, "Failed to store formatted value for variable '%ls' for condition '%ls'", pContext->NextSymbol.Value.sczValue, pContext->wzCondition); + } + break; + + case BURN_SYMBOL_TYPE_NUMBER: __fallthrough; + case BURN_SYMBOL_TYPE_LITERAL: __fallthrough; + case BURN_SYMBOL_TYPE_VERSION: + pOperand->fHidden = FALSE; + // steal value of symbol + memcpy_s(&pOperand->Value, sizeof(BURN_VARIANT), &pContext->NextSymbol.Value, sizeof(BURN_VARIANT)); + memset(&pContext->NextSymbol.Value, 0, sizeof(BURN_VARIANT)); + break; + + default: + pContext->fError = TRUE; + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Failed to parse condition '%ls' at position: %u", pContext->wzCondition, pContext->NextSymbol.iPosition); + } + + // get next symbol + hr = NextSymbol(pContext); + ExitOnFailure(hr, "Failed to read next symbol."); + +LExit: + StrSecureZeroFreeString(sczFormatted); + + return hr; +} + +// +// Expect - expects a symbol. +// +static HRESULT Expect( + __in BURN_CONDITION_PARSE_CONTEXT* pContext, + __in BURN_SYMBOL_TYPE symbolType + ) +{ + HRESULT hr = S_OK; + + if (pContext->NextSymbol.Type != symbolType) + { + pContext->fError = TRUE; + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Failed to parse condition '%ls' at position: %u", pContext->wzCondition, pContext->NextSymbol.iPosition); + } + + hr = NextSymbol(pContext); + ExitOnFailure(hr, "Failed to read next symbol."); + +LExit: + return hr; +} + +// +// NextSymbol - finds the next symbol in an expression string. +// +static HRESULT NextSymbol( + __in BURN_CONDITION_PARSE_CONTEXT* pContext + ) +{ + HRESULT hr = S_OK; + WORD charType = 0; + ptrdiff_t cchPosition = 0; + DWORD iPosition = 0; + DWORD n = 0; + + // free existing symbol + BVariantUninitialize(&pContext->NextSymbol.Value); + memset(&pContext->NextSymbol, 0, sizeof(BURN_SYMBOL)); + + // skip past blanks + while (L'\0' != pContext->wzRead[0]) + { + ::GetStringTypeW(CT_CTYPE1, pContext->wzRead, 1, &charType); + if (0 == (C1_BLANK & charType)) + { + break; // no blank, done + } + ++pContext->wzRead; + } + + cchPosition = pContext->wzRead - pContext->wzCondition; + if (DWORD_MAX < cchPosition || 0 > cchPosition) + { + ExitOnFailure(hr = E_INVALIDARG, "Symbol was too long: %ls", pContext->wzCondition); + } + iPosition = (DWORD)cchPosition; + + // read depending on first character type + switch (pContext->wzRead[0]) + { + case L'\0': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_END; + break; + case L'~': + switch (pContext->wzRead[1]) + { + case L'=': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_EQ_I; + n = 2; + break; + case L'>': + switch (pContext->wzRead[2]) + { + case '=': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_GE_I; + n = 3; + break; + case L'>': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_LOEQ_I; + n = 3; + break; + case L'<': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_BAND_I; + n = 3; + break; + default: + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_GT_I; + n = 2; + } + break; + case L'<': + switch (pContext->wzRead[2]) + { + case '=': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_LE_I; + n = 3; + break; + case L'<': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_HIEQ_I; + n = 3; + break; + case '>': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_NE_I; + n = 3; + break; + default: + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_LT_I; + n = 2; + } + break; + default: + // error + pContext->fError = TRUE; + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Failed to parse condition \"%ls\". Unexpected '~' operator at position %d.", pContext->wzCondition, iPosition); + } + break; + case L'>': + switch (pContext->wzRead[1]) + { + case L'=': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_GE; + n = 2; + break; + case L'>': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_LOEQ; + n = 2; + break; + case L'<': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_BAND; + n = 2; + break; + default: + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_GT; + n = 1; + } + break; + case L'<': + switch (pContext->wzRead[1]) + { + case L'=': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_LE; + n = 2; + break; + case L'<': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_HIEQ; + n = 2; + break; + case L'>': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_NE; + n = 2; + break; + default: + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_LT; + n = 1; + } + break; + case L'=': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_EQ; + n = 1; + break; + case L'(': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_LPAREN; + n = 1; + break; + case L')': + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_RPAREN; + n = 1; + break; + case L'"': // literal + do + { + ++n; + if (L'\0' == pContext->wzRead[n]) + { + // error + pContext->fError = TRUE; + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Failed to parse condition \"%ls\". Unterminated literal at position %d.", pContext->wzCondition, iPosition); + } + } while (L'"' != pContext->wzRead[n]); + ++n; // terminating '"' + + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_LITERAL; + hr = BVariantSetString(&pContext->NextSymbol.Value, &pContext->wzRead[1], n - 2, FALSE); + ExitOnFailure(hr, "Failed to set symbol value."); + break; + default: + if (C1_DIGIT & charType || L'-' == pContext->wzRead[0]) + { + do + { + ++n; + ::GetStringTypeW(CT_CTYPE1, &pContext->wzRead[n], 1, &charType); + if (C1_ALPHA & charType || L'_' == pContext->wzRead[n]) + { + // error, identifier cannot start with a digit + pContext->fError = TRUE; + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Failed to parse condition \"%ls\". Identifier cannot start at a digit, at position %d.", pContext->wzCondition, iPosition); + } + } while (C1_DIGIT & charType); + + // number + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_NUMBER; + + LONGLONG ll = 0; + hr = StrStringToInt64(pContext->wzRead, n, &ll); + if (FAILED(hr)) + { + pContext->fError = TRUE; + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Failed to parse condition \"%ls\". Constant too big, at position %d.", pContext->wzCondition, iPosition); + } + + hr = BVariantSetNumeric(&pContext->NextSymbol.Value, ll); + ExitOnFailure(hr, "Failed to set symbol value."); + } + else if (C1_ALPHA & charType || L'_' == pContext->wzRead[0]) + { + ::GetStringTypeW(CT_CTYPE1, &pContext->wzRead[1], 1, &charType); + if (L'v' == pContext->wzRead[0] && C1_DIGIT & charType) + { + // version + do + { + ++n; + } while (pContext->wzRead[n] >= L'0' && pContext->wzRead[n] <= L'9' || + pContext->wzRead[n] >= L'A' && pContext->wzRead[n] <= L'Z' || + pContext->wzRead[n] >= L'a' && pContext->wzRead[n] <= L'z' || + pContext->wzRead[n] == L'_' || + pContext->wzRead[n] == L'+' || + pContext->wzRead[n] == L'-' || + pContext->wzRead[n] == L'.'); + + hr = VerParseVersion(&pContext->wzRead[1], n - 1, FALSE, &pContext->NextSymbol.Value.pValue); + if (FAILED(hr)) + { + pContext->fError = TRUE; + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Failed to parse condition \"%ls\". Invalid version format, at position %d.", pContext->wzCondition, iPosition); + } + else if (pContext->NextSymbol.Value.pValue->fInvalid) + { + LogId(REPORT_WARNING, MSG_CONDITION_INVALID_VERSION, pContext->wzCondition, pContext->NextSymbol.Value.pValue->sczVersion); + } + + pContext->NextSymbol.Value.Type = BURN_VARIANT_TYPE_VERSION; + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_VERSION; + } + else + { + do + { + ++n; + ::GetStringTypeW(CT_CTYPE1, &pContext->wzRead[n], 1, &charType); + } while (C1_ALPHA & charType || C1_DIGIT & charType || L'_' == pContext->wzRead[n]); + + if (2 == n && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, pContext->wzRead, 2, L"OR", 2)) + { + // OR + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_OR; + } + else if (3 == n && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, pContext->wzRead, 3, L"AND", 3)) + { + // AND + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_AND; + } + else if (3 == n && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, pContext->wzRead, 3, L"NOT", 3)) + { + // NOT + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_NOT; + } + else + { + // identifier + pContext->NextSymbol.Type = BURN_SYMBOL_TYPE_IDENTIFIER; + hr = BVariantSetString(&pContext->NextSymbol.Value, pContext->wzRead, n, FALSE); + ExitOnFailure(hr, "Failed to set symbol value."); + } + } + } + else + { + // error, unexpected character + pContext->fError = TRUE; + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Failed to parse condition \"%ls\". Unexpected character at position %d.", pContext->wzCondition, iPosition); + } + } + pContext->NextSymbol.iPosition = iPosition; + pContext->wzRead += n; + +LExit: + return hr; +} + +// +// CompareOperands - compares two variant values using a given comparison. +// +static HRESULT CompareOperands( + __in BURN_SYMBOL_TYPE comparison, + __in BURN_CONDITION_OPERAND* pLeftOperand, + __in BURN_CONDITION_OPERAND* pRightOperand, + __out BOOL* pfResult + ) +{ + HRESULT hr = S_OK; + LONGLONG llLeft = 0; + VERUTIL_VERSION* pVersionLeft = 0; + LPWSTR sczLeft = NULL; + LONGLONG llRight = 0; + VERUTIL_VERSION* pVersionRight = 0; + LPWSTR sczRight = NULL; + BURN_VARIANT* pLeftValue = &pLeftOperand->Value; + BURN_VARIANT* pRightValue = &pRightOperand->Value; + + // get values to compare based on type + if (BURN_VARIANT_TYPE_STRING == pLeftValue->Type && BURN_VARIANT_TYPE_STRING == pRightValue->Type) + { + hr = BVariantGetString(pLeftValue, &sczLeft); + ExitOnFailure(hr, "Failed to get the left string"); + hr = BVariantGetString(pRightValue, &sczRight); + ExitOnFailure(hr, "Failed to get the right string"); + hr = CompareStringValues(comparison, sczLeft, sczRight, pfResult); + } + else if (BURN_VARIANT_TYPE_NUMERIC == pLeftValue->Type && BURN_VARIANT_TYPE_NUMERIC == pRightValue->Type) + { + hr = BVariantGetNumeric(pLeftValue, &llLeft); + ExitOnFailure(hr, "Failed to get the left numeric"); + hr = BVariantGetNumeric(pRightValue, &llRight); + ExitOnFailure(hr, "Failed to get the right numeric"); + hr = CompareIntegerValues(comparison, llLeft, llRight, pfResult); + } + else if (BURN_VARIANT_TYPE_VERSION == pLeftValue->Type && BURN_VARIANT_TYPE_VERSION == pRightValue->Type) + { + hr = BVariantGetVersionHidden(pLeftValue, pLeftOperand->fHidden, &pVersionLeft); + ExitOnFailure(hr, "Failed to get the left version"); + hr = BVariantGetVersionHidden(pRightValue, pRightOperand->fHidden, &pVersionRight); + ExitOnFailure(hr, "Failed to get the right version"); + hr = CompareVersionValues(comparison, pVersionLeft, pVersionRight, pfResult); + } + else if (BURN_VARIANT_TYPE_VERSION == pLeftValue->Type && BURN_VARIANT_TYPE_STRING == pRightValue->Type) + { + hr = BVariantGetVersionHidden(pLeftValue, pLeftOperand->fHidden, &pVersionLeft); + ExitOnFailure(hr, "Failed to get the left version"); + hr = BVariantGetVersionHidden(pRightValue, pRightOperand->fHidden, &pVersionRight); + if (FAILED(hr)) + { + if (DISP_E_TYPEMISMATCH != hr) + { + ExitOnFailure(hr, "Failed to get the right version"); + } + *pfResult = (BURN_SYMBOL_TYPE_NE == comparison); + hr = S_OK; + } + else + { + hr = CompareVersionValues(comparison, pVersionLeft, pVersionRight, pfResult); + } + } + else if (BURN_VARIANT_TYPE_STRING == pLeftValue->Type && BURN_VARIANT_TYPE_VERSION == pRightValue->Type) + { + hr = BVariantGetVersionHidden(pRightValue, pRightOperand->fHidden, &pVersionRight); + ExitOnFailure(hr, "Failed to get the right version"); + hr = BVariantGetVersionHidden(pLeftValue, pLeftOperand->fHidden, &pVersionLeft); + if (FAILED(hr)) + { + if (DISP_E_TYPEMISMATCH != hr) + { + ExitOnFailure(hr, "Failed to get the left version"); + } + *pfResult = (BURN_SYMBOL_TYPE_NE == comparison); + hr = S_OK; + } + else + { + hr = CompareVersionValues(comparison, pVersionLeft, pVersionRight, pfResult); + } + } + else if (BURN_VARIANT_TYPE_NUMERIC == pLeftValue->Type && BURN_VARIANT_TYPE_STRING == pRightValue->Type) + { + hr = BVariantGetNumeric(pLeftValue, &llLeft); + ExitOnFailure(hr, "Failed to get the left numeric"); + hr = BVariantGetNumeric(pRightValue, &llRight); + if (FAILED(hr)) + { + if (DISP_E_TYPEMISMATCH != hr) + { + ExitOnFailure(hr, "Failed to get the right numeric"); + } + *pfResult = (BURN_SYMBOL_TYPE_NE == comparison); + hr = S_OK; + } + else + { + hr = CompareIntegerValues(comparison, llLeft, llRight, pfResult); + } + } + else if (BURN_VARIANT_TYPE_STRING == pLeftValue->Type && BURN_VARIANT_TYPE_NUMERIC == pRightValue->Type) + { + hr = BVariantGetNumeric(pRightValue, &llRight); + ExitOnFailure(hr, "Failed to get the right numeric"); + hr = BVariantGetNumeric(pLeftValue, &llLeft); + if (FAILED(hr)) + { + if (DISP_E_TYPEMISMATCH != hr) + { + ExitOnFailure(hr, "Failed to get the left numeric"); + } + *pfResult = (BURN_SYMBOL_TYPE_NE == comparison); + hr = S_OK; + } + else + { + hr = CompareIntegerValues(comparison, llLeft, llRight, pfResult); + } + } + else + { + // not a combination that can be compared + *pfResult = (BURN_SYMBOL_TYPE_NE == comparison || BURN_SYMBOL_TYPE_NE_I == comparison); + } + +LExit: + ReleaseVerutilVersion(pVersionLeft); + SecureZeroMemory(&llLeft, sizeof(LONGLONG)); + StrSecureZeroFreeString(sczLeft); + ReleaseVerutilVersion(pVersionRight); + SecureZeroMemory(&llRight, sizeof(LONGLONG)); + StrSecureZeroFreeString(sczRight); + + return hr; +} + +// +// CompareStringValues - compares two string values using a given comparison. +// +static HRESULT CompareStringValues( + __in BURN_SYMBOL_TYPE comparison, + __in_z LPCWSTR wzLeftOperand, + __in_z LPCWSTR wzRightOperand, + __out BOOL* pfResult + ) +{ + HRESULT hr = S_OK; + DWORD dwCompareString = (comparison & INSENSITIVE) ? NORM_IGNORECASE : 0; + size_t cchLeftSize = 0; + size_t cchRightSize = 0; + int cchLeft = 0; + int cchRight = 0; + + hr = ::StringCchLengthW(wzLeftOperand, STRSAFE_MAX_CCH, &cchLeftSize); + ExitOnRootFailure(hr, "Failed to get length of left string: %ls", wzLeftOperand); + + hr = ::StringCchLengthW(wzRightOperand, STRSAFE_MAX_CCH, &cchRightSize); + ExitOnRootFailure(hr, "Failed to get length of right string: %ls", wzRightOperand); + + cchLeft = static_cast(cchLeftSize); + cchRight = static_cast(cchRightSize); + + switch (comparison) + { + case BURN_SYMBOL_TYPE_LT: + case BURN_SYMBOL_TYPE_GT: + case BURN_SYMBOL_TYPE_LE: + case BURN_SYMBOL_TYPE_GE: + case BURN_SYMBOL_TYPE_EQ: + case BURN_SYMBOL_TYPE_NE: + case BURN_SYMBOL_TYPE_LT_I: + case BURN_SYMBOL_TYPE_GT_I: + case BURN_SYMBOL_TYPE_LE_I: + case BURN_SYMBOL_TYPE_GE_I: + case BURN_SYMBOL_TYPE_EQ_I: + case BURN_SYMBOL_TYPE_NE_I: + { + int i = ::CompareStringW(LOCALE_INVARIANT, dwCompareString, wzLeftOperand, cchLeft, wzRightOperand, cchRight); + hr = CompareIntegerValues(comparison, i, CSTR_EQUAL, pfResult); + } + break; + case BURN_SYMBOL_TYPE_BAND: + case BURN_SYMBOL_TYPE_BAND_I: + // test if left string contains right string + for (int i = 0; (i + cchRight) <= cchLeft; ++i) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, dwCompareString, wzLeftOperand + i, cchRight, wzRightOperand, cchRight)) + { + *pfResult = TRUE; + ExitFunction(); + } + } + *pfResult = FALSE; + break; + case BURN_SYMBOL_TYPE_HIEQ: + case BURN_SYMBOL_TYPE_HIEQ_I: + // test if left string starts with right string + *pfResult = cchLeft >= cchRight && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, dwCompareString, wzLeftOperand, cchRight, wzRightOperand, cchRight); + break; + case BURN_SYMBOL_TYPE_LOEQ: + case BURN_SYMBOL_TYPE_LOEQ_I: + // test if left string ends with right string + *pfResult = cchLeft >= cchRight && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, dwCompareString, wzLeftOperand + (cchLeft - cchRight), cchRight, wzRightOperand, cchRight); + break; + default: + ExitFunction1(hr = E_INVALIDARG); + } + +LExit: + return hr; +} + +// +// CompareIntegerValues - compares two integer values using a given comparison. +// +static HRESULT CompareIntegerValues( + __in BURN_SYMBOL_TYPE comparison, + __in LONGLONG llLeftOperand, + __in LONGLONG llRightOperand, + __out BOOL* pfResult + ) +{ + HRESULT hr = S_OK; + + switch (comparison) + { + case BURN_SYMBOL_TYPE_LT: case BURN_SYMBOL_TYPE_LT_I: *pfResult = llLeftOperand < llRightOperand; break; + case BURN_SYMBOL_TYPE_GT: case BURN_SYMBOL_TYPE_GT_I: *pfResult = llLeftOperand > llRightOperand; break; + case BURN_SYMBOL_TYPE_LE: case BURN_SYMBOL_TYPE_LE_I: *pfResult = llLeftOperand <= llRightOperand; break; + case BURN_SYMBOL_TYPE_GE: case BURN_SYMBOL_TYPE_GE_I: *pfResult = llLeftOperand >= llRightOperand; break; + case BURN_SYMBOL_TYPE_EQ: case BURN_SYMBOL_TYPE_EQ_I: *pfResult = llLeftOperand == llRightOperand; break; + case BURN_SYMBOL_TYPE_NE: case BURN_SYMBOL_TYPE_NE_I: *pfResult = llLeftOperand != llRightOperand; break; + case BURN_SYMBOL_TYPE_BAND: case BURN_SYMBOL_TYPE_BAND_I: *pfResult = (llLeftOperand & llRightOperand) ? TRUE : FALSE; break; + case BURN_SYMBOL_TYPE_HIEQ: case BURN_SYMBOL_TYPE_HIEQ_I: *pfResult = ((llLeftOperand >> 16) & 0xFFFF) == llRightOperand; break; + case BURN_SYMBOL_TYPE_LOEQ: case BURN_SYMBOL_TYPE_LOEQ_I: *pfResult = (llLeftOperand & 0xFFFF) == llRightOperand; break; + default: + ExitFunction1(hr = E_INVALIDARG); + } + +LExit: + return hr; +} + +// +// CompareVersionValues - compares two quad-word version values using a given comparison. +// +static HRESULT CompareVersionValues( + __in BURN_SYMBOL_TYPE comparison, + __in VERUTIL_VERSION* pLeftOperand, + __in VERUTIL_VERSION* pRightOperand, + __out BOOL* pfResult + ) +{ + HRESULT hr = S_OK; + int nResult = 0; + + hr = VerCompareParsedVersions(pLeftOperand, pRightOperand, &nResult); + ExitOnFailure(hr, "Failed to compare condition versions: '%ls', '%ls'", pLeftOperand->sczVersion, pRightOperand->sczVersion); + + switch (comparison) + { + case BURN_SYMBOL_TYPE_LT: *pfResult = nResult < 0; break; + case BURN_SYMBOL_TYPE_GT: *pfResult = nResult > 0; break; + case BURN_SYMBOL_TYPE_LE: *pfResult = nResult <= 0; break; + case BURN_SYMBOL_TYPE_GE: *pfResult = nResult >= 0; break; + case BURN_SYMBOL_TYPE_EQ: *pfResult = nResult == 0; break; + case BURN_SYMBOL_TYPE_NE: *pfResult = nResult != 0; break; + default: + ExitFunction1(hr = E_INVALIDARG); + } + +LExit: + return hr; +} diff --git a/src/burn/engine/condition.h b/src/burn/engine/condition.h new file mode 100644 index 00000000..91627f3c --- /dev/null +++ b/src/burn/engine/condition.h @@ -0,0 +1,39 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +typedef struct _BURN_CONDITION +{ + // The is an expression a condition string to fire the built-in "need newer OS" message + LPWSTR sczConditionString; +} BURN_CONDITION; + + +// function declarations + +HRESULT ConditionEvaluate( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzCondition, + __out BOOL* pf + ); +HRESULT ConditionGlobalCheck( + __in BURN_VARIABLES* pVariables, + __in BURN_CONDITION* pBlock, + __in BOOTSTRAPPER_DISPLAY display, + __in_z LPCWSTR wzBundleName, + __out DWORD *pdwExitCode, + __out BOOL *pfContinueExecution + ); +HRESULT ConditionGlobalParseFromXml( + __in BURN_CONDITION* pBlock, + __in IXMLDOMNode* pixnBundle + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/container.cpp b/src/burn/engine/container.cpp new file mode 100644 index 00000000..0cce3131 --- /dev/null +++ b/src/burn/engine/container.cpp @@ -0,0 +1,398 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// function definitions + +extern "C" HRESULT ContainersParseFromXml( + __in BURN_CONTAINERS* pContainers, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + LPWSTR scz = NULL; + + // select container nodes + hr = XmlSelectNodes(pixnBundle, L"Container", &pixnNodes); + ExitOnFailure(hr, "Failed to select container nodes."); + + // get container node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get container node count."); + + if (!cNodes) + { + ExitFunction(); + } + + // allocate memory for searches + pContainers->rgContainers = (BURN_CONTAINER*)MemAlloc(sizeof(BURN_CONTAINER) * cNodes, TRUE); + ExitOnNull(pContainers->rgContainers, hr, E_OUTOFMEMORY, "Failed to allocate memory for container structs."); + + pContainers->cContainers = cNodes; + + // parse search elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_CONTAINER* pContainer = &pContainers->rgContainers[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // TODO: Read type from manifest. Today only CABINET is supported. + pContainer->type = BURN_CONTAINER_TYPE_CABINET; + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &pContainer->sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + // @Primary + hr = XmlGetYesNoAttribute(pixnNode, L"Primary", &pContainer->fPrimary); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Primary."); + } + + // @Attached + hr = XmlGetYesNoAttribute(pixnNode, L"Attached", &pContainer->fAttached); + if (E_NOTFOUND != hr || pContainer->fPrimary) // if it is a primary container, it has to be attached + { + ExitOnFailure(hr, "Failed to get @Attached."); + } + + // @AttachedIndex + hr = XmlGetAttributeNumber(pixnNode, L"AttachedIndex", &pContainer->dwAttachedIndex); + if (E_NOTFOUND != hr || pContainer->fAttached) // if it is an attached container it must have an index + { + ExitOnFailure(hr, "Failed to get @AttachedIndex."); + } + + // Attached containers are always found attached to the current process, so use the current proccess's + // name instead of what may be in the manifest. + if (pContainer->fAttached) + { + hr = PathForCurrentProcess(&scz, NULL); + ExitOnFailure(hr, "Failed to get path to current process for attached container."); + + LPCWSTR wzFileName = PathFile(scz); + + hr = StrAllocString(&pContainer->sczFilePath, wzFileName, 0); + ExitOnFailure(hr, "Failed to set attached container file path."); + } + else + { + // @FilePath + hr = XmlGetAttributeEx(pixnNode, L"FilePath", &pContainer->sczFilePath); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @FilePath."); + } + } + + // The source path starts as the file path. + hr = StrAllocString(&pContainer->sczSourcePath, pContainer->sczFilePath, 0); + ExitOnFailure(hr, "Failed to copy @FilePath"); + + // @DownloadUrl + hr = XmlGetAttributeEx(pixnNode, L"DownloadUrl", &pContainer->downloadSource.sczUrl); + if (E_NOTFOUND != hr || (!pContainer->fPrimary && !pContainer->sczSourcePath)) // if the package is not a primary package, it must have a source path or a download url + { + ExitOnFailure(hr, "Failed to get @DownloadUrl. Either @SourcePath or @DownloadUrl needs to be provided."); + } + + // @Hash + hr = XmlGetAttributeEx(pixnNode, L"Hash", &pContainer->sczHash); + if (SUCCEEDED(hr)) + { + hr = StrAllocHexDecode(pContainer->sczHash, &pContainer->pbHash, &pContainer->cbHash); + ExitOnFailure(hr, "Failed to hex decode the Container/@Hash."); + } + else if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Hash."); + } + + // prepare next iteration + ReleaseNullObject(pixnNode); + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(scz); + + return hr; +} + +extern "C" HRESULT ContainersInitialize( + __in BURN_CONTAINERS* pContainers, + __in BURN_SECTION* pSection + ) +{ + HRESULT hr = S_OK; + + if (pContainers->rgContainers) + { + for (DWORD i = 0; i < pContainers->cContainers; ++i) + { + BURN_CONTAINER* pContainer = &pContainers->rgContainers[i]; + + // If the container is attached, make sure the information in the section matches what the + // manifest contained and get the offset to the container. + if (pContainer->fAttached) + { + hr = SectionGetAttachedContainerInfo(pSection, pContainer->dwAttachedIndex, pContainer->type, &pContainer->qwAttachedOffset, &pContainer->qwFileSize, &pContainer->fActuallyAttached); + ExitOnFailure(hr, "Failed to get attached container information."); + } + } + } + +LExit: + return hr; +} + +extern "C" void ContainersUninitialize( + __in BURN_CONTAINERS* pContainers + ) +{ + if (pContainers->rgContainers) + { + for (DWORD i = 0; i < pContainers->cContainers; ++i) + { + BURN_CONTAINER* pContainer = &pContainers->rgContainers[i]; + + ReleaseStr(pContainer->sczId); + ReleaseStr(pContainer->sczHash); + ReleaseStr(pContainer->sczSourcePath); + ReleaseStr(pContainer->sczFilePath); + ReleaseMem(pContainer->pbHash); + ReleaseStr(pContainer->downloadSource.sczUrl); + ReleaseStr(pContainer->downloadSource.sczUser); + ReleaseStr(pContainer->downloadSource.sczPassword); + ReleaseStr(pContainer->sczUnverifiedPath); + } + MemFree(pContainers->rgContainers); + } + + // clear struct + memset(pContainers, 0, sizeof(BURN_CONTAINERS)); +} + +extern "C" HRESULT ContainerOpenUX( + __in BURN_SECTION* pSection, + __in BURN_CONTAINER_CONTEXT* pContext + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER container = { }; + LPWSTR sczExecutablePath = NULL; + + // open attached container + container.type = BURN_CONTAINER_TYPE_CABINET; + container.fPrimary = TRUE; + container.fAttached = TRUE; + container.dwAttachedIndex = 0; + + hr = SectionGetAttachedContainerInfo(pSection, container.dwAttachedIndex, container.type, &container.qwAttachedOffset, &container.qwFileSize, &container.fActuallyAttached); + ExitOnFailure(hr, "Failed to get container information for UX container."); + + AssertSz(container.fActuallyAttached, "The BA container must always be found attached."); + + hr = PathForCurrentProcess(&sczExecutablePath, NULL); + ExitOnFailure(hr, "Failed to get path for executing module."); + + hr = ContainerOpen(pContext, &container, pSection->hEngineFile, sczExecutablePath); + ExitOnFailure(hr, "Failed to open attached container."); + +LExit: + ReleaseStr(sczExecutablePath); + + return hr; +} + +extern "C" HRESULT ContainerOpen( + __in BURN_CONTAINER_CONTEXT* pContext, + __in BURN_CONTAINER* pContainer, + __in HANDLE hContainerFile, + __in_z LPCWSTR wzFilePath + ) +{ + HRESULT hr = S_OK; + LARGE_INTEGER li = { }; + + // initialize context + pContext->type = pContainer->type; + pContext->qwSize = pContainer->qwFileSize; + pContext->qwOffset = pContainer->qwAttachedOffset; + + // If the handle to the container is not open already, open container file + if (INVALID_HANDLE_VALUE == hContainerFile) + { + pContext->hFile = ::CreateFileW(wzFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + ExitOnInvalidHandleWithLastError(pContext->hFile, hr, "Failed to open file: %ls", wzFilePath); + } + else // use the container file handle. + { + if (!::DuplicateHandle(::GetCurrentProcess(), hContainerFile, ::GetCurrentProcess(), &pContext->hFile, 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + ExitWithLastError(hr, "Failed to duplicate handle to container: %ls", wzFilePath); + } + } + + // If it is a container attached to an executable, seek to the container offset. + if (pContainer->fAttached) + { + li.QuadPart = (LONGLONG)pContext->qwOffset; + } + + if (!::SetFilePointerEx(pContext->hFile, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to move file pointer to container offset."); + } + + // open the archive + switch (pContext->type) + { + case BURN_CONTAINER_TYPE_CABINET: + hr = CabExtractOpen(pContext, wzFilePath); + break; + } + ExitOnFailure(hr, "Failed to open container."); + +LExit: + return hr; +} + +extern "C" HRESULT ContainerNextStream( + __in BURN_CONTAINER_CONTEXT* pContext, + __inout_z LPWSTR* psczStreamName + ) +{ + HRESULT hr = S_OK; + + switch (pContext->type) + { + case BURN_CONTAINER_TYPE_CABINET: + hr = CabExtractNextStream(pContext, psczStreamName); + break; + } + +//LExit: + return hr; +} + +extern "C" HRESULT ContainerStreamToFile( + __in BURN_CONTAINER_CONTEXT* pContext, + __in_z LPCWSTR wzFileName + ) +{ + HRESULT hr = S_OK; + + switch (pContext->type) + { + case BURN_CONTAINER_TYPE_CABINET: + hr = CabExtractStreamToFile(pContext, wzFileName); + break; + } + +//LExit: + return hr; +} + +extern "C" HRESULT ContainerStreamToBuffer( + __in BURN_CONTAINER_CONTEXT* pContext, + __out BYTE** ppbBuffer, + __out SIZE_T* pcbBuffer + ) +{ + HRESULT hr = S_OK; + + switch (pContext->type) + { + case BURN_CONTAINER_TYPE_CABINET: + hr = CabExtractStreamToBuffer(pContext, ppbBuffer, pcbBuffer); + break; + + default: + *ppbBuffer = NULL; + *pcbBuffer = 0; + } + +//LExit: + return hr; +} + +extern "C" HRESULT ContainerSkipStream( + __in BURN_CONTAINER_CONTEXT* pContext + ) +{ + HRESULT hr = S_OK; + + switch (pContext->type) + { + case BURN_CONTAINER_TYPE_CABINET: + hr = CabExtractSkipStream(pContext); + break; + } + +//LExit: + return hr; +} + +extern "C" HRESULT ContainerClose( + __in BURN_CONTAINER_CONTEXT* pContext + ) +{ + HRESULT hr = S_OK; + + // close container + switch (pContext->type) + { + case BURN_CONTAINER_TYPE_CABINET: + hr = CabExtractClose(pContext); + ExitOnFailure(hr, "Failed to close cabinet."); + break; + } + +LExit: + ReleaseFile(pContext->hFile); + + if (SUCCEEDED(hr)) + { + memset(pContext, 0, sizeof(BURN_CONTAINER_CONTEXT)); + } + + return hr; +} + +extern "C" HRESULT ContainerFindById( + __in BURN_CONTAINERS* pContainers, + __in_z LPCWSTR wzId, + __out BURN_CONTAINER** ppContainer + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER* pContainer = NULL; + + for (DWORD i = 0; i < pContainers->cContainers; ++i) + { + pContainer = &pContainers->rgContainers[i]; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pContainer->sczId, -1, wzId, -1)) + { + *ppContainer = pContainer; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} diff --git a/src/burn/engine/container.h b/src/burn/engine/container.h new file mode 100644 index 00000000..c2c1c9a8 --- /dev/null +++ b/src/burn/engine/container.h @@ -0,0 +1,191 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// typedefs + +//typedef HRESULT (*PFN_EXTRACTOPEN)( +// __in HANDLE hFile, +// __in DWORD64 qwOffset, +// __in DWORD64 qwSize, +// __out void** ppCookie +// ); +//typedef HRESULT (*PFN_EXTRACTNEXTSTREAM)( +// __in void* pCookie, +// __inout_z LPWSTR* psczStreamName +// ); +//typedef HRESULT (*PFN_EXTRACTSTREAMTOFILE)( +// __in void* pCookie, +// __in_z LPCWSTR wzFileName +// ); +//typedef HRESULT (*PFN_EXTRACTSTREAMTOBUFFER)( +// __in void* pCookie, +// __out BYTE** ppbBuffer, +// __out SIZE_T* pcbBuffer +// ); +//typedef HRESULT (*PFN_EXTRACTCLOSE)( +// __in void* pCookie +// ); + + +// constants + +enum BURN_CONTAINER_TYPE +{ + BURN_CONTAINER_TYPE_NONE, + BURN_CONTAINER_TYPE_CABINET, + BURN_CONTAINER_TYPE_SEVENZIP, +}; + +enum BURN_CAB_OPERATION +{ + BURN_CAB_OPERATION_NONE, + BURN_CAB_OPERATION_NEXT_STREAM, + BURN_CAB_OPERATION_STREAM_TO_FILE, + BURN_CAB_OPERATION_STREAM_TO_BUFFER, + BURN_CAB_OPERATION_SKIP_STREAM, + BURN_CAB_OPERATION_CLOSE, +}; + + +// structs + +typedef struct _BURN_CONTAINER +{ + LPWSTR sczId; + BURN_CONTAINER_TYPE type; + BOOL fPrimary; + BOOL fAttached; + DWORD dwAttachedIndex; + DWORD64 qwFileSize; + LPWSTR sczHash; + LPWSTR sczFilePath; // relative path to container. + DOWNLOAD_SOURCE downloadSource; + + BYTE* pbHash; + DWORD cbHash; + DWORD64 qwAttachedOffset; + BOOL fActuallyAttached; // indicates whether an attached container is attached or missing. + + // mutable members + BOOL fPlanned; + LPWSTR sczSourcePath; + LPWSTR sczUnverifiedPath; + DWORD64 qwExtractSizeTotal; + DWORD64 qwCommittedCacheProgress; + DWORD64 qwCommittedExtractProgress; + HRESULT hrExtract; +} BURN_CONTAINER; + +typedef struct _BURN_CONTAINERS +{ + BURN_CONTAINER* rgContainers; + DWORD cContainers; +} BURN_CONTAINERS; + +typedef struct _BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER +{ + HANDLE hFile; + LARGE_INTEGER liPosition; +} BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER; + +typedef struct _BURN_CONTAINER_CONTEXT_CABINET +{ + LPWSTR sczFile; + + HANDLE hThread; + HANDLE hBeginOperationEvent; + HANDLE hOperationCompleteEvent; + + BURN_CAB_OPERATION operation; + HRESULT hrError; + + LPWSTR* psczStreamName; + LPCWSTR wzTargetFile; + HANDLE hTargetFile; + BYTE* pbTargetBuffer; + DWORD cbTargetBuffer; + DWORD iTargetBuffer; + + BURN_CONTAINER_CONTEXT_CABINET_VIRTUAL_FILE_POINTER* rgVirtualFilePointers; + DWORD cVirtualFilePointers; +} BURN_CONTAINER_CONTEXT_CABINET; + +typedef struct _BURN_CONTAINER_CONTEXT +{ + HANDLE hFile; + DWORD64 qwOffset; + DWORD64 qwSize; + + //PFN_EXTRACTOPEN pfnExtractOpen; + //PFN_EXTRACTNEXTSTREAM pfnExtractNextStream; + //PFN_EXTRACTSTREAMTOFILE pfnExtractStreamToFile; + //PFN_EXTRACTSTREAMTOBUFFER pfnExtractStreamToBuffer; + //PFN_EXTRACTCLOSE pfnExtractClose; + //void* pCookie; + BURN_CONTAINER_TYPE type; + union + { + BURN_CONTAINER_CONTEXT_CABINET Cabinet; + }; + +} BURN_CONTAINER_CONTEXT; + + +// functions + +HRESULT ContainersParseFromXml( + __in BURN_CONTAINERS* pContainers, + __in IXMLDOMNode* pixnBundle + ); +HRESULT ContainersInitialize( + __in BURN_CONTAINERS* pContainers, + __in BURN_SECTION* pSection + ); +void ContainersUninitialize( + __in BURN_CONTAINERS* pContainers + ); +HRESULT ContainerOpenUX( + __in BURN_SECTION* pSection, + __in BURN_CONTAINER_CONTEXT* pContext + ); +HRESULT ContainerOpen( + __in BURN_CONTAINER_CONTEXT* pContext, + __in BURN_CONTAINER* pContainer, + __in HANDLE hContainerFile, + __in_z LPCWSTR wzFilePath + ); +HRESULT ContainerNextStream( + __in BURN_CONTAINER_CONTEXT* pContext, + __inout_z LPWSTR* psczStreamName + ); +HRESULT ContainerStreamToFile( + __in BURN_CONTAINER_CONTEXT* pContext, + __in_z LPCWSTR wzFileName + ); +HRESULT ContainerStreamToBuffer( + __in BURN_CONTAINER_CONTEXT* pContext, + __out BYTE** ppbBuffer, + __out SIZE_T* pcbBuffer + ); +HRESULT ContainerSkipStream( + __in BURN_CONTAINER_CONTEXT* pContext + ); +HRESULT ContainerClose( + __in BURN_CONTAINER_CONTEXT* pContext + ); +HRESULT ContainerFindById( + __in BURN_CONTAINERS* pContainers, + __in_z LPCWSTR wzId, + __out BURN_CONTAINER** ppContainer + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/core.cpp b/src/burn/engine/core.cpp new file mode 100644 index 00000000..535043af --- /dev/null +++ b/src/burn/engine/core.cpp @@ -0,0 +1,1856 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// structs + +struct BURN_CACHE_THREAD_CONTEXT +{ + BURN_ENGINE_STATE* pEngineState; + DWORD* pcOverallProgressTicks; + BOOL* pfRollback; +}; + + +// internal function declarations + +static HRESULT ParseCommandLine( + __in int argc, + __in LPWSTR* argv, + __in BOOTSTRAPPER_COMMAND* pCommand, + __in BURN_PIPE_CONNECTION* pCompanionConnection, + __in BURN_PIPE_CONNECTION* pEmbeddedConnection, + __in BURN_VARIABLES* pVariables, + __out BURN_MODE* pMode, + __out BURN_AU_PAUSE_ACTION* pAutomaticUpdates, + __out BOOL* pfDisableSystemRestore, + __out_z LPWSTR* psczSourceProcessPath, + __out_z LPWSTR* psczOriginalSource, + __out BOOL* pfDisableUnelevate, + __out DWORD *pdwLoggingAttributes, + __out_z LPWSTR* psczLogFile, + __out_z LPWSTR* psczActiveParent, + __out_z LPWSTR* psczIgnoreDependencies, + __out_z LPWSTR* psczAncestors, + __out_z LPWSTR* psczSanitizedCommandLine + ); +static HRESULT ParsePipeConnection( + __in_ecount(3) LPWSTR* rgArgs, + __in BURN_PIPE_CONNECTION* pConnection + ); +static HRESULT DetectPackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_PACKAGE* pPackage + ); +static HRESULT DetectPackagePayloadsCached( + __in BURN_PACKAGE* pPackage + ); +static DWORD WINAPI CacheThreadProc( + __in LPVOID lpThreadParameter + ); +static HRESULT WaitForCacheThread( + __in HANDLE hCacheThread + ); +static void LogPackages( + __in_opt const BURN_PACKAGE* pUpgradeBundlePackage, + __in_opt const BURN_PACKAGE* pForwardCompatibleBundlePackage, + __in const BURN_PACKAGES* pPackages, + __in const BURN_RELATED_BUNDLES* pRelatedBundles, + __in const BOOTSTRAPPER_ACTION action + ); +static void LogRelatedBundles( + __in const BURN_RELATED_BUNDLES* pRelatedBundles, + __in BOOL fReverse + ); + + +// function definitions + +extern "C" HRESULT CoreInitialize( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + LPWSTR sczSanitizedCommandLine = NULL; + LPWSTR sczStreamName = NULL; + BYTE* pbBuffer = NULL; + SIZE_T cbBuffer = 0; + BURN_CONTAINER_CONTEXT containerContext = { }; + BOOL fElevated = FALSE; + LPWSTR sczSourceProcessPath = NULL; + LPWSTR sczSourceProcessFolder = NULL; + LPWSTR sczOriginalSource = NULL; + + // Initialize variables. + hr = VariableInitialize(&pEngineState->variables); + ExitOnFailure(hr, "Failed to initialize variables."); + + // Open attached UX container. + hr = ContainerOpenUX(&pEngineState->section, &containerContext); + ExitOnFailure(hr, "Failed to open attached UX container."); + + // Load manifest. + hr = ContainerNextStream(&containerContext, &sczStreamName); + ExitOnFailure(hr, "Failed to open manifest stream."); + + hr = ContainerStreamToBuffer(&containerContext, &pbBuffer, &cbBuffer); + ExitOnFailure(hr, "Failed to get manifest stream from container."); + + hr = ManifestLoadXmlFromBuffer(pbBuffer, cbBuffer, pEngineState); + ExitOnFailure(hr, "Failed to load manifest."); + + hr = ContainersInitialize(&pEngineState->containers, &pEngineState->section); + ExitOnFailure(hr, "Failed to initialize containers."); + + // Parse command line. + hr = ParseCommandLine(pEngineState->argc, pEngineState->argv, &pEngineState->command, &pEngineState->companionConnection, &pEngineState->embeddedConnection, &pEngineState->variables, &pEngineState->mode, &pEngineState->automaticUpdates, &pEngineState->fDisableSystemRestore, &sczSourceProcessPath, &sczOriginalSource, &pEngineState->fDisableUnelevate, &pEngineState->log.dwAttributes, &pEngineState->log.sczPath, &pEngineState->registration.sczActiveParent, &pEngineState->sczIgnoreDependencies, &pEngineState->registration.sczAncestors, &sczSanitizedCommandLine); + ExitOnFailure(hr, "Failed to parse command line."); + + LogId(REPORT_STANDARD, MSG_BURN_COMMAND_LINE, sczSanitizedCommandLine ? sczSanitizedCommandLine : L""); + + hr = CoreInitializeConstants(pEngineState); + ExitOnFailure(hr, "Failed to initialize contants."); + + // Retain whether bundle was initially run elevated. + ProcElevated(::GetCurrentProcess(), &fElevated); + + hr = VariableSetNumeric(&pEngineState->variables, BURN_BUNDLE_ELEVATED, fElevated, TRUE); + ExitOnFailure(hr, "Failed to overwrite the %ls built-in variable.", BURN_BUNDLE_ELEVATED); + + hr = VariableSetNumeric(&pEngineState->variables, BURN_BUNDLE_UILEVEL, pEngineState->command.display, TRUE); + ExitOnFailure(hr, "Failed to overwrite the %ls built-in variable.", BURN_BUNDLE_UILEVEL); + + if (sczSourceProcessPath) + { + hr = VariableSetString(&pEngineState->variables, BURN_BUNDLE_SOURCE_PROCESS_PATH, sczSourceProcessPath, TRUE, FALSE); + ExitOnFailure(hr, "Failed to set source process path variable."); + + hr = PathGetDirectory(sczSourceProcessPath, &sczSourceProcessFolder); + ExitOnFailure(hr, "Failed to get source process folder from path."); + + hr = VariableSetString(&pEngineState->variables, BURN_BUNDLE_SOURCE_PROCESS_FOLDER, sczSourceProcessFolder, TRUE, FALSE); + ExitOnFailure(hr, "Failed to set source process folder variable."); + } + + // Set BURN_BUNDLE_ORIGINAL_SOURCE, if it was passed in on the command line. + // Needs to be done after ManifestLoadXmlFromBuffer. + if (sczOriginalSource) + { + hr = VariableSetString(&pEngineState->variables, BURN_BUNDLE_ORIGINAL_SOURCE, sczOriginalSource, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set original source variable."); + } + + if (BURN_MODE_UNTRUSTED == pEngineState->mode || BURN_MODE_NORMAL == pEngineState->mode || BURN_MODE_EMBEDDED == pEngineState->mode) + { + hr = CacheInitialize(&pEngineState->registration, &pEngineState->variables, sczSourceProcessPath); + ExitOnFailure(hr, "Failed to initialize internal cache functionality."); + } + + // If we're not elevated then we'll be loading the bootstrapper application, so extract + // the payloads from the BA container. + if (BURN_MODE_NORMAL == pEngineState->mode || BURN_MODE_EMBEDDED == pEngineState->mode) + { + // Extract all UX payloads to working folder. + hr = UserExperienceEnsureWorkingFolder(pEngineState->registration.sczId, &pEngineState->userExperience.sczTempDirectory); + ExitOnFailure(hr, "Failed to get unique temporary folder for bootstrapper application."); + + hr = PayloadExtractUXContainer(&pEngineState->userExperience.payloads, &containerContext, pEngineState->userExperience.sczTempDirectory); + ExitOnFailure(hr, "Failed to extract bootstrapper application payloads."); + + hr = PathConcat(pEngineState->userExperience.sczTempDirectory, L"BootstrapperApplicationData.xml", &pEngineState->command.wzBootstrapperApplicationDataPath); + ExitOnFailure(hr, "Failed to get BootstrapperApplicationDataPath."); + + hr = StrAllocString(&pEngineState->command.wzBootstrapperWorkingFolder, pEngineState->userExperience.sczTempDirectory, 0); + ExitOnFailure(hr, "Failed to copy sczBootstrapperWorkingFolder."); + } + +LExit: + ReleaseStr(sczOriginalSource); + ReleaseStr(sczSourceProcessFolder); + ReleaseStr(sczSourceProcessPath); + ContainerClose(&containerContext); + ReleaseStr(sczStreamName); + ReleaseStr(sczSanitizedCommandLine); + ReleaseMem(pbBuffer); + + return hr; +} + +extern "C" HRESULT CoreInitializeConstants( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + BURN_REGISTRATION* pRegistration = &pEngineState->registration; + + hr = DependencyInitialize(pRegistration, pEngineState->sczIgnoreDependencies); + ExitOnFailure(hr, "Failed to initialize dependency data."); + + // Support passing Ancestors to embedded burn bundles. + if (pRegistration->sczAncestors && *pRegistration->sczAncestors) + { + hr = StrAllocFormatted(&pRegistration->sczBundlePackageAncestors, L"%ls;%ls", pRegistration->sczAncestors, pRegistration->sczId); + ExitOnFailure(hr, "Failed to copy ancestors and self to bundle package ancestors."); + } + else + { + hr = StrAllocString(&pRegistration->sczBundlePackageAncestors, pRegistration->sczId, 0); + ExitOnFailure(hr, "Failed to copy self to bundle package ancestors."); + } + + for (DWORD i = 0; i < pEngineState->packages.cPackages; ++i) + { + BURN_PACKAGE* pPackage = pEngineState->packages.rgPackages + i; + + if (BURN_PACKAGE_TYPE_EXE == pPackage->type && BURN_EXE_PROTOCOL_TYPE_BURN == pPackage->Exe.protocol) // TODO: Don't assume exePackages with burn protocol are bundles. + { + // Pass along any ancestors and ourself to prevent infinite loops. + pPackage->Exe.wzAncestors = pRegistration->sczBundlePackageAncestors; + } + } + +LExit: + return hr; +} + +extern "C" HRESULT CoreSerializeEngineState( + __in BURN_ENGINE_STATE* pEngineState, + __inout BYTE** ppbBuffer, + __inout SIZE_T* piBuffer + ) +{ + HRESULT hr = S_OK; + + hr = VariableSerialize(&pEngineState->variables, TRUE, ppbBuffer, piBuffer); + ExitOnFailure(hr, "Failed to serialize variables."); + +LExit: + return hr; +} + +extern "C" HRESULT CoreQueryRegistration( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + BYTE* pbBuffer = NULL; + SIZE_T cbBuffer = 0; + SIZE_T iBuffer = 0; + + // Detect if bundle is already installed. + hr = RegistrationDetectInstalled(&pEngineState->registration); + ExitOnFailure(hr, "Failed to detect bundle install state."); + + // detect resume type + hr = RegistrationDetectResumeType(&pEngineState->registration, &pEngineState->command.resumeType); + ExitOnFailure(hr, "Failed to detect resume type."); + + // If we have a resume mode that suggests the bundle might already be present, try to load any + // previously stored state. + if (BOOTSTRAPPER_RESUME_TYPE_INVALID < pEngineState->command.resumeType) + { + // load resume state + hr = RegistrationLoadState(&pEngineState->registration, &pbBuffer, &cbBuffer); + if (SUCCEEDED(hr)) + { + hr = VariableDeserialize(&pEngineState->variables, TRUE, pbBuffer, cbBuffer, &iBuffer); + } + + // Log any failures and continue. + if (FAILED(hr)) + { + LogId(REPORT_STANDARD, MSG_CANNOT_LOAD_STATE_FILE, hr, pEngineState->registration.sczStateFile); + hr = S_OK; + } + } + +LExit: + ReleaseBuffer(pbBuffer); + + return hr; +} + +extern "C" HRESULT CoreDetect( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HWND hwndParent + ) +{ + HRESULT hr = S_OK; + BOOL fDetectBegan = FALSE; + BURN_PACKAGE* pPackage = NULL; + HRESULT hrFirstPackageFailure = S_OK; + + LogId(REPORT_STANDARD, MSG_DETECT_BEGIN, pEngineState->packages.cPackages); + + // Always reset the detect state which means the plan should be reset too. + pEngineState->fDetected = FALSE; + pEngineState->fPlanned = FALSE; + DetectReset(&pEngineState->registration, &pEngineState->packages); + PlanReset(&pEngineState->plan, &pEngineState->containers, &pEngineState->packages, &pEngineState->layoutPayloads); + + // Detect if bundle installed state has changed since start up. This + // only happens if Apply() changed the state of bundle (installed or + // uninstalled). In that case, Detect() can be used here to reset + // the installed state. + hr = RegistrationDetectInstalled(&pEngineState->registration); + ExitOnFailure(hr, "Failed to detect bundle install state."); + + if (pEngineState->registration.fInstalled) + { + hr = VariableSetNumeric(&pEngineState->variables, BURN_BUNDLE_INSTALLED, 1, TRUE); + ExitOnFailure(hr, "Failed to set the bundle installed built-in variable."); + } + else + { + hr = VariableSetString(&pEngineState->variables, BURN_BUNDLE_INSTALLED, NULL, TRUE, FALSE); + ExitOnFailure(hr, "Failed to unset the bundle installed built-in variable."); + } + + fDetectBegan = TRUE; + hr = UserExperienceOnDetectBegin(&pEngineState->userExperience, pEngineState->registration.fCached, pEngineState->registration.fInstalled, pEngineState->packages.cPackages); + ExitOnRootFailure(hr, "UX aborted detect begin."); + + pEngineState->userExperience.hwndDetect = hwndParent; + + hr = SearchesExecute(&pEngineState->searches, &pEngineState->variables); + ExitOnFailure(hr, "Failed to execute searches."); + + // Load all of the related bundles. + hr = RegistrationDetectRelatedBundles(&pEngineState->registration); + ExitOnFailure(hr, "Failed to detect related bundles."); + + hr = DependencyDetectProviderKeyBundleId(&pEngineState->registration); + if (SUCCEEDED(hr)) + { + hr = DetectForwardCompatibleBundles(&pEngineState->userExperience, &pEngineState->registration); + ExitOnFailure(hr, "Failed to detect forward compatible bundle."); + } + else if (E_NOTFOUND == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to detect provider key bundle id."); + + // Report the related bundles. + hr = DetectReportRelatedBundles(&pEngineState->userExperience, &pEngineState->registration, pEngineState->command.relationType, pEngineState->command.action, &pEngineState->registration.fEligibleForCleanup); + ExitOnFailure(hr, "Failed to report detected related bundles."); + + // Do update detection. + hr = DetectUpdate(pEngineState->registration.sczId, &pEngineState->userExperience, &pEngineState->update); + ExitOnFailure(hr, "Failed to detect update."); + + // Detecting MSPs requires special initialization before processing each package but + // only do the detection if there are actually patch packages to detect because it + // can be expensive. + if (pEngineState->packages.cPatchInfo) + { + hr = MspEngineDetectInitialize(&pEngineState->packages); + ExitOnFailure(hr, "Failed to initialize MSP engine detection."); + + hr = MsiEngineDetectInitialize(&pEngineState->packages); + ExitOnFailure(hr, "Failed to initialize MSI engine detection."); + } + + for (DWORD i = 0; i < pEngineState->packages.cPackages; ++i) + { + pPackage = pEngineState->packages.rgPackages + i; + + hr = DetectPackage(pEngineState, pPackage); + + // If the package detection failed, ensure the package state is set to unknown. + if (FAILED(hr)) + { + if (SUCCEEDED(hrFirstPackageFailure)) + { + hrFirstPackageFailure = hr; + } + + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN; + pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + } + } + + hr = DependencyDetect(pEngineState); + ExitOnFailure(hr, "Failed to detect the dependencies."); + + // Log the detected states. + for (DWORD iPackage = 0; iPackage < pEngineState->packages.cPackages; ++iPackage) + { + pPackage = pEngineState->packages.rgPackages + iPackage; + + // If any packages that can affect registration are present, then the bundle should not automatically be uninstalled. + if (pEngineState->registration.fEligibleForCleanup && pPackage->fCanAffectRegistration && + (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->cacheRegistrationState || + BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->installRegistrationState)) + { + pEngineState->registration.fEligibleForCleanup = FALSE; + } + + LogId(REPORT_STANDARD, MSG_DETECTED_PACKAGE, pPackage->sczId, LoggingPackageStateToString(pPackage->currentState), LoggingBoolToString(pPackage->fCached), LoggingPackageRegistrationStateToString(pPackage->fCanAffectRegistration, pPackage->installRegistrationState), LoggingPackageRegistrationStateToString(pPackage->fCanAffectRegistration, pPackage->cacheRegistrationState)); + + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) + { + for (DWORD iFeature = 0; iFeature < pPackage->Msi.cFeatures; ++iFeature) + { + const BURN_MSIFEATURE* pFeature = pPackage->Msi.rgFeatures + iFeature; + LogId(REPORT_STANDARD, MSG_DETECTED_MSI_FEATURE, pPackage->sczId, pFeature->sczId, LoggingMsiFeatureStateToString(pFeature->currentState)); + } + } + else if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + for (DWORD iTargetProduct = 0; iTargetProduct < pPackage->Msp.cTargetProductCodes; ++iTargetProduct) + { + const BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + iTargetProduct; + LogId(REPORT_STANDARD, MSG_DETECTED_MSP_TARGET, pPackage->sczId, pTargetProduct->wzTargetProductCode, LoggingPackageStateToString(pTargetProduct->patchPackageState)); + } + } + } + +LExit: + if (SUCCEEDED(hr)) + { + hr = hrFirstPackageFailure; + } + + if (SUCCEEDED(hr)) + { + pEngineState->fDetected = TRUE; + } + + if (fDetectBegan) + { + UserExperienceOnDetectComplete(&pEngineState->userExperience, hr, pEngineState->registration.fEligibleForCleanup); + } + + pEngineState->userExperience.hwndDetect = NULL; + + LogId(REPORT_STANDARD, MSG_DETECT_COMPLETE, hr, !fDetectBegan ? "(failed)" : LoggingBoolToString(pEngineState->registration.fInstalled), !fDetectBegan ? "(failed)" : LoggingBoolToString(pEngineState->registration.fCached), FAILED(hr) ? "(failed)" : LoggingBoolToString(pEngineState->registration.fEligibleForCleanup)); + + return hr; +} + +extern "C" HRESULT CorePlan( + __in BURN_ENGINE_STATE* pEngineState, + __in BOOTSTRAPPER_ACTION action + ) +{ + HRESULT hr = S_OK; + BOOL fPlanBegan = FALSE; + BURN_PACKAGE* pUpgradeBundlePackage = NULL; + BURN_PACKAGE* pForwardCompatibleBundlePackage = NULL; + BOOL fContinuePlanning = TRUE; // assume we won't skip planning due to dependencies. + + LogId(REPORT_STANDARD, MSG_PLAN_BEGIN, pEngineState->packages.cPackages, LoggingBurnActionToString(action)); + + fPlanBegan = TRUE; + hr = UserExperienceOnPlanBegin(&pEngineState->userExperience, pEngineState->packages.cPackages); + ExitOnRootFailure(hr, "BA aborted plan begin."); + + if (!pEngineState->fDetected) + { + ExitOnFailure(hr = E_INVALIDSTATE, "Plan cannot be done without a successful Detect."); + } + else if (pEngineState->plan.fAffectedMachineState) + { + ExitOnFailure(hr = E_INVALIDSTATE, "Plan requires a new successful Detect after calling Apply."); + } + + // Always reset the plan. + pEngineState->fPlanned = FALSE; + PlanReset(&pEngineState->plan, &pEngineState->containers, &pEngineState->packages, &pEngineState->layoutPayloads); + + // Remember the overall action state in the plan since it shapes the changes + // we make everywhere. + pEngineState->plan.action = action; + pEngineState->plan.pPayloads = &pEngineState->payloads; + pEngineState->plan.wzBundleId = pEngineState->registration.sczId; + pEngineState->plan.wzBundleProviderKey = pEngineState->registration.sczId; + pEngineState->plan.fDisableRollback = pEngineState->fDisableRollback; + + hr = PlanSetVariables(action, &pEngineState->variables); + ExitOnFailure(hr, "Failed to update action."); + + // Set resume commandline + hr = PlanSetResumeCommand(&pEngineState->registration, action, &pEngineState->command, &pEngineState->log); + ExitOnFailure(hr, "Failed to set resume command"); + + hr = DependencyPlanInitialize(&pEngineState->registration, &pEngineState->plan); + ExitOnFailure(hr, "Failed to initialize the dependencies for the plan."); + + if (BOOTSTRAPPER_ACTION_LAYOUT == action) + { + Assert(!pEngineState->plan.fPerMachine); + + // Plan the bundle's layout. + hr = PlanLayoutBundle(&pEngineState->plan, pEngineState->registration.sczExecutableName, pEngineState->section.qwBundleSize, &pEngineState->variables, &pEngineState->layoutPayloads); + ExitOnFailure(hr, "Failed to plan the layout of the bundle."); + + // Plan the packages' layout. + hr = PlanPackages(&pEngineState->userExperience, &pEngineState->packages, &pEngineState->plan, &pEngineState->log, &pEngineState->variables, pEngineState->command.display, pEngineState->command.relationType); + ExitOnFailure(hr, "Failed to plan packages."); + } + else if (BOOTSTRAPPER_ACTION_UPDATE_REPLACE == action || BOOTSTRAPPER_ACTION_UPDATE_REPLACE_EMBEDDED == action) + { + Assert(!pEngineState->plan.fPerMachine); + + pUpgradeBundlePackage = &pEngineState->update.package; + + hr = PlanUpdateBundle(&pEngineState->userExperience, pUpgradeBundlePackage, &pEngineState->plan, &pEngineState->log, &pEngineState->variables, pEngineState->command.display, pEngineState->command.relationType); + ExitOnFailure(hr, "Failed to plan update."); + } + else + { + hr = PlanForwardCompatibleBundles(&pEngineState->userExperience, &pEngineState->command, &pEngineState->plan, &pEngineState->registration, action); + ExitOnFailure(hr, "Failed to plan forward compatible bundles."); + + if (pEngineState->plan.fEnabledForwardCompatibleBundle) + { + Assert(!pEngineState->plan.fPerMachine); + + pForwardCompatibleBundlePackage = &pEngineState->plan.forwardCompatibleBundle; + + hr = PlanPassThroughBundle(&pEngineState->userExperience, pForwardCompatibleBundlePackage, &pEngineState->plan, &pEngineState->log, &pEngineState->variables, pEngineState->command.display, pEngineState->command.relationType); + ExitOnFailure(hr, "Failed to plan passthrough."); + } + else // doing an action that modifies the machine state. + { + pEngineState->plan.fPerMachine = pEngineState->registration.fPerMachine; // default the scope of the plan to the per-machine state of the bundle. + + hr = PlanRegistration(&pEngineState->plan, &pEngineState->registration, pEngineState->command.resumeType, pEngineState->command.relationType, &fContinuePlanning); + ExitOnFailure(hr, "Failed to plan registration."); + + if (fContinuePlanning) + { + // Remember the early index, because we want to be able to insert some related bundles + // into the plan before other executed packages. This particularly occurs for uninstallation + // of addons and patches, which should be uninstalled before the main product. + DWORD dwExecuteActionEarlyIndex = pEngineState->plan.cExecuteActions; + + // Plan the related bundles first to support downgrades with ref-counting. + hr = PlanRelatedBundlesBegin(&pEngineState->userExperience, &pEngineState->registration, pEngineState->command.relationType, &pEngineState->plan); + ExitOnFailure(hr, "Failed to plan related bundles."); + + hr = PlanPackages(&pEngineState->userExperience, &pEngineState->packages, &pEngineState->plan, &pEngineState->log, &pEngineState->variables, pEngineState->command.display, pEngineState->command.relationType); + ExitOnFailure(hr, "Failed to plan packages."); + + // Schedule the update of related bundles last. + hr = PlanRelatedBundlesComplete(&pEngineState->registration, &pEngineState->plan, &pEngineState->log, &pEngineState->variables, dwExecuteActionEarlyIndex); + ExitOnFailure(hr, "Failed to schedule related bundles."); + } + } + } + + if (fContinuePlanning) + { + // Finally, display all packages and related bundles in the log. + LogPackages(pUpgradeBundlePackage, pForwardCompatibleBundlePackage, &pEngineState->packages, &pEngineState->registration.relatedBundles, action); + } + + PlanDump(&pEngineState->plan); + +LExit: + if (SUCCEEDED(hr)) + { + pEngineState->fPlanned = TRUE; + } + + if (fPlanBegan) + { + UserExperienceOnPlanComplete(&pEngineState->userExperience, hr); + } + + LogId(REPORT_STANDARD, MSG_PLAN_COMPLETE, hr); + + return hr; +} + +extern "C" HRESULT CoreElevate( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HWND hwndParent + ) +{ + HRESULT hr = S_OK; + DWORD cAVRetryAttempts = 0; + + while (INVALID_HANDLE_VALUE == pEngineState->companionConnection.hPipe) + { + // If the elevated companion pipe isn't created yet, let's make that happen. + if (!pEngineState->sczBundleEngineWorkingPath) + { + hr = CacheBundleToWorkingDirectory(pEngineState->registration.sczId, pEngineState->registration.sczExecutableName, &pEngineState->section, &pEngineState->sczBundleEngineWorkingPath); + ExitOnFailure(hr, "Failed to cache engine to working directory."); + } + + hr = ElevationElevate(pEngineState, hwndParent); + if (E_SUSPECTED_AV_INTERFERENCE == hr && 1 > cAVRetryAttempts) + { + ++cAVRetryAttempts; + continue; + } + ExitOnFailure(hr, "Failed to actually elevate."); + + hr = VariableSetNumeric(&pEngineState->variables, BURN_BUNDLE_ELEVATED, TRUE, TRUE); + ExitOnFailure(hr, "Failed to overwrite the %ls built-in variable.", BURN_BUNDLE_ELEVATED); + } + +LExit: + return hr; +} + +extern "C" HRESULT CoreApply( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HWND hwndParent + ) +{ + HRESULT hr = S_OK; + HANDLE hLock = NULL; + DWORD cOverallProgressTicks = 0; + HANDLE hCacheThread = NULL; + BOOL fApplyInitialize = FALSE; + BOOL fElevated = FALSE; + BOOL fRegistered = FALSE; + BOOL fRollback = FALSE; + BOOL fSuspend = FALSE; + BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE; + BURN_CACHE_THREAD_CONTEXT cacheThreadContext = { }; + DWORD dwPhaseCount = 0; + BOOTSTRAPPER_APPLYCOMPLETE_ACTION applyCompleteAction = BOOTSTRAPPER_APPLYCOMPLETE_ACTION_NONE; + + LogId(REPORT_STANDARD, MSG_APPLY_BEGIN); + + if (!pEngineState->fPlanned) + { + ExitOnFailure(hr = E_INVALIDSTATE, "Apply cannot be done without a successful Plan."); + } + else if (pEngineState->plan.fAffectedMachineState) + { + ExitOnFailure(hr = E_INVALIDSTATE, "Plans cannot be applied multiple times."); + } + + // Ensure any previous attempts to execute are reset. + ApplyReset(&pEngineState->userExperience, &pEngineState->packages); + + if (pEngineState->plan.cCacheActions) + { + ++dwPhaseCount; + } + if (pEngineState->plan.cExecuteActions) + { + ++dwPhaseCount; + } + + hr = UserExperienceOnApplyBegin(&pEngineState->userExperience, dwPhaseCount); + ExitOnRootFailure(hr, "BA aborted apply begin."); + + pEngineState->plan.fAffectedMachineState = pEngineState->plan.fCanAffectMachineState; + + // Abort if this bundle already requires a restart. + if (BOOTSTRAPPER_RESUME_TYPE_REBOOT_PENDING == pEngineState->command.resumeType) + { + restart = BOOTSTRAPPER_APPLY_RESTART_REQUIRED; + + hr = HRESULT_FROM_WIN32(ERROR_FAIL_NOACTION_REBOOT); + UserExperienceSendError(&pEngineState->userExperience, BOOTSTRAPPER_ERROR_TYPE_APPLY, NULL, hr, NULL, MB_ICONERROR | MB_OK, IDNOACTION); // ignore return value. + ExitFunction(); + } + + hr = ApplyLock(FALSE, &hLock); + ExitOnFailure(hr, "Another per-user setup is already executing."); + + // Initialize only after getting a lock. + fApplyInitialize = TRUE; + ApplyInitialize(); + + pEngineState->userExperience.hwndApply = hwndParent; + + hr = ApplySetVariables(&pEngineState->variables); + ExitOnFailure(hr, "Failed to set initial apply variables."); + + // If the plan is empty of work to do, skip everything. + if (!(pEngineState->plan.cRegistrationActions || pEngineState->plan.cCacheActions || pEngineState->plan.cExecuteActions || pEngineState->plan.cCleanActions)) + { + LogId(REPORT_STANDARD, MSG_APPLY_SKIPPED); + ExitFunction(); + } + + // Ensure the engine is cached to the working path. + if (!pEngineState->sczBundleEngineWorkingPath) + { + hr = CacheBundleToWorkingDirectory(pEngineState->registration.sczId, pEngineState->registration.sczExecutableName, &pEngineState->section, &pEngineState->sczBundleEngineWorkingPath); + ExitOnFailure(hr, "Failed to cache engine to working directory."); + } + + // Elevate. + if (pEngineState->plan.fPerMachine) + { + hr = CoreElevate(pEngineState, pEngineState->userExperience.hwndApply); + ExitOnFailure(hr, "Failed to elevate."); + + hr = ElevationApplyInitialize(pEngineState->companionConnection.hPipe, &pEngineState->userExperience, &pEngineState->variables, pEngineState->plan.action, pEngineState->automaticUpdates, !pEngineState->fDisableSystemRestore); + ExitOnFailure(hr, "Failed to initialize apply in elevated process."); + + fElevated = TRUE; + } + + // Register. + if (pEngineState->plan.fCanAffectMachineState) + { + fRegistered = TRUE; + hr = ApplyRegister(pEngineState); + ExitOnFailure(hr, "Failed to register bundle."); + } + + // Cache. + if (pEngineState->plan.cCacheActions) + { + // Launch the cache thread. + cacheThreadContext.pEngineState = pEngineState; + cacheThreadContext.pcOverallProgressTicks = &cOverallProgressTicks; + cacheThreadContext.pfRollback = &fRollback; + + hCacheThread = ::CreateThread(NULL, 0, CacheThreadProc, &cacheThreadContext, 0, NULL); + ExitOnNullWithLastError(hCacheThread, hr, "Failed to create cache thread."); + + // If we're not caching in parallel, wait for the cache thread to terminate. + if (!pEngineState->fParallelCacheAndExecute) + { + hr = WaitForCacheThread(hCacheThread); + ExitOnFailure(hr, "Failed while caching, aborting execution."); + + ReleaseHandle(hCacheThread); + } + } + + // Execute. + if (pEngineState->plan.cExecuteActions) + { + hr = ApplyExecute(pEngineState, hCacheThread, &cOverallProgressTicks, &fRollback, &fSuspend, &restart); + UserExperienceExecutePhaseComplete(&pEngineState->userExperience, hr); // signal that execute completed. + } + + // Wait for cache thread to terminate, this should return immediately unless we're waiting for layout to complete. + if (hCacheThread) + { + HRESULT hrCached = WaitForCacheThread(hCacheThread); + if (SUCCEEDED(hr)) + { + hr = hrCached; + } + } + + // If something went wrong or force restarted, skip cleaning. + if (FAILED(hr) || fRollback || fSuspend || BOOTSTRAPPER_APPLY_RESTART_INITIATED == restart) + { + ExitFunction(); + } + + // Clean. + if (pEngineState->plan.cCleanActions) + { + ApplyClean(&pEngineState->userExperience, &pEngineState->plan, pEngineState->companionConnection.hPipe); + } + +LExit: + // Unregister. + if (fRegistered) + { + ApplyUnregister(pEngineState, FAILED(hr) || fRollback, fSuspend, restart); + } + + if (fElevated) + { + ElevationApplyUninitialize(pEngineState->companionConnection.hPipe); + } + + pEngineState->userExperience.hwndApply = NULL; + + if (fApplyInitialize) + { + ApplyUninitialize(); + } + + if (hLock) + { + ::ReleaseMutex(hLock); + ::CloseHandle(hLock); + } + + ReleaseHandle(hCacheThread); + + UserExperienceOnApplyComplete(&pEngineState->userExperience, hr, restart, &applyCompleteAction); + if (BOOTSTRAPPER_APPLYCOMPLETE_ACTION_RESTART == applyCompleteAction) + { + pEngineState->fRestart = TRUE; + } + + LogId(REPORT_STANDARD, MSG_APPLY_COMPLETE, hr, LoggingRestartToString(restart), LoggingBoolToString(pEngineState->fRestart)); + + return hr; +} + +extern "C" HRESULT CoreLaunchApprovedExe( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe + ) +{ + HRESULT hr = S_OK; + DWORD dwProcessId = 0; + + LogId(REPORT_STANDARD, MSG_LAUNCH_APPROVED_EXE_BEGIN, pLaunchApprovedExe->sczId); + + hr = UserExperienceOnLaunchApprovedExeBegin(&pEngineState->userExperience); + ExitOnRootFailure(hr, "BA aborted LaunchApprovedExe begin."); + + // Elevate. + hr = CoreElevate(pEngineState, pLaunchApprovedExe->hwndParent); + ExitOnFailure(hr, "Failed to elevate."); + + // Launch. + hr = ElevationLaunchApprovedExe(pEngineState->companionConnection.hPipe, pLaunchApprovedExe, &dwProcessId); + +LExit: + UserExperienceOnLaunchApprovedExeComplete(&pEngineState->userExperience, hr, dwProcessId); + + LogId(REPORT_STANDARD, MSG_LAUNCH_APPROVED_EXE_COMPLETE, hr, dwProcessId); + + ApprovedExesUninitializeLaunch(pLaunchApprovedExe); + + return hr; +} + +extern "C" HRESULT CoreQuit( + __in BURN_ENGINE_STATE* pEngineState, + __in int nExitCode + ) +{ + HRESULT hr = S_OK; + + // Save engine state if resume mode is unequal to "none". + if (BURN_RESUME_MODE_NONE != pEngineState->resumeMode) + { + hr = CoreSaveEngineState(pEngineState); + if (FAILED(hr)) + { + LogErrorId(hr, MSG_STATE_NOT_SAVED); + hr = S_OK; + } + } + + LogId(REPORT_STANDARD, MSG_QUIT, nExitCode); + + pEngineState->fQuit = TRUE; + + ::PostQuitMessage(nExitCode); // go bye-bye. + + return hr; +} + +extern "C" HRESULT CoreSaveEngineState( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + BYTE* pbBuffer = NULL; + SIZE_T cbBuffer = 0; + + // serialize engine state + hr = CoreSerializeEngineState(pEngineState, &pbBuffer, &cbBuffer); + ExitOnFailure(hr, "Failed to serialize engine state."); + + // write to registration store + if (pEngineState->registration.fPerMachine) + { + hr = ElevationSaveState(pEngineState->companionConnection.hPipe, pbBuffer, cbBuffer); + ExitOnFailure(hr, "Failed to save engine state in per-machine process."); + } + else + { + hr = RegistrationSaveState(&pEngineState->registration, pbBuffer, cbBuffer); + ExitOnFailure(hr, "Failed to save engine state."); + } + +LExit: + ReleaseBuffer(pbBuffer); + + return hr; +} + +extern "C" LPCWSTR CoreRelationTypeToCommandLineString( + __in BOOTSTRAPPER_RELATION_TYPE relationType + ) +{ + LPCWSTR wzRelationTypeCommandLine = NULL; + switch (relationType) + { + case BOOTSTRAPPER_RELATION_DETECT: + wzRelationTypeCommandLine = BURN_COMMANDLINE_SWITCH_RELATED_DETECT; + break; + case BOOTSTRAPPER_RELATION_UPGRADE: + wzRelationTypeCommandLine = BURN_COMMANDLINE_SWITCH_RELATED_UPGRADE; + break; + case BOOTSTRAPPER_RELATION_ADDON: + wzRelationTypeCommandLine = BURN_COMMANDLINE_SWITCH_RELATED_ADDON; + break; + case BOOTSTRAPPER_RELATION_PATCH: + wzRelationTypeCommandLine = BURN_COMMANDLINE_SWITCH_RELATED_PATCH; + break; + case BOOTSTRAPPER_RELATION_UPDATE: + wzRelationTypeCommandLine = BURN_COMMANDLINE_SWITCH_RELATED_UPDATE; + break; + case BOOTSTRAPPER_RELATION_DEPENDENT: + break; + case BOOTSTRAPPER_RELATION_NONE: __fallthrough; + default: + wzRelationTypeCommandLine = NULL; + break; + } + + return wzRelationTypeCommandLine; +} + +extern "C" HRESULT CoreRecreateCommandLine( + __deref_inout_z LPWSTR* psczCommandLine, + __in BOOTSTRAPPER_ACTION action, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RESTART restart, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BOOL fPassthrough, + __in_z_opt LPCWSTR wzActiveParent, + __in_z_opt LPCWSTR wzAncestors, + __in_z_opt LPCWSTR wzAppendLogPath, + __in_z_opt LPCWSTR wzAdditionalCommandLineArguments + ) +{ + HRESULT hr = S_OK; + LPWSTR scz = NULL; + LPCWSTR wzRelationTypeCommandLine = CoreRelationTypeToCommandLineString(relationType); + + hr = StrAllocString(psczCommandLine, L"", 0); + ExitOnFailure(hr, "Failed to empty command line."); + + switch (display) + { + case BOOTSTRAPPER_DISPLAY_NONE: + hr = StrAllocConcat(psczCommandLine, L" /quiet", 0); + break; + case BOOTSTRAPPER_DISPLAY_PASSIVE: + hr = StrAllocConcat(psczCommandLine, L" /passive", 0); + break; + } + ExitOnFailure(hr, "Failed to append display state to command-line"); + + switch (action) + { + case BOOTSTRAPPER_ACTION_MODIFY: + hr = StrAllocConcat(psczCommandLine, L" /modify", 0); + break; + case BOOTSTRAPPER_ACTION_REPAIR: + hr = StrAllocConcat(psczCommandLine, L" /repair", 0); + break; + case BOOTSTRAPPER_ACTION_UNINSTALL: + hr = StrAllocConcat(psczCommandLine, L" /uninstall", 0); + break; + } + ExitOnFailure(hr, "Failed to append action state to command-line"); + + switch (restart) + { + case BOOTSTRAPPER_RESTART_ALWAYS: + hr = StrAllocConcat(psczCommandLine, L" /forcerestart", 0); + break; + case BOOTSTRAPPER_RESTART_NEVER: + hr = StrAllocConcat(psczCommandLine, L" /norestart", 0); + break; + } + ExitOnFailure(hr, "Failed to append restart state to command-line"); + + if (wzActiveParent) + { + if (*wzActiveParent) + { + hr = StrAllocFormatted(&scz, L" /%ls \"%ls\"", BURN_COMMANDLINE_SWITCH_PARENT, wzActiveParent); + ExitOnFailure(hr, "Failed to format active parent command-line for command-line."); + } + else + { + hr = StrAllocFormatted(&scz, L" /%ls", BURN_COMMANDLINE_SWITCH_PARENT_NONE); + ExitOnFailure(hr, "Failed to format parent:none command-line for command-line."); + } + + hr = StrAllocConcat(psczCommandLine, scz, 0); + ExitOnFailure(hr, "Failed to append active parent command-line to command-line."); + } + + if (wzAncestors) + { + hr = StrAllocFormatted(&scz, L" /%ls=%ls", BURN_COMMANDLINE_SWITCH_ANCESTORS, wzAncestors); + ExitOnFailure(hr, "Failed to format ancestors for command-line."); + + hr = StrAllocConcat(psczCommandLine, scz, 0); + ExitOnFailure(hr, "Failed to append ancestors to command-line."); + } + + if (wzRelationTypeCommandLine) + { + hr = StrAllocFormatted(&scz, L" /%ls", wzRelationTypeCommandLine); + ExitOnFailure(hr, "Failed to format relation type for command-line."); + + hr = StrAllocConcat(psczCommandLine, scz, 0); + ExitOnFailure(hr, "Failed to append relation type to command-line."); + } + + if (fPassthrough) + { + hr = StrAllocFormatted(&scz, L" /%ls", BURN_COMMANDLINE_SWITCH_PASSTHROUGH); + ExitOnFailure(hr, "Failed to format passthrough for command-line."); + + hr = StrAllocConcat(psczCommandLine, scz, 0); + ExitOnFailure(hr, "Failed to append passthrough to command-line."); + } + + if (wzAppendLogPath && *wzAppendLogPath) + { + hr = StrAllocFormatted(&scz, L" /%ls \"%ls\"", BURN_COMMANDLINE_SWITCH_LOG_APPEND, wzAppendLogPath); + ExitOnFailure(hr, "Failed to format append log command-line for command-line."); + + hr = StrAllocConcat(psczCommandLine, scz, 0); + ExitOnFailure(hr, "Failed to append log command-line to command-line"); + } + + if (wzAdditionalCommandLineArguments && *wzAdditionalCommandLineArguments) + { + hr = StrAllocConcat(psczCommandLine, L" ", 0); + ExitOnFailure(hr, "Failed to append space to command-line."); + + hr = StrAllocConcat(psczCommandLine, wzAdditionalCommandLineArguments, 0); + ExitOnFailure(hr, "Failed to append command-line to command-line."); + } + +LExit: + ReleaseStr(scz); + + return hr; +} + +extern "C" HRESULT CoreAppendFileHandleAttachedToCommandLine( + __in HANDLE hFileWithAttachedContainer, + __out HANDLE* phExecutableFile, + __deref_inout_z LPWSTR* psczCommandLine + ) +{ + HRESULT hr = S_OK; + HANDLE hExecutableFile = INVALID_HANDLE_VALUE; + + *phExecutableFile = INVALID_HANDLE_VALUE; + + if (!::DuplicateHandle(::GetCurrentProcess(), hFileWithAttachedContainer, ::GetCurrentProcess(), &hExecutableFile, 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + ExitWithLastError(hr, "Failed to duplicate file handle for attached container."); + } + + hr = StrAllocFormattedSecure(psczCommandLine, L"%ls -%ls=%Iu", *psczCommandLine, BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED, reinterpret_cast(hExecutableFile)); + ExitOnFailure(hr, "Failed to append the file handle to the command line."); + + *phExecutableFile = hExecutableFile; + hExecutableFile = INVALID_HANDLE_VALUE; + +LExit: + ReleaseFileHandle(hExecutableFile); + + return hr; +} + +extern "C" HRESULT CoreAppendFileHandleSelfToCommandLine( + __in LPCWSTR wzExecutablePath, + __out HANDLE* phExecutableFile, + __deref_inout_z LPWSTR* psczCommandLine, + __deref_inout_z_opt LPWSTR* psczObfuscatedCommandLine + ) +{ + HRESULT hr = S_OK; + HANDLE hExecutableFile = INVALID_HANDLE_VALUE; + SECURITY_ATTRIBUTES securityAttributes = { }; + securityAttributes.bInheritHandle = TRUE; + *phExecutableFile = INVALID_HANDLE_VALUE; + + hExecutableFile = ::CreateFileW(wzExecutablePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, &securityAttributes, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE != hExecutableFile) + { + hr = StrAllocFormattedSecure(psczCommandLine, L"%ls -%ls=%Iu", *psczCommandLine, BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF, reinterpret_cast(hExecutableFile)); + ExitOnFailure(hr, "Failed to append the file handle to the command line."); + + if (psczObfuscatedCommandLine) + { + hr = StrAllocFormatted(psczObfuscatedCommandLine, L"%ls -%ls=%Iu", *psczObfuscatedCommandLine, BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF, reinterpret_cast(hExecutableFile)); + ExitOnFailure(hr, "Failed to append the file handle to the obfuscated command line."); + } + + *phExecutableFile = hExecutableFile; + hExecutableFile = INVALID_HANDLE_VALUE; + } + +LExit: + ReleaseFileHandle(hExecutableFile); + + return hr; +} + +extern "C" void CoreCleanup( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + LONGLONG llValue = 0; + BOOL fNeedsElevation = pEngineState->registration.fPerMachine && INVALID_HANDLE_VALUE == pEngineState->companionConnection.hPipe; + + LogId(REPORT_STANDARD, MSG_CLEANUP_BEGIN); + + if (pEngineState->plan.fAffectedMachineState) + { + LogId(REPORT_STANDARD, MSG_CLEANUP_SKIPPED_APPLY); + ExitFunction(); + } + + if (fNeedsElevation) + { + hr = VariableGetNumeric(&pEngineState->variables, BURN_BUNDLE_ELEVATED, &llValue); + ExitOnFailure(hr, "Failed to get value of WixBundleElevated variable during cleanup"); + + if (llValue) + { + fNeedsElevation = FALSE; + } + else + { + LogId(REPORT_STANDARD, MSG_CLEANUP_SKIPPED_ELEVATION_REQUIRED); + ExitFunction(); + } + } + + if (!pEngineState->fDetected) + { + hr = CoreDetect(pEngineState, pEngineState->hMessageWindow); + ExitOnFailure(hr, "Detect during cleanup failed"); + } + + if (!pEngineState->registration.fEligibleForCleanup) + { + ExitFunction(); + } + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNINSTALL); + ExitOnFailure(hr, "Plan during cleanup failed"); + + hr = CoreApply(pEngineState, pEngineState->hMessageWindow); + ExitOnFailure(hr, "Apply during cleanup failed"); + +LExit: + LogId(REPORT_STANDARD, MSG_CLEANUP_COMPLETE, hr); +} + +// internal helper functions + +static HRESULT ParseCommandLine( + __in int argc, + __in LPWSTR* argv, + __in BOOTSTRAPPER_COMMAND* pCommand, + __in BURN_PIPE_CONNECTION* pCompanionConnection, + __in BURN_PIPE_CONNECTION* pEmbeddedConnection, + __in BURN_VARIABLES* pVariables, + __out BURN_MODE* pMode, + __out BURN_AU_PAUSE_ACTION* pAutomaticUpdates, + __out BOOL* pfDisableSystemRestore, + __out_z LPWSTR* psczSourceProcessPath, + __out_z LPWSTR* psczOriginalSource, + __out BOOL* pfDisableUnelevate, + __out DWORD *pdwLoggingAttributes, + __out_z LPWSTR* psczLogFile, + __out_z LPWSTR* psczActiveParent, + __out_z LPWSTR* psczIgnoreDependencies, + __out_z LPWSTR* psczAncestors, + __out_z LPWSTR* psczSanitizedCommandLine + ) +{ + HRESULT hr = S_OK; + BOOL fUnknownArg = FALSE; + BOOL fHidden = FALSE; + LPWSTR sczCommandLine = NULL; + LPWSTR sczSanitizedArgument = NULL; + LPWSTR sczVariableName = NULL; + + for (int i = 0; i < argc; ++i) + { + fUnknownArg = FALSE; + int originalIndex = i; + ReleaseNullStr(sczSanitizedArgument); + + if (argv[i][0] == L'-' || argv[i][0] == L'/') + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"l", -1) || + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"log", -1) || + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"xlog", -1)) + { + *pdwLoggingAttributes &= ~BURN_LOGGING_ATTRIBUTE_APPEND; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], 1, L"x", 1)) + { + *pdwLoggingAttributes |= BURN_LOGGING_ATTRIBUTE_VERBOSE | BURN_LOGGING_ATTRIBUTE_EXTRADEBUG; + } + + if (i + 1 >= argc) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a path for log."); + } + + ++i; + + hr = StrAllocString(psczLogFile, argv[i], 0); + ExitOnFailure(hr, "Failed to copy log file path."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"?", -1) || + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"h", -1) || + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"help", -1)) + { + pCommand->action = BOOTSTRAPPER_ACTION_HELP; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"q", -1) || + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"quiet", -1) || + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"s", -1) || + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"silent", -1)) + { + pCommand->display = BOOTSTRAPPER_DISPLAY_NONE; + + if (BOOTSTRAPPER_RESTART_UNKNOWN == pCommand->restart) + { + pCommand->restart = BOOTSTRAPPER_RESTART_AUTOMATIC; + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"passive", -1)) + { + pCommand->display = BOOTSTRAPPER_DISPLAY_PASSIVE; + + if (BOOTSTRAPPER_RESTART_UNKNOWN == pCommand->restart) + { + pCommand->restart = BOOTSTRAPPER_RESTART_AUTOMATIC; + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"norestart", -1)) + { + pCommand->restart = BOOTSTRAPPER_RESTART_NEVER; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"forcerestart", -1)) + { + pCommand->restart = BOOTSTRAPPER_RESTART_ALWAYS; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"promptrestart", -1)) + { + pCommand->restart = BOOTSTRAPPER_RESTART_PROMPT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"layout", -1)) + { + if (BOOTSTRAPPER_ACTION_HELP != pCommand->action) + { + pCommand->action = BOOTSTRAPPER_ACTION_LAYOUT; + } + + // If there is another command line argument and it is not a switch, use that as the layout directory. + if (i + 1 < argc && argv[i + 1][0] != L'-' && argv[i + 1][0] != L'/') + { + ++i; + + hr = PathExpand(&pCommand->wzLayoutDirectory, argv[i], PATH_EXPAND_ENVIRONMENT | PATH_EXPAND_FULLPATH); + ExitOnFailure(hr, "Failed to copy path for layout directory."); + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"uninstall", -1)) + { + if (BOOTSTRAPPER_ACTION_HELP != pCommand->action) + { + pCommand->action = BOOTSTRAPPER_ACTION_UNINSTALL; + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"repair", -1)) + { + if (BOOTSTRAPPER_ACTION_HELP != pCommand->action) + { + pCommand->action = BOOTSTRAPPER_ACTION_REPAIR; + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"modify", -1)) + { + if (BOOTSTRAPPER_ACTION_HELP != pCommand->action) + { + pCommand->action = BOOTSTRAPPER_ACTION_MODIFY; + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"package", -1) || + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"update", -1)) + { + if (BOOTSTRAPPER_ACTION_UNKNOWN == pCommand->action) + { + pCommand->action = BOOTSTRAPPER_ACTION_INSTALL; + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"noaupause", -1)) + { + *pAutomaticUpdates = BURN_AU_PAUSE_ACTION_NONE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"keepaupaused", -1)) + { + // Switch /noaupause takes precedence. + if (BURN_AU_PAUSE_ACTION_NONE != *pAutomaticUpdates) + { + *pAutomaticUpdates = BURN_AU_PAUSE_ACTION_IFELEVATED_NORESUME; + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"disablesystemrestore", -1)) + { + *pfDisableSystemRestore = TRUE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"originalsource", -1)) + { + if (i + 1 >= argc) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a path for original source."); + } + + ++i; + hr = StrAllocString(psczOriginalSource, argv[i], 0); + ExitOnFailure(hr, "Failed to copy last used source."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_PARENT, -1)) + { + if (i + 1 >= argc) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a value for parent."); + } + + ++i; + + hr = StrAllocString(psczActiveParent, argv[i], 0); + ExitOnFailure(hr, "Failed to copy parent."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_PARENT_NONE, -1)) + { + hr = StrAllocString(psczActiveParent, L"", 0); + ExitOnFailure(hr, "Failed to initialize parent to none."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_LOG_APPEND, -1)) + { + if (i + 1 >= argc) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a path for append log."); + } + + ++i; + + hr = StrAllocString(psczLogFile, argv[i], 0); + ExitOnFailure(hr, "Failed to copy append log file path."); + + *pdwLoggingAttributes |= BURN_LOGGING_ATTRIBUTE_APPEND; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_ELEVATED, -1)) + { + if (i + 3 >= argc) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Must specify the elevated name, token and parent process id."); + } + + if (BURN_MODE_UNTRUSTED != *pMode) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Multiple mode command-line switches were provided."); + } + + *pMode = BURN_MODE_ELEVATED; + + ++i; + + hr = ParsePipeConnection(argv + i, pCompanionConnection); + ExitOnFailure(hr, "Failed to parse elevated connection."); + + i += 2; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_CLEAN_ROOM), BURN_COMMANDLINE_SWITCH_CLEAN_ROOM, lstrlenW(BURN_COMMANDLINE_SWITCH_CLEAN_ROOM))) + { + // Get a pointer to the next character after the switch. + LPCWSTR wzParam = &argv[i][1 + lstrlenW(BURN_COMMANDLINE_SWITCH_CLEAN_ROOM)]; + if (L'=' != wzParam[0] || L'\0' == wzParam[1]) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Missing required parameter for switch: %ls", BURN_COMMANDLINE_SWITCH_CLEAN_ROOM); + } + + if (BURN_MODE_UNTRUSTED != *pMode) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Multiple mode command-line switches were provided."); + } + + *pMode = BURN_MODE_NORMAL; + + hr = StrAllocString(psczSourceProcessPath, wzParam + 1, 0); + ExitOnFailure(hr, "Failed to copy source process path."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_EMBEDDED, -1)) + { + if (i + 3 >= argc) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Must specify the embedded name, token and parent process id."); + } + + switch (*pMode) + { + case BURN_MODE_UNTRUSTED: + // Leave mode as UNTRUSTED to launch the clean room process. + break; + case BURN_MODE_NORMAL: + // The initialization code already assumes that the + // clean room switch is at the beginning of the command line, + // so it's safe to assume that the mode is NORMAL in the clean room. + *pMode = BURN_MODE_EMBEDDED; + break; + default: + ExitOnRootFailure(hr = E_INVALIDARG, "Multiple mode command-line switches were provided."); + } + + ++i; + + hr = ParsePipeConnection(argv + i, pEmbeddedConnection); + ExitOnFailure(hr, "Failed to parse embedded connection."); + + i += 2; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_RELATED_DETECT, -1)) + { + pCommand->relationType = BOOTSTRAPPER_RELATION_DETECT; + + LogId(REPORT_STANDARD, MSG_BURN_RUN_BY_RELATED_BUNDLE, LoggingRelationTypeToString(pCommand->relationType)); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_RELATED_UPGRADE, -1)) + { + pCommand->relationType = BOOTSTRAPPER_RELATION_UPGRADE; + + LogId(REPORT_STANDARD, MSG_BURN_RUN_BY_RELATED_BUNDLE, LoggingRelationTypeToString(pCommand->relationType)); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_RELATED_ADDON, -1)) + { + pCommand->relationType = BOOTSTRAPPER_RELATION_ADDON; + + LogId(REPORT_STANDARD, MSG_BURN_RUN_BY_RELATED_BUNDLE, LoggingRelationTypeToString(pCommand->relationType)); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_RELATED_PATCH, -1)) + { + pCommand->relationType = BOOTSTRAPPER_RELATION_PATCH; + + LogId(REPORT_STANDARD, MSG_BURN_RUN_BY_RELATED_BUNDLE, LoggingRelationTypeToString(pCommand->relationType)); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_RELATED_UPDATE, -1)) + { + pCommand->relationType = BOOTSTRAPPER_RELATION_UPDATE; + + LogId(REPORT_STANDARD, MSG_BURN_RUN_BY_RELATED_BUNDLE, LoggingRelationTypeToString(pCommand->relationType)); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_PASSTHROUGH, -1)) + { + pCommand->fPassthrough = TRUE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_DISABLE_UNELEVATE, -1)) + { + *pfDisableUnelevate = TRUE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, BURN_COMMANDLINE_SWITCH_RUNONCE, -1)) + { + if (BURN_MODE_UNTRUSTED != *pMode) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Multiple mode command-line switches were provided."); + } + + *pMode = BURN_MODE_RUNONCE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES), BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES, lstrlenW(BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES))) + { + // Get a pointer to the next character after the switch. + LPCWSTR wzParam = &argv[i][1 + lstrlenW(BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES)]; + if (L'=' != wzParam[0] || L'\0' == wzParam[1]) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Missing required parameter for switch: %ls", BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES); + } + + hr = StrAllocString(psczIgnoreDependencies, &wzParam[1], 0); + ExitOnFailure(hr, "Failed to allocate the list of dependencies to ignore."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_ANCESTORS), BURN_COMMANDLINE_SWITCH_ANCESTORS, lstrlenW(BURN_COMMANDLINE_SWITCH_ANCESTORS))) + { + // Get a pointer to the next character after the switch. + LPCWSTR wzParam = &argv[i][1 + lstrlenW(BURN_COMMANDLINE_SWITCH_ANCESTORS)]; + if (L'=' != wzParam[0] || L'\0' == wzParam[1]) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Missing required parameter for switch: %ls", BURN_COMMANDLINE_SWITCH_ANCESTORS); + } + + hr = StrAllocString(psczAncestors, &wzParam[1], 0); + ExitOnFailure(hr, "Failed to allocate the list of ancestors."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED), BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED, lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED))) + { + // Already processed in InitializeEngineState. + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF), BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF, lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF))) + { + // Already processed in InitializeEngineState. + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_PREFIX), BURN_COMMANDLINE_SWITCH_PREFIX, lstrlenW(BURN_COMMANDLINE_SWITCH_PREFIX))) + { + // Skip (but log) any other private burn switches we don't recognize, so that + // adding future private variables doesn't break old bundles + LogId(REPORT_STANDARD, MSG_BURN_UNKNOWN_PRIVATE_SWITCH, &argv[i][1]); + } + else + { + fUnknownArg = TRUE; + } + } + else + { + fUnknownArg = TRUE; + + const wchar_t* pwc = wcschr(argv[i], L'='); + if (pwc) + { + hr = StrAllocString(&sczVariableName, argv[i], pwc - argv[i]); + ExitOnFailure(hr, "Failed to copy variable name."); + + hr = VariableIsHidden(pVariables, sczVariableName, &fHidden); + ExitOnFailure(hr, "Failed to determine whether variable is hidden."); + + if (fHidden) + { + hr = StrAllocFormatted(&sczSanitizedArgument, L"%ls=*****", sczVariableName); + ExitOnFailure(hr, "Failed to copy sanitized argument."); + } + } + } + + // Remember command-line switch to pass off to UX. + if (fUnknownArg) + { + PathCommandLineAppend(&pCommand->wzCommandLine, argv[i]); + } + + if (sczSanitizedArgument) + { + PathCommandLineAppend(psczSanitizedCommandLine, sczSanitizedArgument); + } + else + { + for (; originalIndex <= i; ++originalIndex) + { + PathCommandLineAppend(psczSanitizedCommandLine, argv[originalIndex]); + } + } + } + + // If embedded, ensure the display goes embedded as well. + if (BURN_MODE_EMBEDDED == *pMode) + { + pCommand->display = BOOTSTRAPPER_DISPLAY_EMBEDDED; + } + + // Set the defaults if nothing was set above. + if (BOOTSTRAPPER_ACTION_UNKNOWN == pCommand->action) + { + pCommand->action = BOOTSTRAPPER_ACTION_INSTALL; + } + + if (BOOTSTRAPPER_DISPLAY_UNKNOWN == pCommand->display) + { + pCommand->display = BOOTSTRAPPER_DISPLAY_FULL; + } + + if (BOOTSTRAPPER_RESTART_UNKNOWN == pCommand->restart) + { + pCommand->restart = BOOTSTRAPPER_RESTART_PROMPT; + } + +LExit: + ReleaseStr(sczVariableName); + ReleaseStr(sczSanitizedArgument); + ReleaseStr(sczCommandLine); + + return hr; +} + +static HRESULT ParsePipeConnection( + __in_ecount(3) LPWSTR* rgArgs, + __in BURN_PIPE_CONNECTION* pConnection + ) +{ + HRESULT hr = S_OK; + + hr = StrAllocString(&pConnection->sczName, rgArgs[0], 0); + ExitOnFailure(hr, "Failed to copy connection name from command line."); + + hr = StrAllocString(&pConnection->sczSecret, rgArgs[1], 0); + ExitOnFailure(hr, "Failed to copy connection secret from command line."); + + hr = StrStringToUInt32(rgArgs[2], 0, reinterpret_cast(&pConnection->dwProcessId)); + ExitOnFailure(hr, "Failed to copy parent process id from command line."); + +LExit: + return hr; +} + +static HRESULT DetectPackage( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + BOOL fBegan = FALSE; + + fBegan = TRUE; + hr = UserExperienceOnDetectPackageBegin(&pEngineState->userExperience, pPackage->sczId); + ExitOnRootFailure(hr, "BA aborted detect package begin."); + + // Detect the cache state of the package. + hr = DetectPackagePayloadsCached(pPackage); + ExitOnFailure(hr, "Failed to detect if payloads are all cached for package: %ls", pPackage->sczId); + + // Use the correct engine to detect the package. + switch (pPackage->type) + { + case BURN_PACKAGE_TYPE_EXE: + hr = ExeEngineDetectPackage(pPackage, &pEngineState->variables); + break; + + case BURN_PACKAGE_TYPE_MSI: + hr = MsiEngineDetectPackage(pPackage, &pEngineState->userExperience); + break; + + case BURN_PACKAGE_TYPE_MSP: + hr = MspEngineDetectPackage(pPackage, &pEngineState->userExperience); + break; + + case BURN_PACKAGE_TYPE_MSU: + hr = MsuEngineDetectPackage(pPackage, &pEngineState->variables); + break; + + default: + hr = E_NOTIMPL; + ExitOnRootFailure(hr, "Package type not supported by detect yet."); + } + +LExit: + if (FAILED(hr)) + { + LogErrorId(hr, MSG_FAILED_DETECT_PACKAGE, pPackage->sczId, NULL, NULL); + } + + if (fBegan) + { + UserExperienceOnDetectPackageComplete(&pEngineState->userExperience, pPackage->sczId, hr, pPackage->currentState, pPackage->fCached); + } + + return hr; +} + +static HRESULT DetectPackagePayloadsCached( + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCachePath = NULL; + BOOL fCached = FALSE; // assume the package is not cached. + LPWSTR sczPayloadCachePath = NULL; + + if (pPackage->sczCacheId && *pPackage->sczCacheId) + { + hr = CacheGetCompletedPath(pPackage->fPerMachine, pPackage->sczCacheId, &sczCachePath); + ExitOnFailure(hr, "Failed to get completed cache path."); + + // If the cached directory exists, we have something. + if (DirExists(sczCachePath, NULL)) + { + // Check all payloads to see if any exist. + for (DWORD i = 0; i < pPackage->payloads.cItems; ++i) + { + BURN_PAYLOAD* pPayload = pPackage->payloads.rgItems[i].pPayload; + + hr = PathConcat(sczCachePath, pPayload->sczFilePath, &sczPayloadCachePath); + ExitOnFailure(hr, "Failed to concat payload cache path."); + + if (FileExistsEx(sczPayloadCachePath, NULL)) + { + fCached = TRUE; + break; + } + else + { + LogId(REPORT_STANDARD, MSG_DETECT_PACKAGE_NOT_FULLY_CACHED, pPackage->sczId, pPayload->sczKey); + } + } + } + } + + pPackage->fCached = fCached; + + if (pPackage->fCanAffectRegistration) + { + pPackage->cacheRegistrationState = pPackage->fCached ? BURN_PACKAGE_REGISTRATION_STATE_PRESENT : BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + +LExit: + ReleaseStr(sczPayloadCachePath); + ReleaseStr(sczCachePath); + return hr; +} + +static DWORD WINAPI CacheThreadProc( + __in LPVOID lpThreadParameter + ) +{ + HRESULT hr = S_OK; + BURN_CACHE_THREAD_CONTEXT* pContext = reinterpret_cast(lpThreadParameter); + BURN_ENGINE_STATE* pEngineState = pContext->pEngineState; + DWORD* pcOverallProgressTicks = pContext->pcOverallProgressTicks; + BOOL* pfRollback = pContext->pfRollback; + BOOL fComInitialized = FALSE; + + // initialize COM + hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); + ExitOnFailure(hr, "Failed to initialize COM on cache thread."); + fComInitialized = TRUE; + + // cache packages + hr = ApplyCache(pEngineState->section.hSourceEngineFile, &pEngineState->userExperience, &pEngineState->variables, &pEngineState->plan, pEngineState->companionConnection.hCachePipe, pcOverallProgressTicks, pfRollback); + +LExit: + UserExperienceExecutePhaseComplete(&pEngineState->userExperience, hr); // signal that cache completed. + + if (fComInitialized) + { + ::CoUninitialize(); + } + + return (DWORD)hr; +} + +static HRESULT WaitForCacheThread( + __in HANDLE hCacheThread + ) +{ + HRESULT hr = S_OK; + + if (WAIT_OBJECT_0 != ::WaitForSingleObject(hCacheThread, INFINITE)) + { + ExitWithLastError(hr, "Failed to wait for cache thread to terminate."); + } + + if (!::GetExitCodeThread(hCacheThread, (DWORD*)&hr)) + { + ExitWithLastError(hr, "Failed to get cache thread exit code."); + } + +LExit: + return hr; +} + +static void LogPackages( + __in_opt const BURN_PACKAGE* pUpgradeBundlePackage, + __in_opt const BURN_PACKAGE* pForwardCompatibleBundlePackage, + __in const BURN_PACKAGES* pPackages, + __in const BURN_RELATED_BUNDLES* pRelatedBundles, + __in const BOOTSTRAPPER_ACTION action + ) +{ + if (pUpgradeBundlePackage) + { + LogId(REPORT_STANDARD, MSG_PLANNED_UPGRADE_BUNDLE, pUpgradeBundlePackage->sczId, LoggingRequestStateToString(pUpgradeBundlePackage->defaultRequested), LoggingRequestStateToString(pUpgradeBundlePackage->requested), LoggingActionStateToString(pUpgradeBundlePackage->execute), LoggingActionStateToString(pUpgradeBundlePackage->rollback), LoggingDependencyActionToString(pUpgradeBundlePackage->dependencyExecute)); + } + else if (pForwardCompatibleBundlePackage) + { + LogId(REPORT_STANDARD, MSG_PLANNED_FORWARD_COMPATIBLE_BUNDLE, pForwardCompatibleBundlePackage->sczId, LoggingRequestStateToString(pForwardCompatibleBundlePackage->defaultRequested), LoggingRequestStateToString(pForwardCompatibleBundlePackage->requested), LoggingActionStateToString(pForwardCompatibleBundlePackage->execute), LoggingActionStateToString(pForwardCompatibleBundlePackage->rollback), LoggingDependencyActionToString(pForwardCompatibleBundlePackage->dependencyExecute)); + } + else + { + // Display related bundles first if uninstalling. + if (BOOTSTRAPPER_ACTION_UNINSTALL == action) + { + LogRelatedBundles(pRelatedBundles, TRUE); + } + + // Display all the packages in the log. + for (DWORD i = 0; i < pPackages->cPackages; ++i) + { + const DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == action) ? pPackages->cPackages - 1 - i : i; + const BURN_PACKAGE* pPackage = &pPackages->rgPackages[iPackage]; + + LogId(REPORT_STANDARD, MSG_PLANNED_PACKAGE, pPackage->sczId, LoggingPackageStateToString(pPackage->currentState), LoggingRequestStateToString(pPackage->defaultRequested), LoggingRequestStateToString(pPackage->requested), LoggingActionStateToString(pPackage->execute), LoggingActionStateToString(pPackage->rollback), LoggingBoolToString(pPackage->fPlannedCache), LoggingBoolToString(pPackage->fPlannedUncache), LoggingDependencyActionToString(pPackage->dependencyExecute), LoggingPackageRegistrationStateToString(pPackage->fCanAffectRegistration, pPackage->expectedInstallRegistrationState), LoggingPackageRegistrationStateToString(pPackage->fCanAffectRegistration, pPackage->expectedCacheRegistrationState)); + + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) + { + if (pPackage->Msi.cFeatures) + { + LogId(REPORT_STANDARD, MSG_PLANNED_MSI_FEATURES, pPackage->Msi.cFeatures, pPackage->sczId); + + for (DWORD j = 0; j < pPackage->Msi.cFeatures; ++j) + { + const BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[j]; + + LogId(REPORT_STANDARD, MSG_PLANNED_MSI_FEATURE, pFeature->sczId, LoggingMsiFeatureStateToString(pFeature->currentState), LoggingMsiFeatureStateToString(pFeature->defaultRequested), LoggingMsiFeatureStateToString(pFeature->requested), LoggingMsiFeatureActionToString(pFeature->execute), LoggingMsiFeatureActionToString(pFeature->rollback)); + } + } + + if (pPackage->Msi.cSlipstreamMspPackages) + { + LogId(REPORT_STANDARD, MSG_PLANNED_SLIPSTREAMED_MSP_TARGETS, pPackage->Msi.cSlipstreamMspPackages, pPackage->sczId); + + for (DWORD j = 0; j < pPackage->Msi.cSlipstreamMspPackages; ++j) + { + const BURN_SLIPSTREAM_MSP* pSlipstreamMsp = &pPackage->Msi.rgSlipstreamMsps[j]; + + LogId(REPORT_STANDARD, MSG_PLANNED_SLIPSTREAMED_MSP_TARGET, pSlipstreamMsp->pMspPackage->sczId, LoggingActionStateToString(pSlipstreamMsp->execute), LoggingActionStateToString(pSlipstreamMsp->rollback)); + } + } + } + else if (BURN_PACKAGE_TYPE_MSP == pPackage->type && pPackage->Msp.cTargetProductCodes) + { + LogId(REPORT_STANDARD, MSG_PLANNED_MSP_TARGETS, pPackage->Msp.cTargetProductCodes, pPackage->sczId); + + for (DWORD j = 0; j < pPackage->Msp.cTargetProductCodes; ++j) + { + const BURN_MSPTARGETPRODUCT* pTargetProduct = &pPackage->Msp.rgTargetProducts[j]; + + LogId(REPORT_STANDARD, MSG_PLANNED_MSP_TARGET, pTargetProduct->wzTargetProductCode, LoggingPackageStateToString(pTargetProduct->patchPackageState), LoggingRequestStateToString(pTargetProduct->defaultRequested), LoggingRequestStateToString(pTargetProduct->requested), LoggingMspTargetActionToString(pTargetProduct->execute, pTargetProduct->executeSkip), LoggingMspTargetActionToString(pTargetProduct->rollback, pTargetProduct->rollbackSkip)); + } + } + } + + // Display related bundles last if caching, installing, modifying, or repairing. + if (BOOTSTRAPPER_ACTION_UNINSTALL < action) + { + LogRelatedBundles(pRelatedBundles, FALSE); + } + } +} + +static void LogRelatedBundles( + __in const BURN_RELATED_BUNDLES* pRelatedBundles, + __in BOOL fReverse + ) +{ + if (0 < pRelatedBundles->cRelatedBundles) + { + for (DWORD i = 0; i < pRelatedBundles->cRelatedBundles; ++i) + { + const DWORD iRelatedBundle = fReverse ? pRelatedBundles->cRelatedBundles - 1 - i : i; + const BURN_RELATED_BUNDLE* pRelatedBundle = pRelatedBundles->rgRelatedBundles + iRelatedBundle; + const BURN_PACKAGE* pPackage = &pRelatedBundle->package; + + if (pRelatedBundle->fPlannable) + { + LogId(REPORT_STANDARD, MSG_PLANNED_RELATED_BUNDLE, pPackage->sczId, LoggingRelationTypeToString(pRelatedBundle->relationType), LoggingRequestStateToString(pPackage->defaultRequested), LoggingRequestStateToString(pPackage->requested), LoggingActionStateToString(pPackage->execute), LoggingActionStateToString(pPackage->rollback), LoggingDependencyActionToString(pPackage->dependencyExecute)); + } + } + } +} diff --git a/src/burn/engine/core.h b/src/burn/engine/core.h new file mode 100644 index 00000000..e96440bb --- /dev/null +++ b/src/burn/engine/core.h @@ -0,0 +1,218 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + +const LPCWSTR BURN_POLICY_REGISTRY_PATH = L"WiX\\Burn"; + +const LPCWSTR BURN_COMMANDLINE_SWITCH_PARENT = L"parent"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_PARENT_NONE = L"parent:none"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_CLEAN_ROOM = L"burn.clean.room"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_ELEVATED = L"burn.elevated"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_EMBEDDED = L"burn.embedded"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_RUNONCE = L"burn.runonce"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_LOG_APPEND = L"burn.log.append"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_RELATED_DETECT = L"burn.related.detect"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_RELATED_UPGRADE = L"burn.related.upgrade"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_RELATED_ADDON = L"burn.related.addon"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_RELATED_PATCH = L"burn.related.patch"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_RELATED_UPDATE = L"burn.related.update"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_PASSTHROUGH = L"burn.passthrough"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_DISABLE_UNELEVATE = L"burn.disable.unelevate"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES = L"burn.ignoredependencies"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_ANCESTORS = L"burn.ancestors"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED = L"burn.filehandle.attached"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF = L"burn.filehandle.self"; +const LPCWSTR BURN_COMMANDLINE_SWITCH_PREFIX = L"burn."; + +const LPCWSTR BURN_BUNDLE_LAYOUT_DIRECTORY = L"WixBundleLayoutDirectory"; +const LPCWSTR BURN_BUNDLE_ACTION = L"WixBundleAction"; +const LPCWSTR BURN_BUNDLE_ACTIVE_PARENT = L"WixBundleActiveParent"; +const LPCWSTR BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER = L"WixBundleExecutePackageCacheFolder"; +const LPCWSTR BURN_BUNDLE_EXECUTE_PACKAGE_ACTION = L"WixBundleExecutePackageAction"; +const LPCWSTR BURN_BUNDLE_FORCED_RESTART_PACKAGE = L"WixBundleForcedRestartPackage"; +const LPCWSTR BURN_BUNDLE_INSTALLED = L"WixBundleInstalled"; +const LPCWSTR BURN_BUNDLE_ELEVATED = L"WixBundleElevated"; +const LPCWSTR BURN_BUNDLE_PROVIDER_KEY = L"WixBundleProviderKey"; +const LPCWSTR BURN_BUNDLE_MANUFACTURER = L"WixBundleManufacturer"; +const LPCWSTR BURN_BUNDLE_SOURCE_PROCESS_PATH = L"WixBundleSourceProcessPath"; +const LPCWSTR BURN_BUNDLE_SOURCE_PROCESS_FOLDER = L"WixBundleSourceProcessFolder"; +const LPCWSTR BURN_BUNDLE_TAG = L"WixBundleTag"; +const LPCWSTR BURN_BUNDLE_UILEVEL = L"WixBundleUILevel"; +const LPCWSTR BURN_BUNDLE_VERSION = L"WixBundleVersion"; +const LPCWSTR BURN_REBOOT_PENDING = L"RebootPending"; + +// The following constants must stay in sync with src\wix\Binder.cs +const LPCWSTR BURN_BUNDLE_NAME = L"WixBundleName"; +const LPCWSTR BURN_BUNDLE_ORIGINAL_SOURCE = L"WixBundleOriginalSource"; +const LPCWSTR BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER = L"WixBundleOriginalSourceFolder"; +const LPCWSTR BURN_BUNDLE_LAST_USED_SOURCE = L"WixBundleLastUsedSource"; + + +// enums + +enum BURN_MODE +{ + BURN_MODE_UNTRUSTED, + BURN_MODE_NORMAL, + BURN_MODE_ELEVATED, + BURN_MODE_EMBEDDED, + BURN_MODE_RUNONCE, +}; + +enum BURN_AU_PAUSE_ACTION +{ + BURN_AU_PAUSE_ACTION_NONE, + BURN_AU_PAUSE_ACTION_IFELEVATED, + BURN_AU_PAUSE_ACTION_IFELEVATED_NORESUME, +}; + + +// structs + +typedef struct _BURN_ENGINE_STATE +{ + // UX flow control + BOOL fDetected; + BOOL fPlanned; + BOOL fQuit; + //BOOL fSuspend; // Is TRUE when UX made Suspend() call on core. + //BOOL fForcedReboot; // Is TRUE when UX made Reboot() call on core. + //BOOL fCancelled; // Is TRUE when UX return cancel on UX OnXXX() methods. + //BOOL fReboot; // Is TRUE when UX confirms OnRestartRequried(). + BOOL fRestart; // Set TRUE when UX returns IDRESTART during Apply(). + + // engine data + BOOTSTRAPPER_COMMAND command; + BURN_SECTION section; + BURN_VARIABLES variables; + BURN_CONDITION condition; + BURN_SEARCHES searches; + BURN_USER_EXPERIENCE userExperience; + BURN_REGISTRATION registration; + BURN_CONTAINERS containers; + BURN_PAYLOADS payloads; + BURN_PACKAGES packages; + BURN_UPDATE update; + BURN_APPROVED_EXES approvedExes; + BURN_EXTENSIONS extensions; + + HWND hMessageWindow; + HANDLE hMessageWindowThread; + + BOOL fDisableRollback; + BOOL fDisableSystemRestore; + BOOL fParallelCacheAndExecute; + + BURN_LOGGING log; + + BURN_PAYLOAD_GROUP layoutPayloads; + + BURN_PLAN plan; + + BURN_MODE mode; + BURN_AU_PAUSE_ACTION automaticUpdates; + + DWORD dwElevatedLoggingTlsId; + + LPWSTR sczBundleEngineWorkingPath; + BURN_PIPE_CONNECTION companionConnection; + BURN_PIPE_CONNECTION embeddedConnection; + + BURN_RESUME_MODE resumeMode; + BOOL fDisableUnelevate; + + LPWSTR sczIgnoreDependencies; + + int argc; + LPWSTR* argv; +} BURN_ENGINE_STATE; + + +// function declarations + +HRESULT CoreInitialize( + __in BURN_ENGINE_STATE* pEngineState + ); +HRESULT CoreInitializeConstants( + __in BURN_ENGINE_STATE* pEngineState + ); +HRESULT CoreSerializeEngineState( + __in BURN_ENGINE_STATE* pEngineState, + __inout BYTE** ppbBuffer, + __inout SIZE_T* piBuffer + ); +HRESULT CoreQueryRegistration( + __in BURN_ENGINE_STATE* pEngineState + ); +//HRESULT CoreDeserializeEngineState( +// __in BURN_ENGINE_STATE* pEngineState, +// __in_bcount(cbBuffer) BYTE* pbBuffer, +// __in SIZE_T cbBuffer +// ); +HRESULT CoreDetect( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HWND hwndParent + ); +HRESULT CorePlan( + __in BURN_ENGINE_STATE* pEngineState, + __in BOOTSTRAPPER_ACTION action + ); +HRESULT CoreElevate( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HWND hwndParent + ); +HRESULT CoreApply( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HWND hwndParent + ); +HRESULT CoreLaunchApprovedExe( + __in BURN_ENGINE_STATE* pEngineState, + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe + ); +HRESULT CoreQuit( + __in BURN_ENGINE_STATE* pEngineState, + __in int nExitCode + ); +HRESULT CoreSaveEngineState( + __in BURN_ENGINE_STATE* pEngineState + ); +LPCWSTR CoreRelationTypeToCommandLineString( + __in BOOTSTRAPPER_RELATION_TYPE relationType + ); +HRESULT CoreRecreateCommandLine( + __deref_inout_z LPWSTR* psczCommandLine, + __in BOOTSTRAPPER_ACTION action, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RESTART restart, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BOOL fPassthrough, + __in_z_opt LPCWSTR wzActiveParent, + __in_z_opt LPCWSTR wzAncestors, + __in_z_opt LPCWSTR wzAppendLogPath, + __in_z_opt LPCWSTR wzAdditionalCommandLineArguments + ); +HRESULT CoreAppendFileHandleAttachedToCommandLine( + __in HANDLE hFileWithAttachedContainer, + __out HANDLE* phExecutableFile, + __deref_inout_z LPWSTR* psczCommandLine + ); +HRESULT CoreAppendFileHandleSelfToCommandLine( + __in LPCWSTR wzExecutablePath, + __out HANDLE* phExecutableFile, + __deref_inout_z LPWSTR* psczCommandLine, + __deref_inout_z_opt LPWSTR* psczObfuscatedCommandLine + ); +void CoreCleanup( + __in BURN_ENGINE_STATE* pEngineState + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/dependency.cpp b/src/burn/engine/dependency.cpp new file mode 100644 index 00000000..876cd8b3 --- /dev/null +++ b/src/burn/engine/dependency.cpp @@ -0,0 +1,1312 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// constants + +#define INITIAL_STRINGDICT_SIZE 48 +const LPCWSTR vcszIgnoreDependenciesDelim = L";"; + + +// internal function declarations + +static HRESULT DetectPackageDependents( + __in BURN_PACKAGE* pPackage, + __in STRINGDICT_HANDLE sdIgnoredDependents, + __in const BURN_REGISTRATION* pRegistration + ); + +static HRESULT SplitIgnoreDependencies( + __in_z LPCWSTR wzIgnoreDependencies, + __deref_inout_ecount_opt(*pcDependencies) DEPENDENCY** prgDependencies, + __inout LPUINT pcDependencies, + __out BOOL* pfIgnoreAll + ); + +static HRESULT JoinIgnoreDependencies( + __out_z LPWSTR* psczIgnoreDependencies, + __in_ecount(cDependencies) const DEPENDENCY* rgDependencies, + __in UINT cDependencies + ); + +static HRESULT GetIgnoredDependents( + __in const BURN_PACKAGE* pPackage, + __in const BURN_PLAN* pPlan, + __deref_inout STRINGDICT_HANDLE* psdIgnoredDependents + ); + +static BOOL GetProviderExists( + __in HKEY hkRoot, + __in_z LPCWSTR wzProviderKey + ); + +static void CalculateDependencyActionStates( + __in const BURN_PACKAGE* pPackage, + __in const BOOTSTRAPPER_ACTION action, + __out BURN_DEPENDENCY_ACTION* pDependencyExecuteAction, + __out BURN_DEPENDENCY_ACTION* pDependencyRollbackAction + ); + +static HRESULT AddPackageDependencyActions( + __in_opt DWORD *pdwInsertSequence, + __in const BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in const BURN_DEPENDENCY_ACTION dependencyExecuteAction, + __in const BURN_DEPENDENCY_ACTION dependencyRollbackAction + ); + +static HRESULT RegisterPackageProvider( + __in const BURN_PACKAGE* pPackage + ); + +static void UnregisterPackageProvider( + __in const BURN_PACKAGE* pPackage + ); + +static HRESULT RegisterPackageDependency( + __in BOOL fPerMachine, + __in const BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzDependentProviderKey + ); + +static void UnregisterPackageDependency( + __in BOOL fPerMachine, + __in const BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzDependentProviderKey + ); + + +// functions + +extern "C" void DependencyUninitializeProvider( + __in BURN_DEPENDENCY_PROVIDER* pProvider + ) +{ + ReleaseStr(pProvider->sczKey); + ReleaseStr(pProvider->sczVersion); + ReleaseStr(pProvider->sczDisplayName); + ReleaseDependencyArray(pProvider->rgDependents, pProvider->cDependents); + + memset(pProvider, 0, sizeof(BURN_DEPENDENCY_PROVIDER)); +} + +extern "C" HRESULT DependencyParseProvidersFromXml( + __in BURN_PACKAGE* pPackage, + __in IXMLDOMNode* pixnPackage + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + DWORD cNodes = 0; + IXMLDOMNode* pixnNode = NULL; + + // Select dependency provider nodes. + hr = XmlSelectNodes(pixnPackage, L"Provides", &pixnNodes); + ExitOnFailure(hr, "Failed to select dependency provider nodes."); + + // Get dependency provider node count. + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get the dependency provider node count."); + + if (!cNodes) + { + ExitFunction1(hr = S_OK); + } + + // Allocate memory for dependency provider pointers. + pPackage->rgDependencyProviders = (BURN_DEPENDENCY_PROVIDER*)MemAlloc(sizeof(BURN_DEPENDENCY_PROVIDER) * cNodes, TRUE); + ExitOnNull(pPackage->rgDependencyProviders, hr, E_OUTOFMEMORY, "Failed to allocate memory for dependency providers."); + + pPackage->cDependencyProviders = cNodes; + + // Parse dependency provider elements. + for (DWORD i = 0; i < cNodes; i++) + { + BURN_DEPENDENCY_PROVIDER* pDependencyProvider = &pPackage->rgDependencyProviders[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get the next dependency provider node."); + + // @Key + hr = XmlGetAttributeEx(pixnNode, L"Key", &pDependencyProvider->sczKey); + ExitOnFailure(hr, "Failed to get the Key attribute."); + + // @Version + hr = XmlGetAttributeEx(pixnNode, L"Version", &pDependencyProvider->sczVersion); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get the Version attribute."); + } + + // @DisplayName + hr = XmlGetAttributeEx(pixnNode, L"DisplayName", &pDependencyProvider->sczDisplayName); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get the DisplayName attribute."); + } + + // @Imported + hr = XmlGetYesNoAttribute(pixnNode, L"Imported", &pDependencyProvider->fImported); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get the Imported attribute."); + } + else + { + pDependencyProvider->fImported = FALSE; + hr = S_OK; + } + + // Prepare next iteration. + ReleaseNullObject(pixnNode); + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNode); + ReleaseObject(pixnNodes); + + return hr; +} + +extern "C" HRESULT DependencyInitialize( + __in BURN_REGISTRATION* pRegistration, + __in_z_opt LPCWSTR wzIgnoreDependencies + ) +{ + HRESULT hr = S_OK; + + // If no parent was specified at all, use the bundle id as the self dependent. + if (!pRegistration->sczActiveParent) + { + pRegistration->wzSelfDependent = pRegistration->sczId; + } + else if (*pRegistration->sczActiveParent) // if parent was specified use that as the self dependent. + { + pRegistration->wzSelfDependent = pRegistration->sczActiveParent; + } + // else parent:none was used which means we should not register a dependency on ourself. + + // The current bundle provider key should always be ignored for dependency checks. + hr = DepDependencyArrayAlloc(&pRegistration->rgIgnoredDependencies, &pRegistration->cIgnoredDependencies, pRegistration->sczProviderKey, NULL); + ExitOnFailure(hr, "Failed to add the bundle provider key to the list of dependencies to ignore."); + + // Add the list of dependencies to ignore. + if (wzIgnoreDependencies) + { + hr = SplitIgnoreDependencies(wzIgnoreDependencies, &pRegistration->rgIgnoredDependencies, &pRegistration->cIgnoredDependencies, &pRegistration->fIgnoreAllDependents); + ExitOnFailure(hr, "Failed to split the list of dependencies to ignore."); + } + +LExit: + return hr; +} + +extern "C" HRESULT DependencyDetectProviderKeyBundleId( + __in BURN_REGISTRATION* pRegistration + ) +{ + HRESULT hr = S_OK; + + hr = DepGetProviderInformation(pRegistration->hkRoot, pRegistration->sczProviderKey, &pRegistration->sczDetectedProviderKeyBundleId, NULL, NULL); + if (E_NOTFOUND == hr) + { + ExitFunction(); + } + ExitOnFailure(hr, "Failed to get provider key bundle id."); + + // If a bundle id was not explicitly set, default the provider key bundle id to this bundle's provider key. + if (!pRegistration->sczDetectedProviderKeyBundleId || !*pRegistration->sczDetectedProviderKeyBundleId) + { + hr = StrAllocString(&pRegistration->sczDetectedProviderKeyBundleId, pRegistration->sczProviderKey, 0); + ExitOnFailure(hr, "Failed to initialize provider key bundle id."); + } + +LExit: + return hr; +} + +extern "C" HRESULT DependencyDetect( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + BURN_REGISTRATION* pRegistration = &pEngineState->registration; + STRINGDICT_HANDLE sdIgnoredDependents = NULL; + BURN_PACKAGE* pPackage = NULL; + BOOL fSelfDependent = NULL != pRegistration->wzSelfDependent; + BOOL fActiveParent = NULL != pRegistration->sczActiveParent && NULL != *pRegistration->sczActiveParent; + + // Always leave this empty so that all dependents get detected. Plan will ignore dependents based on its own logic. + hr = DictCreateStringList(&sdIgnoredDependents, INITIAL_STRINGDICT_SIZE, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create the string dictionary."); + + hr = DepCheckDependents(pRegistration->hkRoot, pRegistration->sczProviderKey, 0, sdIgnoredDependents, &pRegistration->rgDependents, &pRegistration->cDependents); + if (E_FILENOTFOUND != hr) + { + ExitOnFailure(hr, "Failed dependents check on bundle"); + } + else + { + hr = S_OK; + } + + for (DWORD iPackage = 0; iPackage < pEngineState->packages.cPackages; ++iPackage) + { + pPackage = pEngineState->packages.rgPackages + iPackage; + hr = DetectPackageDependents(pPackage, sdIgnoredDependents, pRegistration); + ExitOnFailure(hr, "Failed to detect dependents for package '%ls'", pPackage->sczId); + } + + for (DWORD iRelatedBundle = 0; iRelatedBundle < pEngineState->registration.relatedBundles.cRelatedBundles; ++iRelatedBundle) + { + BURN_RELATED_BUNDLE* pRelatedBundle = pEngineState->registration.relatedBundles.rgRelatedBundles + iRelatedBundle; + if (!pRelatedBundle->fPlannable) + { + continue; + } + + pPackage = &pRelatedBundle->package; + hr = DetectPackageDependents(pPackage, sdIgnoredDependents, pRegistration); + ExitOnFailure(hr, "Failed to detect dependents for related bundle '%ls'", pPackage->sczId); + } + + if (fSelfDependent || fActiveParent) + { + for (DWORD i = 0; i < pRegistration->cDependents; ++i) + { + DEPENDENCY* pDependent = pRegistration->rgDependents + i; + + if (fActiveParent && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pRegistration->sczActiveParent, -1, pDependent->sczKey, -1)) + { + pRegistration->fParentRegisteredAsDependent = TRUE; + } + + if (fSelfDependent && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pRegistration->wzSelfDependent, -1, pDependent->sczKey, -1)) + { + pRegistration->fSelfRegisteredAsDependent = TRUE; + } + } + } + +LExit: + ReleaseDict(sdIgnoredDependents); + + return hr; +} + +extern "C" HRESULT DependencyPlanInitialize( + __in const BURN_REGISTRATION* pRegistration, + __in BURN_PLAN* pPlan + ) +{ + HRESULT hr = S_OK; + + // TODO: After adding enumeration to STRINGDICT, a single STRINGDICT_HANDLE can be used everywhere. + for (DWORD i = 0; i < pRegistration->cIgnoredDependencies; ++i) + { + DEPENDENCY* pDependency = pRegistration->rgIgnoredDependencies + i; + + hr = DepDependencyArrayAlloc(&pPlan->rgPlannedProviders, &pPlan->cPlannedProviders, pDependency->sczKey, pDependency->sczName); + ExitOnFailure(hr, "Failed to add the detected provider to the list of dependencies to ignore."); + } + +LExit: + return hr; +} + +extern "C" HRESULT DependencyAllocIgnoreDependencies( + __in const BURN_PLAN *pPlan, + __out_z LPWSTR* psczIgnoreDependencies + ) +{ + HRESULT hr = S_OK; + + // Join the list of dependencies to ignore for each related bundle. + if (0 < pPlan->cPlannedProviders) + { + hr = JoinIgnoreDependencies(psczIgnoreDependencies, pPlan->rgPlannedProviders, pPlan->cPlannedProviders); + ExitOnFailure(hr, "Failed to join the list of dependencies to ignore."); + } + +LExit: + return hr; +} + +extern "C" HRESULT DependencyAddIgnoreDependencies( + __in STRINGDICT_HANDLE sdIgnoreDependencies, + __in_z LPCWSTR wzAddIgnoreDependencies + ) +{ + HRESULT hr = S_OK; + LPWSTR wzContext = NULL; + + // Parse through the semicolon-delimited tokens and add to the array. + for (LPCWSTR wzToken = ::wcstok_s(const_cast(wzAddIgnoreDependencies), vcszIgnoreDependenciesDelim, &wzContext); wzToken; wzToken = ::wcstok_s(NULL, vcszIgnoreDependenciesDelim, &wzContext)) + { + hr = DictKeyExists(sdIgnoreDependencies, wzToken); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to check the dictionary of unique dependencies."); + } + else + { + hr = DictAddKey(sdIgnoreDependencies, wzToken); + ExitOnFailure(hr, "Failed to add \"%ls\" to the string dictionary.", wzToken); + } + } + +LExit: + return hr; +} + +extern "C" HRESULT DependencyPlanPackageBegin( + __in BOOL fPerMachine, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan + ) +{ + HRESULT hr = S_OK; + STRINGDICT_HANDLE sdIgnoredDependents = NULL; + BURN_DEPENDENCY_ACTION dependencyExecuteAction = BURN_DEPENDENCY_ACTION_NONE; + BURN_DEPENDENCY_ACTION dependencyRollbackAction = BURN_DEPENDENCY_ACTION_NONE; + + pPackage->dependencyExecute = BURN_DEPENDENCY_ACTION_NONE; + pPackage->dependencyRollback = BURN_DEPENDENCY_ACTION_NONE; + + // Make sure the package defines at least one provider. + if (0 == pPackage->cDependencyProviders) + { + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_PACKAGE_SKIP_NOPROVIDERS, pPackage->sczId); + ExitFunction1(hr = S_OK); + } + + // Make sure the package is in the same scope as the bundle. + if (fPerMachine != pPackage->fPerMachine) + { + LogId(REPORT_STANDARD, MSG_DEPENDENCY_PACKAGE_SKIP_WRONGSCOPE, pPackage->sczId, LoggingPerMachineToString(fPerMachine), LoggingPerMachineToString(pPackage->fPerMachine)); + ExitFunction1(hr = S_OK); + } + + // If we're uninstalling the package, check if any dependents are registered. + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) + { + // Build up a list of dependents to ignore, including the current bundle. + hr = GetIgnoredDependents(pPackage, pPlan, &sdIgnoredDependents); + ExitOnFailure(hr, "Failed to build the list of ignored dependents."); + + // Skip the dependency check if "ALL" was authored for IGNOREDEPENDENCIES. + hr = DictKeyExists(sdIgnoredDependents, L"ALL"); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to check if \"ALL\" was set in IGNOREDEPENDENCIES."); + } + else + { + hr = S_OK; + + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + const BURN_DEPENDENCY_PROVIDER* pProvider = pPackage->rgDependencyProviders + i; + + for (DWORD j = 0; j < pProvider->cDependents; ++j) + { + const DEPENDENCY* pDependency = pProvider->rgDependents + j; + + hr = DictKeyExists(sdIgnoredDependents, pDependency->sczKey); + if (E_NOTFOUND == hr) + { + hr = S_OK; + + if (!pPackage->fDependencyManagerWasHere) + { + pPackage->fDependencyManagerWasHere = TRUE; + + LogId(REPORT_STANDARD, MSG_DEPENDENCY_PACKAGE_HASDEPENDENTS, pPackage->sczId); + } + + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_PACKAGE_DEPENDENT, pDependency->sczKey, LoggingStringOrUnknownIfNull(pDependency->sczName)); + } + ExitOnFailure(hr, "Failed to check the dictionary of ignored dependents."); + } + } + } + } + + // Calculate the dependency actions before the package itself is planned. + CalculateDependencyActionStates(pPackage, pPlan->action, &dependencyExecuteAction, &dependencyRollbackAction); + + // If dependents were found, change the action to not uninstall the package. + if (pPackage->fDependencyManagerWasHere) + { + pPackage->execute = BOOTSTRAPPER_ACTION_STATE_NONE; + pPackage->rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + } + else + { + // Use the calculated dependency actions as the provider actions if there + // are any non-imported providers that need to be registered and the package + // is current (not obsolete). + if (BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE != pPackage->currentState) + { + BOOL fAllImportedProviders = TRUE; // assume all providers were imported. + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + const BURN_DEPENDENCY_PROVIDER* pProvider = &pPackage->rgDependencyProviders[i]; + if (!pProvider->fImported) + { + fAllImportedProviders = FALSE; + break; + } + } + + if (!fAllImportedProviders) + { + pPackage->providerExecute = dependencyExecuteAction; + pPackage->providerRollback = dependencyRollbackAction; + } + } + + // If the package will be removed, add its providers to the growing list in the plan. + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) + { + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + const BURN_DEPENDENCY_PROVIDER* pProvider = &pPackage->rgDependencyProviders[i]; + + hr = DepDependencyArrayAlloc(&pPlan->rgPlannedProviders, &pPlan->cPlannedProviders, pProvider->sczKey, NULL); + ExitOnFailure(hr, "Failed to add the package provider key \"%ls\" to the planned list.", pProvider->sczKey); + } + } + } + + pPackage->dependencyExecute = dependencyExecuteAction; + pPackage->dependencyRollback = dependencyRollbackAction; + +LExit: + ReleaseDict(sdIgnoredDependents); + + return hr; +} + +extern "C" HRESULT DependencyPlanPackage( + __in_opt DWORD *pdwInsertSequence, + __in const BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pAction = NULL; + + // If the dependency execution action is to unregister, add the dependency actions to the plan + // *before* the provider key is potentially removed. + if (BURN_DEPENDENCY_ACTION_UNREGISTER == pPackage->dependencyExecute) + { + hr = AddPackageDependencyActions(pdwInsertSequence, pPackage, pPlan, pPackage->dependencyExecute, pPackage->dependencyRollback); + ExitOnFailure(hr, "Failed to plan the dependency actions for package: %ls", pPackage->sczId); + } + + // Add the provider rollback plan. + if (BURN_DEPENDENCY_ACTION_NONE != pPackage->providerRollback) + { + hr = PlanAppendRollbackAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append provider rollback action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_PACKAGE_PROVIDER; + pAction->packageProvider.pPackage = const_cast(pPackage); + pAction->packageProvider.action = pPackage->providerRollback; + + // Put a checkpoint before the execute action so that rollback happens + // if execute fails. + hr = PlanExecuteCheckpoint(pPlan); + ExitOnFailure(hr, "Failed to plan provider checkpoint action."); + } + + // Add the provider execute plan. This comes after rollback so if something goes wrong + // rollback will try to clean up after us. + if (BURN_DEPENDENCY_ACTION_NONE != pPackage->providerExecute) + { + if (NULL != pdwInsertSequence) + { + hr = PlanInsertExecuteAction(*pdwInsertSequence, pPlan, &pAction); + ExitOnFailure(hr, "Failed to insert provider execute action."); + + // Always move the sequence after this dependency action so the provider registration + // stays in front of the inserted actions. + ++(*pdwInsertSequence); + } + else + { + hr = PlanAppendExecuteAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append provider execute action."); + } + + pAction->type = BURN_EXECUTE_ACTION_TYPE_PACKAGE_PROVIDER; + pAction->packageProvider.pPackage = const_cast(pPackage); + pAction->packageProvider.action = pPackage->providerExecute; + } + +LExit: + return hr; +} + +extern "C" HRESULT DependencyPlanPackageComplete( + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan + ) +{ + HRESULT hr = S_OK; + + // Registration of dependencies happens here, after the package is planned to be + // installed and all that good stuff. + if (BURN_DEPENDENCY_ACTION_REGISTER == pPackage->dependencyExecute) + { + // Recalculate the dependency actions in case other operations may have changed + // the package execution state. + CalculateDependencyActionStates(pPackage, pPlan->action, &pPackage->dependencyExecute, &pPackage->dependencyRollback); + + // If the dependency execution action is *still* to register, add the dependency actions to the plan. + if (BURN_DEPENDENCY_ACTION_REGISTER == pPackage->dependencyExecute) + { + hr = AddPackageDependencyActions(NULL, pPackage, pPlan, pPackage->dependencyExecute, pPackage->dependencyRollback); + ExitOnFailure(hr, "Failed to plan the dependency actions for package: %ls", pPackage->sczId); + } + } + +LExit: + return hr; +} + +extern "C" HRESULT DependencyExecutePackageProviderAction( + __in const BURN_EXECUTE_ACTION* pAction + ) +{ + AssertSz(BURN_EXECUTE_ACTION_TYPE_PACKAGE_PROVIDER == pAction->type, "Execute action type not supported by this function."); + + HRESULT hr = S_OK; + const BURN_PACKAGE* pPackage = pAction->packageProvider.pPackage; + + // Register or unregister the package provider(s). + if (BURN_DEPENDENCY_ACTION_REGISTER == pAction->packageProvider.action) + { + hr = RegisterPackageProvider(pPackage); + ExitOnFailure(hr, "Failed to register the package providers."); + } + else if (BURN_DEPENDENCY_ACTION_UNREGISTER == pAction->packageProvider.action) + { + UnregisterPackageProvider(pPackage); + } + +LExit: + if (!pPackage->fVital) + { + hr = S_OK; + } + + return hr; +} + +extern "C" HRESULT DependencyExecutePackageDependencyAction( + __in BOOL fPerMachine, + __in const BURN_EXECUTE_ACTION* pAction + ) +{ + AssertSz(BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY == pAction->type, "Execute action type not supported by this function."); + + HRESULT hr = S_OK; + const BURN_PACKAGE* pPackage = pAction->packageDependency.pPackage; + + // Register or unregister the bundle as a dependent of each package dependency provider. + if (BURN_DEPENDENCY_ACTION_REGISTER == pAction->packageDependency.action) + { + hr = RegisterPackageDependency(fPerMachine, pPackage, pAction->packageDependency.sczBundleProviderKey); + ExitOnFailure(hr, "Failed to register the dependency on the package provider."); + } + else if (BURN_DEPENDENCY_ACTION_UNREGISTER == pAction->packageDependency.action) + { + UnregisterPackageDependency(fPerMachine, pPackage, pAction->packageDependency.sczBundleProviderKey); + } + +LExit: + if (!pPackage->fVital) + { + hr = S_OK; + } + + return hr; +} + +extern "C" HRESULT DependencyRegisterBundle( + __in const BURN_REGISTRATION* pRegistration + ) +{ + HRESULT hr = S_OK; + + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_BUNDLE_REGISTER, pRegistration->sczProviderKey, pRegistration->pVersion->sczVersion); + + // Register the bundle provider key. + hr = DepRegisterDependency(pRegistration->hkRoot, pRegistration->sczProviderKey, pRegistration->pVersion->sczVersion, pRegistration->sczDisplayName, pRegistration->sczId, 0); + ExitOnFailure(hr, "Failed to register the bundle dependency provider."); + +LExit: + return hr; +} + +extern "C" HRESULT DependencyProcessDependentRegistration( + __in const BURN_REGISTRATION* pRegistration, + __in const BURN_DEPENDENT_REGISTRATION_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + + switch (pAction->type) + { + case BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_REGISTER: + hr = DepRegisterDependent(pRegistration->hkRoot, pRegistration->sczProviderKey, pAction->sczDependentProviderKey, NULL, NULL, 0); + ExitOnFailure(hr, "Failed to register dependent: %ls", pAction->sczDependentProviderKey); + break; + + case BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_UNREGISTER: + hr = DepUnregisterDependent(pRegistration->hkRoot, pRegistration->sczProviderKey, pAction->sczDependentProviderKey); + ExitOnFailure(hr, "Failed to unregister dependent: %ls", pAction->sczDependentProviderKey); + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Unrecognized registration action type: %d", pAction->type); + } + +LExit: + return hr; +} + +extern "C" void DependencyUnregisterBundle( + __in const BURN_REGISTRATION* pRegistration, + __in const BURN_PACKAGES* pPackages + ) +{ + HRESULT hr = S_OK; + LPCWSTR wzDependentProviderKey = pRegistration->sczId; + + // Remove the bundle provider key. + hr = DepUnregisterDependency(pRegistration->hkRoot, pRegistration->sczProviderKey); + if (SUCCEEDED(hr)) + { + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_BUNDLE_UNREGISTERED, pRegistration->sczProviderKey); + } + else if (FAILED(hr) && E_FILENOTFOUND != hr) + { + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_BUNDLE_UNREGISTERED_FAILED, pRegistration->sczProviderKey, hr); + } + + // Best effort to make sure this bundle is not registered as a dependent for anything. + for (DWORD i = 0; i < pPackages->cPackages; ++i) + { + const BURN_PACKAGE* pPackage = pPackages->rgPackages + i; + UnregisterPackageDependency(pPackage->fPerMachine, pPackage, wzDependentProviderKey); + } + + for (DWORD i = 0; i < pRegistration->relatedBundles.cRelatedBundles; ++i) + { + const BURN_PACKAGE* pPackage = &pRegistration->relatedBundles.rgRelatedBundles[i].package; + UnregisterPackageDependency(pPackage->fPerMachine, pPackage, wzDependentProviderKey); + } +} + +// internal functions + + +static HRESULT DetectPackageDependents( + __in BURN_PACKAGE* pPackage, + __in STRINGDICT_HANDLE sdIgnoredDependents, + __in const BURN_REGISTRATION* pRegistration + ) +{ + HRESULT hr = S_OK; + HKEY hkHive = pPackage->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + BOOL fCanIgnorePresence = pPackage->fCanAffectRegistration && 0 < pPackage->cDependencyProviders && + (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->cacheRegistrationState || BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->installRegistrationState); + BOOL fBundleRegisteredAsDependent = FALSE; + + // There's currently no point in getting the dependents if the scope doesn't match, + // because they will just get ignored. + if (pRegistration->fPerMachine != pPackage->fPerMachine) + { + ExitFunction(); + } + + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + BURN_DEPENDENCY_PROVIDER* pProvider = &pPackage->rgDependencyProviders[i]; + + hr = DepCheckDependents(hkHive, pProvider->sczKey, 0, sdIgnoredDependents, &pProvider->rgDependents, &pProvider->cDependents); + if (E_FILENOTFOUND != hr) + { + ExitOnFailure(hr, "Failed dependents check on package provider: %ls", pProvider->sczKey); + + if (!pPackage->fPackageProviderExists && (0 < pProvider->cDependents || GetProviderExists(hkHive, pProvider->sczKey))) + { + pPackage->fPackageProviderExists = TRUE; + } + + if (fCanIgnorePresence && !fBundleRegisteredAsDependent) + { + for (DWORD iDependent = 0; iDependent < pProvider->cDependents; ++iDependent) + { + DEPENDENCY* pDependent = pProvider->rgDependents + iDependent; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pRegistration->sczId, -1, pDependent->sczKey, -1)) + { + fBundleRegisteredAsDependent = TRUE; + break; + } + } + } + } + else + { + hr = S_OK; + + if (!pPackage->fPackageProviderExists && GetProviderExists(hkHive, pProvider->sczKey)) + { + pPackage->fPackageProviderExists = TRUE; + } + } + } + + // Older bundles may not have written the id so try the default. + if (!pPackage->fPackageProviderExists && BURN_PACKAGE_TYPE_MSI == pPackage->type && pPackage->Msi.sczProductCode && GetProviderExists(hkHive, pPackage->Msi.sczProductCode)) + { + pPackage->fPackageProviderExists = TRUE; + } + + if (fCanIgnorePresence && !fBundleRegisteredAsDependent) + { + if (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->cacheRegistrationState) + { + pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_IGNORED; + } + if (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->installRegistrationState) + { + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_IGNORED; + } + if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; + + if (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pTargetProduct->registrationState) + { + pTargetProduct->registrationState = BURN_PACKAGE_REGISTRATION_STATE_IGNORED; + } + } + } + } + +LExit: + return hr; +} + +/******************************************************************** + SplitIgnoreDependencies - Splits a semicolon-delimited + string into a list of unique dependencies to ignore. + +*********************************************************************/ +static HRESULT SplitIgnoreDependencies( + __in_z LPCWSTR wzIgnoreDependencies, + __deref_inout_ecount_opt(*pcDependencies) DEPENDENCY** prgDependencies, + __inout LPUINT pcDependencies, + __out BOOL* pfIgnoreAll + ) +{ + HRESULT hr = S_OK; + LPWSTR wzContext = NULL; + STRINGDICT_HANDLE sdIgnoreDependencies = NULL; + *pfIgnoreAll = FALSE; + + // Create a dictionary to hold unique dependencies. + hr = DictCreateStringList(&sdIgnoreDependencies, INITIAL_STRINGDICT_SIZE, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create the string dictionary."); + + // Parse through the semicolon-delimited tokens and add to the array. + for (LPCWSTR wzToken = ::wcstok_s(const_cast(wzIgnoreDependencies), vcszIgnoreDependenciesDelim, &wzContext); wzToken; wzToken = ::wcstok_s(NULL, vcszIgnoreDependenciesDelim, &wzContext)) + { + hr = DictKeyExists(sdIgnoreDependencies, wzToken); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to check the dictionary of unique dependencies."); + } + else + { + hr = DepDependencyArrayAlloc(prgDependencies, pcDependencies, wzToken, NULL); + ExitOnFailure(hr, "Failed to add \"%ls\" to the list of dependencies to ignore.", wzToken); + + hr = DictAddKey(sdIgnoreDependencies, wzToken); + ExitOnFailure(hr, "Failed to add \"%ls\" to the string dictionary.", wzToken); + + if (!*pfIgnoreAll && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, L"ALL", -1, wzToken, -1)) + { + *pfIgnoreAll = TRUE; + } + } + } + +LExit: + ReleaseDict(sdIgnoreDependencies); + + return hr; +} + +/******************************************************************** + JoinIgnoreDependencies - Joins a list of dependencies + to ignore into a semicolon-delimited string of unique values. + +*********************************************************************/ +static HRESULT JoinIgnoreDependencies( + __out_z LPWSTR* psczIgnoreDependencies, + __in_ecount(cDependencies) const DEPENDENCY* rgDependencies, + __in UINT cDependencies + ) +{ + HRESULT hr = S_OK; + STRINGDICT_HANDLE sdIgnoreDependencies = NULL; + + // Make sure we pass back an empty string if there are no dependencies. + if (0 == cDependencies) + { + ExitFunction1(hr = S_OK); + } + + // Create a dictionary to hold unique dependencies. + hr = DictCreateStringList(&sdIgnoreDependencies, INITIAL_STRINGDICT_SIZE, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create the string dictionary."); + + for (UINT i = 0; i < cDependencies; ++i) + { + const DEPENDENCY* pDependency = &rgDependencies[i]; + + hr = DictKeyExists(sdIgnoreDependencies, pDependency->sczKey); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to check the dictionary of unique dependencies."); + } + else + { + if (0 < i) + { + hr = StrAllocConcat(psczIgnoreDependencies, vcszIgnoreDependenciesDelim, 1); + ExitOnFailure(hr, "Failed to append the string delimiter."); + } + + hr = StrAllocConcat(psczIgnoreDependencies, pDependency->sczKey, 0); + ExitOnFailure(hr, "Failed to append the key \"%ls\".", pDependency->sczKey); + + hr = DictAddKey(sdIgnoreDependencies, pDependency->sczKey); + ExitOnFailure(hr, "Failed to add \"%ls\" to the string dictionary.", pDependency->sczKey); + } + } + +LExit: + ReleaseDict(sdIgnoreDependencies); + + return hr; +} + +/******************************************************************** + GetIgnoredDependents - Combines the current bundle's + provider key, packages' provider keys that are being uninstalled, + and any ignored dependencies authored for packages into a string + list to pass to deputil. + +*********************************************************************/ +static HRESULT GetIgnoredDependents( + __in const BURN_PACKAGE* pPackage, + __in const BURN_PLAN* pPlan, + __deref_inout STRINGDICT_HANDLE* psdIgnoredDependents + ) +{ + HRESULT hr = S_OK; + LPWSTR sczIgnoreDependencies = NULL; + + // Create the dictionary and add the bundle provider key initially. + hr = DictCreateStringList(psdIgnoredDependents, INITIAL_STRINGDICT_SIZE, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create the string dictionary."); + + hr = DictAddKey(*psdIgnoredDependents, pPlan->wzBundleProviderKey); + ExitOnFailure(hr, "Failed to add the bundle provider key \"%ls\" to the list of ignored dependencies.", pPlan->wzBundleProviderKey); + + // Add previously planned package providers to the dictionary. + for (DWORD i = 0; i < pPlan->cPlannedProviders; ++i) + { + const DEPENDENCY* pDependency = &pPlan->rgPlannedProviders[i]; + + hr = DictAddKey(*psdIgnoredDependents, pDependency->sczKey); + ExitOnFailure(hr, "Failed to add the package provider key \"%ls\" to the list of ignored dependencies.", pDependency->sczKey); + } + + // Get the IGNOREDEPENDENCIES property if defined. + hr = PackageGetProperty(pPackage, DEPENDENCY_IGNOREDEPENDENCIES, &sczIgnoreDependencies); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get the package property: %ls", DEPENDENCY_IGNOREDEPENDENCIES); + + hr = DependencyAddIgnoreDependencies(*psdIgnoredDependents, sczIgnoreDependencies); + ExitOnFailure(hr, "Failed to add the authored ignored dependencies to the cumulative list of ignored dependencies."); + } + else + { + hr = S_OK; + } + +LExit: + ReleaseStr(sczIgnoreDependencies); + + return hr; +} + +/******************************************************************** + GetProviderExists - Gets whether the provider key is registered. + +*********************************************************************/ +static BOOL GetProviderExists( + __in HKEY hkRoot, + __in_z LPCWSTR wzProviderKey + ) +{ + HRESULT hr = DepGetProviderInformation(hkRoot, wzProviderKey, NULL, NULL, NULL); + return SUCCEEDED(hr); +} + +/******************************************************************** + CalculateDependencyActionStates - Calculates the dependency execute and + rollback actions for a package. + +*********************************************************************/ +static void CalculateDependencyActionStates( + __in const BURN_PACKAGE* pPackage, + __in const BOOTSTRAPPER_ACTION action, + __out BURN_DEPENDENCY_ACTION* pDependencyExecuteAction, + __out BURN_DEPENDENCY_ACTION* pDependencyRollbackAction + ) +{ + switch (action) + { + case BOOTSTRAPPER_ACTION_UNINSTALL: + // Always remove the dependency when uninstalling a bundle even if the package is absent. + *pDependencyExecuteAction = BURN_DEPENDENCY_ACTION_UNREGISTER; + break; + case BOOTSTRAPPER_ACTION_INSTALL: __fallthrough; + case BOOTSTRAPPER_ACTION_CACHE: + // Always remove the dependency during rollback when installing a bundle. + *pDependencyRollbackAction = BURN_DEPENDENCY_ACTION_UNREGISTER; + __fallthrough; + case BOOTSTRAPPER_ACTION_MODIFY: __fallthrough; + case BOOTSTRAPPER_ACTION_REPAIR: + switch (pPackage->execute) + { + case BOOTSTRAPPER_ACTION_STATE_NONE: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_NONE: + // Register if a newer, compatible package is already installed. + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: + if (!pPackage->fPackageProviderExists) + { + break; + } + __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED: + *pDependencyExecuteAction = BURN_DEPENDENCY_ACTION_REGISTER; + break; + } + break; + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_MEND: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + // Register if the package is requested but already installed. + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: + if (!pPackage->fPackageProviderExists) + { + break; + } + __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED: + *pDependencyExecuteAction = BURN_DEPENDENCY_ACTION_REGISTER; + break; + } + break; + } + break; + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + *pDependencyExecuteAction = BURN_DEPENDENCY_ACTION_UNREGISTER; + break; + case BOOTSTRAPPER_ACTION_STATE_INSTALL: __fallthrough; + case BOOTSTRAPPER_ACTION_STATE_MODIFY: __fallthrough; + case BOOTSTRAPPER_ACTION_STATE_MEND: __fallthrough; + case BOOTSTRAPPER_ACTION_STATE_REPAIR: __fallthrough; + case BOOTSTRAPPER_ACTION_STATE_MINOR_UPGRADE: __fallthrough; + *pDependencyExecuteAction = BURN_DEPENDENCY_ACTION_REGISTER; + break; + } + break; + } + + switch (*pDependencyExecuteAction) + { + case BURN_DEPENDENCY_ACTION_REGISTER: + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: __fallthrough; + *pDependencyRollbackAction = BURN_DEPENDENCY_ACTION_UNREGISTER; + break; + } + break; + case BURN_DEPENDENCY_ACTION_UNREGISTER: + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED: + *pDependencyRollbackAction = BURN_DEPENDENCY_ACTION_REGISTER; + break; + } + break; + } +} + +/******************************************************************** + AddPackageDependencyActions - Adds the dependency execute and rollback + actions to the plan. + +*********************************************************************/ +static HRESULT AddPackageDependencyActions( + __in_opt DWORD *pdwInsertSequence, + __in const BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in const BURN_DEPENDENCY_ACTION dependencyExecuteAction, + __in const BURN_DEPENDENCY_ACTION dependencyRollbackAction + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pAction = NULL; + + // Add the rollback plan. + if (BURN_DEPENDENCY_ACTION_NONE != dependencyRollbackAction) + { + hr = PlanAppendRollbackAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append rollback action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY; + pAction->packageDependency.pPackage = const_cast(pPackage); + pAction->packageDependency.action = dependencyRollbackAction; + + hr = StrAllocString(&pAction->packageDependency.sczBundleProviderKey, pPlan->wzBundleProviderKey, 0); + ExitOnFailure(hr, "Failed to copy the bundle dependency provider."); + + // Put a checkpoint before the execute action so that rollback happens + // if execute fails. + hr = PlanExecuteCheckpoint(pPlan); + ExitOnFailure(hr, "Failed to plan dependency checkpoint action."); + } + + // Add the execute plan. This comes after rollback so if something goes wrong + // rollback will try to clean up after us correctly. + if (BURN_DEPENDENCY_ACTION_NONE != dependencyExecuteAction) + { + if (NULL != pdwInsertSequence) + { + hr = PlanInsertExecuteAction(*pdwInsertSequence, pPlan, &pAction); + ExitOnFailure(hr, "Failed to insert execute action."); + + // Always move the sequence after this dependency action so the dependency registration + // stays in front of the inserted actions. + ++(*pdwInsertSequence); + } + else + { + hr = PlanAppendExecuteAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append execute action."); + } + + pAction->type = BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY; + pAction->packageDependency.pPackage = const_cast(pPackage); + pAction->packageDependency.action = dependencyExecuteAction; + + hr = StrAllocString(&pAction->packageDependency.sczBundleProviderKey, pPlan->wzBundleProviderKey, 0); + ExitOnFailure(hr, "Failed to copy the bundle dependency provider."); + } + +LExit: + return hr; +} + +static HRESULT RegisterPackageProvider( + __in const BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + LPWSTR wzId = NULL; + HKEY hkRoot = pPackage->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + + if (pPackage->rgDependencyProviders) + { + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) + { + wzId = pPackage->Msi.sczProductCode; + } + else if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + wzId = pPackage->Msp.sczPatchCode; + } + + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + const BURN_DEPENDENCY_PROVIDER* pProvider = &pPackage->rgDependencyProviders[i]; + + if (!pProvider->fImported) + { + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_PACKAGE_REGISTER, pProvider->sczKey, pProvider->sczVersion, pPackage->sczId); + + hr = DepRegisterDependency(hkRoot, pProvider->sczKey, pProvider->sczVersion, pProvider->sczDisplayName, wzId, 0); + ExitOnFailure(hr, "Failed to register the package dependency provider: %ls", pProvider->sczKey); + } + } + } + +LExit: + if (!pPackage->fVital) + { + hr = S_OK; + } + + return hr; +} + +/******************************************************************** + UnregisterPackageProvider - Removes each dependency provider + for the package (if not imported from the package itself). + + Note: Does not check for existing dependents before removing the key. +*********************************************************************/ +static void UnregisterPackageProvider( + __in const BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + HKEY hkRoot = pPackage->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + + if (pPackage->rgDependencyProviders) + { + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + const BURN_DEPENDENCY_PROVIDER* pProvider = &pPackage->rgDependencyProviders[i]; + + if (!pProvider->fImported) + { + hr = DepUnregisterDependency(hkRoot, pProvider->sczKey); + if (SUCCEEDED(hr)) + { + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_PACKAGE_UNREGISTERED, pProvider->sczKey, pPackage->sczId); + } + else if (FAILED(hr) && E_FILENOTFOUND != hr) + { + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_PACKAGE_UNREGISTERED_FAILED, pProvider->sczKey, pPackage->sczId, hr); + } + } + } + } +} + +/******************************************************************** + RegisterPackageDependency - Registers the provider key + as a dependent of a package. + +*********************************************************************/ +static HRESULT RegisterPackageDependency( + __in BOOL fPerMachine, + __in const BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzDependentProviderKey + ) +{ + HRESULT hr = S_OK; + HKEY hkRoot = fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + + // Do not register a dependency on a package in a different install context. + if (fPerMachine != pPackage->fPerMachine) + { + LogId(REPORT_STANDARD, MSG_DEPENDENCY_PACKAGE_SKIP_WRONGSCOPE, pPackage->sczId, LoggingPerMachineToString(fPerMachine), LoggingPerMachineToString(pPackage->fPerMachine)); + ExitFunction1(hr = S_OK); + } + + if (pPackage->rgDependencyProviders) + { + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + const BURN_DEPENDENCY_PROVIDER* pProvider = &pPackage->rgDependencyProviders[i]; + + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_PACKAGE_REGISTER_DEPENDENCY, wzDependentProviderKey, pProvider->sczKey, pPackage->sczId); + + hr = DepRegisterDependent(hkRoot, pProvider->sczKey, wzDependentProviderKey, NULL, NULL, 0); + if (E_FILENOTFOUND != hr || pPackage->fVital) + { + ExitOnFailure(hr, "Failed to register the dependency on package dependency provider: %ls", pProvider->sczKey); + } + else + { + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_PACKAGE_SKIP_MISSING, pProvider->sczKey, pPackage->sczId); + hr = S_OK; + } + } + } + +LExit: + return hr; +} + +/******************************************************************** + UnregisterPackageDependency - Unregisters the provider key + as a dependent of a package. + +*********************************************************************/ +static void UnregisterPackageDependency( + __in BOOL fPerMachine, + __in const BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzDependentProviderKey + ) +{ + HRESULT hr = S_OK; + HKEY hkRoot = fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + + // Should be no registration to remove since we don't write keys across contexts. + if (fPerMachine != pPackage->fPerMachine) + { + LogId(REPORT_STANDARD, MSG_DEPENDENCY_PACKAGE_SKIP_WRONGSCOPE, pPackage->sczId, LoggingPerMachineToString(fPerMachine), LoggingPerMachineToString(pPackage->fPerMachine)); + return; + } + + // Loop through each package provider and remove the bundle dependency key. + if (pPackage->rgDependencyProviders) + { + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + const BURN_DEPENDENCY_PROVIDER* pProvider = &pPackage->rgDependencyProviders[i]; + + hr = DepUnregisterDependent(hkRoot, pProvider->sczKey, wzDependentProviderKey); + if (SUCCEEDED(hr)) + { + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_PACKAGE_UNREGISTERED_DEPENDENCY, wzDependentProviderKey, pProvider->sczKey, pPackage->sczId); + } + else if (FAILED(hr) && E_FILENOTFOUND != hr) + { + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_PACKAGE_UNREGISTERED_DEPENDENCY_FAILED, wzDependentProviderKey, pProvider->sczKey, pPackage->sczId, hr); + } + } + } +} diff --git a/src/burn/engine/dependency.h b/src/burn/engine/dependency.h new file mode 100644 index 00000000..06a01a20 --- /dev/null +++ b/src/burn/engine/dependency.h @@ -0,0 +1,168 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + +// constants + +const LPCWSTR DEPENDENCY_IGNOREDEPENDENCIES = L"IGNOREDEPENDENCIES"; + + +// function declarations + +/******************************************************************** + DependencyUninitializeProvider - Frees and zeros memory allocated in + the dependency provider. + +*********************************************************************/ +void DependencyUninitializeProvider( + __in BURN_DEPENDENCY_PROVIDER* pProvider + ); + +/******************************************************************** + DependencyParseProvidersFromXml - Parses dependency information + from the manifest for the specified package. + +*********************************************************************/ +HRESULT DependencyParseProvidersFromXml( + __in BURN_PACKAGE* pPackage, + __in IXMLDOMNode* pixnPackage + ); + +HRESULT DependencyInitialize( + __in BURN_REGISTRATION* pRegistration, + __in_z_opt LPCWSTR wzIgnoreDependencies + ); + +/******************************************************************** + DependencyDetectProviderKeyBundleId - Detect if the provider key is + registered and if so what bundle is registered. + + Note: Returns E_NOTFOUND if the provider key is not registered. +*********************************************************************/ +HRESULT DependencyDetectProviderKeyBundleId( + __in BURN_REGISTRATION* pRegistration + ); + +/******************************************************************** + DependencyDetect - Detects dependency information. + +*********************************************************************/ +HRESULT DependencyDetect( + __in BURN_ENGINE_STATE* pEngineState + ); + +/******************************************************************** + DependencyPlanInitialize - Initializes the plan. + +*********************************************************************/ +HRESULT DependencyPlanInitialize( + __in const BURN_REGISTRATION* pRegistration, + __in BURN_PLAN* pPlan + ); + +/******************************************************************** + DependencyAllocIgnoreDependencies - Allocates the dependencies to + ignore as a semicolon-delimited string. + +*********************************************************************/ +HRESULT DependencyAllocIgnoreDependencies( + __in const BURN_PLAN *pPlan, + __out_z LPWSTR* psczIgnoreDependencies + ); + +/******************************************************************** + DependencyAddIgnoreDependencies - Populates the ignore dependency + names. + +*********************************************************************/ +HRESULT DependencyAddIgnoreDependencies( + __in STRINGDICT_HANDLE sdIgnoreDependencies, + __in_z LPCWSTR wzAddIgnoreDependencies + ); + +/******************************************************************** + DependencyPlanPackageBegin - Updates the dependency registration + action depending on the calculated state for the package. + +*********************************************************************/ +HRESULT DependencyPlanPackageBegin( + __in BOOL fPerMachine, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan + ); + +/******************************************************************** + DependencyPlanPackage - adds dependency related actions to the plan + for this package. + +*********************************************************************/ +HRESULT DependencyPlanPackage( + __in_opt DWORD *pdwInsertSequence, + __in const BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan + ); + +/******************************************************************** + DependencyPlanPackageComplete - Updates the dependency registration + action depending on the planned action for the package. + +*********************************************************************/ +HRESULT DependencyPlanPackageComplete( + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan + ); + +/******************************************************************** + DependencyExecutePackageProviderAction - Registers or unregisters + provider information for the package contained within the action. + +*********************************************************************/ +HRESULT DependencyExecutePackageProviderAction( + __in const BURN_EXECUTE_ACTION* pAction + ); + +/******************************************************************** + DependencyExecutePackageDependencyAction - Registers or unregisters + dependency information for the package contained within the action. + +*********************************************************************/ +HRESULT DependencyExecutePackageDependencyAction( + __in BOOL fPerMachine, + __in const BURN_EXECUTE_ACTION* pAction + ); + +/******************************************************************** + DependencyRegisterBundle - Registers the bundle dependency provider. + +*********************************************************************/ +HRESULT DependencyRegisterBundle( + __in const BURN_REGISTRATION* pRegistration + ); + +/******************************************************************** + DependencyProcessDependentRegistration - Registers or unregisters dependents + on the bundle based on the action. + +*********************************************************************/ +HRESULT DependencyProcessDependentRegistration( + __in const BURN_REGISTRATION* pRegistration, + __in const BURN_DEPENDENT_REGISTRATION_ACTION* pAction + ); + +/******************************************************************** + DependencyUnregisterBundle - Removes the bundle dependency provider. + + Note: Does not check for existing dependents before removing the key. +*********************************************************************/ +void DependencyUnregisterBundle( + __in const BURN_REGISTRATION* pRegistration, + __in const BURN_PACKAGES* pPackages + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/detect.cpp b/src/burn/engine/detect.cpp new file mode 100644 index 00000000..dc35e747 --- /dev/null +++ b/src/burn/engine/detect.cpp @@ -0,0 +1,469 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +typedef struct _DETECT_AUTHENTICATION_REQUIRED_DATA +{ + BURN_USER_EXPERIENCE* pUX; + LPCWSTR wzPackageOrContainerId; +} DETECT_AUTHENTICATION_REQUIRED_DATA; + +// internal function definitions +static HRESULT WINAPI AuthenticationRequired( + __in LPVOID pData, + __in HINTERNET hUrl, + __in long lHttpCode, + __out BOOL* pfRetrySend, + __out BOOL* pfRetry + ); + +static HRESULT DetectAtomFeedUpdate( + __in_z LPCWSTR wzBundleId, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_UPDATE* pUpdate + ); + +static HRESULT DownloadUpdateFeed( + __in_z LPCWSTR wzBundleId, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_UPDATE* pUpdate, + __deref_inout_z LPWSTR* psczTempFile + ); + +// function definitions + +extern "C" void DetectReset( + __in BURN_REGISTRATION* pRegistration, + __in BURN_PACKAGES* pPackages + ) +{ + RelatedBundlesUninitialize(&pRegistration->relatedBundles); + ReleaseNullStr(pRegistration->sczDetectedProviderKeyBundleId); + pRegistration->fSelfRegisteredAsDependent = FALSE; + pRegistration->fParentRegisteredAsDependent = FALSE; + pRegistration->fForwardCompatibleBundleExists = FALSE; + pRegistration->fEligibleForCleanup = FALSE; + + if (pRegistration->rgIgnoredDependencies) + { + ReleaseDependencyArray(pRegistration->rgIgnoredDependencies, pRegistration->cIgnoredDependencies); + } + pRegistration->rgIgnoredDependencies = NULL; + pRegistration->cIgnoredDependencies = 0; + + if (pRegistration->rgDependents) + { + ReleaseDependencyArray(pRegistration->rgDependents, pRegistration->cDependents); + } + pRegistration->rgDependents = NULL; + pRegistration->cDependents = 0; + + for (DWORD iPackage = 0; iPackage < pPackages->cPackages; ++iPackage) + { + BURN_PACKAGE* pPackage = pPackages->rgPackages + iPackage; + + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN; + pPackage->fPackageProviderExists = FALSE; + pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + + pPackage->fCached = FALSE; + + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) + { + for (DWORD iFeature = 0; iFeature < pPackage->Msi.cFeatures; ++iFeature) + { + BURN_MSIFEATURE* pFeature = pPackage->Msi.rgFeatures + iFeature; + + pFeature->currentState = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; + } + + for (DWORD iSlipstreamMsp = 0; iSlipstreamMsp < pPackage->Msi.cSlipstreamMspPackages; ++iSlipstreamMsp) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pPackage->Msi.rgSlipstreamMsps + iSlipstreamMsp; + + pSlipstreamMsp->dwMsiChainedPatchIndex = BURN_PACKAGE_INVALID_PATCH_INDEX; + } + + ReleaseNullMem(pPackage->Msi.rgChainedPatches); + pPackage->Msi.cChainedPatches = 0; + } + else if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + ReleaseNullMem(pPackage->Msp.rgTargetProducts); + pPackage->Msp.cTargetProductCodes = 0; + } + + for (DWORD iProvider = 0; iProvider < pPackage->cDependencyProviders; ++iProvider) + { + BURN_DEPENDENCY_PROVIDER* pProvider = pPackage->rgDependencyProviders + iProvider; + + if (pProvider->rgDependents) + { + ReleaseDependencyArray(pProvider->rgDependents, pProvider->cDependents); + } + pProvider->rgDependents = NULL; + pProvider->cDependents = 0; + } + } + + for (DWORD iPatchInfo = 0; iPatchInfo < pPackages->cPatchInfo; ++iPatchInfo) + { + MSIPATCHSEQUENCEINFOW* pPatchInfo = pPackages->rgPatchInfo + iPatchInfo; + pPatchInfo->dwOrder = 0; + pPatchInfo->uStatus = 0; + } +} + +extern "C" HRESULT DetectForwardCompatibleBundles( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_REGISTRATION* pRegistration + ) +{ + HRESULT hr = S_OK; + int nCompareResult = 0; + + if (pRegistration->sczDetectedProviderKeyBundleId && + CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pRegistration->sczDetectedProviderKeyBundleId, -1, pRegistration->sczId, -1)) + { + for (DWORD iRelatedBundle = 0; iRelatedBundle < pRegistration->relatedBundles.cRelatedBundles; ++iRelatedBundle) + { + BURN_RELATED_BUNDLE* pRelatedBundle = pRegistration->relatedBundles.rgRelatedBundles + iRelatedBundle; + + if (BOOTSTRAPPER_RELATION_UPGRADE == pRelatedBundle->relationType && + CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pRegistration->sczDetectedProviderKeyBundleId, -1, pRelatedBundle->package.sczId, -1)) + { + hr = VerCompareParsedVersions(pRegistration->pVersion, pRelatedBundle->pVersion, &nCompareResult); + ExitOnFailure(hr, "Failed to compare bundle version '%ls' to related bundle version '%ls'", pRegistration->pVersion->sczVersion, pRelatedBundle->pVersion->sczVersion); + + if (nCompareResult <= 0) + { + if (pRelatedBundle->fPlannable) + { + pRelatedBundle->fForwardCompatible = TRUE; + pRegistration->fForwardCompatibleBundleExists = TRUE; + } + + hr = UserExperienceOnDetectForwardCompatibleBundle(pUX, pRelatedBundle->package.sczId, pRelatedBundle->relationType, pRelatedBundle->sczTag, pRelatedBundle->package.fPerMachine, pRelatedBundle->pVersion, !pRelatedBundle->package.fCached); + ExitOnRootFailure(hr, "BA aborted detect forward compatible bundle."); + + LogId(REPORT_STANDARD, MSG_DETECTED_FORWARD_COMPATIBLE_BUNDLE, pRelatedBundle->package.sczId, LoggingRelationTypeToString(pRelatedBundle->relationType), LoggingPerMachineToString(pRelatedBundle->package.fPerMachine), pRelatedBundle->pVersion->sczVersion, LoggingBoolToString(pRelatedBundle->package.fCached)); + } + } + } + } + +LExit: + return hr; +} + +extern "C" HRESULT DetectReportRelatedBundles( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BOOTSTRAPPER_ACTION action, + __out BOOL* pfEligibleForCleanup + ) +{ + HRESULT hr = S_OK; + int nCompareResult = 0; + BOOTSTRAPPER_REQUEST_STATE uninstallRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; + *pfEligibleForCleanup = pRegistration->fInstalled || pRegistration->fCached; + + for (DWORD iRelatedBundle = 0; iRelatedBundle < pRegistration->relatedBundles.cRelatedBundles; ++iRelatedBundle) + { + const BURN_RELATED_BUNDLE* pRelatedBundle = pRegistration->relatedBundles.rgRelatedBundles + iRelatedBundle; + BOOTSTRAPPER_RELATED_OPERATION operation = BOOTSTRAPPER_RELATED_OPERATION_NONE; + + switch (pRelatedBundle->relationType) + { + case BOOTSTRAPPER_RELATION_UPGRADE: + if (BOOTSTRAPPER_RELATION_UPGRADE != relationType && BOOTSTRAPPER_ACTION_UNINSTALL < action) + { + hr = VerCompareParsedVersions(pRegistration->pVersion, pRelatedBundle->pVersion, &nCompareResult); + ExitOnFailure(hr, "Failed to compare bundle version '%ls' to related bundle version '%ls'", pRegistration->pVersion->sczVersion, pRelatedBundle->pVersion->sczVersion); + + if (nCompareResult < 0) + { + operation = BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE; + } + else + { + operation = BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE; + } + } + break; + + case BOOTSTRAPPER_RELATION_PATCH: __fallthrough; + case BOOTSTRAPPER_RELATION_ADDON: + if (BOOTSTRAPPER_ACTION_UNINSTALL == action) + { + operation = BOOTSTRAPPER_RELATED_OPERATION_REMOVE; + } + else if (BOOTSTRAPPER_ACTION_INSTALL == action || BOOTSTRAPPER_ACTION_MODIFY == action) + { + operation = BOOTSTRAPPER_RELATED_OPERATION_INSTALL; + } + else if (BOOTSTRAPPER_ACTION_REPAIR == action) + { + operation = BOOTSTRAPPER_RELATED_OPERATION_REPAIR; + } + break; + + case BOOTSTRAPPER_RELATION_DETECT: __fallthrough; + case BOOTSTRAPPER_RELATION_DEPENDENT: + break; + + default: + hr = E_FAIL; + ExitOnRootFailure(hr, "Unexpected relation type encountered: %d", pRelatedBundle->relationType); + break; + } + + LogId(REPORT_STANDARD, MSG_DETECTED_RELATED_BUNDLE, pRelatedBundle->package.sczId, LoggingRelationTypeToString(pRelatedBundle->relationType), LoggingPerMachineToString(pRelatedBundle->package.fPerMachine), pRelatedBundle->pVersion->sczVersion, LoggingRelatedOperationToString(operation), LoggingBoolToString(pRelatedBundle->package.fCached)); + + hr = UserExperienceOnDetectRelatedBundle(pUX, pRelatedBundle->package.sczId, pRelatedBundle->relationType, pRelatedBundle->sczTag, pRelatedBundle->package.fPerMachine, pRelatedBundle->pVersion, operation, !pRelatedBundle->package.fCached); + ExitOnRootFailure(hr, "BA aborted detect related bundle."); + + // For now, if any related bundles will be executed during uninstall by default then never automatically clean up the bundle. + if (*pfEligibleForCleanup && pRelatedBundle->fPlannable) + { + uninstallRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; + hr = PlanDefaultRelatedBundleRequestState(relationType, pRelatedBundle->relationType, BOOTSTRAPPER_ACTION_UNINSTALL, pRegistration->pVersion, pRelatedBundle->pVersion, &uninstallRequestState); + ExitOnFailure(hr, "Failed to get the default request state for related bundle for calculating fEligibleForCleanup"); + + if (BOOTSTRAPPER_REQUEST_STATE_NONE != uninstallRequestState) + { + *pfEligibleForCleanup = FALSE; + } + } + } + +LExit: + return hr; +} + +extern "C" HRESULT DetectUpdate( + __in_z LPCWSTR wzBundleId, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_UPDATE* pUpdate + ) +{ + HRESULT hr = S_OK; + BOOL fBeginCalled = FALSE; + BOOL fSkip = TRUE; + BOOL fIgnoreError = FALSE; + LPWSTR sczOriginalSource = NULL; + + // If no update source was specified, skip update detection. + if (!pUpdate->sczUpdateSource || !*pUpdate->sczUpdateSource) + { + ExitFunction(); + } + + fBeginCalled = TRUE; + + hr = StrAllocString(&sczOriginalSource, pUpdate->sczUpdateSource, 0); + ExitOnFailure(hr, "Failed to duplicate update feed source."); + + hr = UserExperienceOnDetectUpdateBegin(pUX, sczOriginalSource, &fSkip); + ExitOnRootFailure(hr, "BA aborted detect update begin."); + + if (!fSkip) + { + hr = DetectAtomFeedUpdate(wzBundleId, pUX, pUpdate); + ExitOnFailure(hr, "Failed to detect atom feed update."); + } + +LExit: + ReleaseStr(sczOriginalSource); + + if (fBeginCalled) + { + UserExperienceOnDetectUpdateComplete(pUX, hr, &fIgnoreError); + if (fIgnoreError) + { + hr = S_OK; + } + } + + return hr; +} + +static HRESULT WINAPI AuthenticationRequired( + __in LPVOID pData, + __in HINTERNET hUrl, + __in long lHttpCode, + __out BOOL* pfRetrySend, + __out BOOL* pfRetry + ) +{ + Assert(401 == lHttpCode || 407 == lHttpCode); + + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + BOOTSTRAPPER_ERROR_TYPE errorType = (401 == lHttpCode) ? BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_SERVER : BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_PROXY; + LPWSTR sczError = NULL; + DETECT_AUTHENTICATION_REQUIRED_DATA* pAuthenticationData = reinterpret_cast(pData); + int nResult = IDNOACTION; + + *pfRetrySend = FALSE; + *pfRetry = FALSE; + + hr = StrAllocFromError(&sczError, HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED), NULL); + ExitOnFailure(hr, "Failed to allocation error string."); + + UserExperienceOnError(pAuthenticationData->pUX, errorType, pAuthenticationData->wzPackageOrContainerId, ERROR_ACCESS_DENIED, sczError, MB_RETRYTRYAGAIN, 0, NULL, &nResult); // ignore return value. + nResult = UserExperienceCheckExecuteResult(pAuthenticationData->pUX, FALSE, MB_RETRYTRYAGAIN, nResult); + if (IDTRYAGAIN == nResult && pAuthenticationData->pUX->hwndDetect) + { + er = ::InternetErrorDlg(pAuthenticationData->pUX->hwndDetect, hUrl, ERROR_INTERNET_INCORRECT_PASSWORD, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, NULL); + if (ERROR_SUCCESS == er || ERROR_CANCELLED == er) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + else if (ERROR_INTERNET_FORCE_RETRY == er) + { + *pfRetrySend = TRUE; + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); + } + } + else if (IDRETRY == nResult) + { + *pfRetry = TRUE; + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); + } + +LExit: + ReleaseStr(sczError); + + return hr; +} + +static HRESULT DownloadUpdateFeed( + __in_z LPCWSTR wzBundleId, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_UPDATE* pUpdate, + __deref_inout_z LPWSTR* psczTempFile + ) +{ + HRESULT hr = S_OK; + DOWNLOAD_SOURCE downloadSource = { }; + DOWNLOAD_CACHE_CALLBACK cacheCallback = { }; + DOWNLOAD_AUTHENTICATION_CALLBACK authenticationCallback = { }; + DETECT_AUTHENTICATION_REQUIRED_DATA authenticationData = { }; + LPWSTR sczUpdateId = NULL; + LPWSTR sczError = NULL; + DWORD64 qwDownloadSize = 0; + + // Always do our work in the working folder, even if cached. + hr = PathCreateTimeBasedTempFile(NULL, L"UpdateFeed", NULL, L"xml", psczTempFile, NULL); + ExitOnFailure(hr, "Failed to create UpdateFeed based on current system time."); + + // Do we need a means of the BA to pass in a user name and password? If so, we should copy it to downloadSource here + hr = StrAllocString(&downloadSource.sczUrl, pUpdate->sczUpdateSource, 0); + ExitOnFailure(hr, "Failed to copy update url."); + + cacheCallback.pfnProgress = NULL; //UpdateProgressRoutine; + cacheCallback.pfnCancel = NULL; // TODO: set this + cacheCallback.pv = NULL; //pProgress; + + authenticationData.pUX = pUX; + authenticationData.wzPackageOrContainerId = wzBundleId; + + authenticationCallback.pv = static_cast(&authenticationData); + authenticationCallback.pfnAuthenticate = &AuthenticationRequired; + + hr = DownloadUrl(&downloadSource, qwDownloadSize, *psczTempFile, &cacheCallback, &authenticationCallback); + ExitOnFailure(hr, "Failed attempt to download update feed from URL: '%ls' to: '%ls'", downloadSource.sczUrl, *psczTempFile); + +LExit: + if (FAILED(hr)) + { + if (*psczTempFile) + { + FileEnsureDelete(*psczTempFile); + } + + ReleaseNullStr(*psczTempFile); + } + + ReleaseStr(downloadSource.sczUrl); + ReleaseStr(downloadSource.sczUser); + ReleaseStr(downloadSource.sczPassword); + ReleaseStr(sczUpdateId); + ReleaseStr(sczError); + return hr; +} + + +static HRESULT DetectAtomFeedUpdate( + __in_z LPCWSTR wzBundleId, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_UPDATE* pUpdate + ) +{ + Assert(pUpdate && pUpdate->sczUpdateSource && *pUpdate->sczUpdateSource); +#ifdef DEBUG + LogStringLine(REPORT_STANDARD, "DetectAtomFeedUpdate() - update location: %ls", pUpdate->sczUpdateSource); +#endif + + + HRESULT hr = S_OK; + LPWSTR sczUpdateFeedTempFile = NULL; + ATOM_FEED* pAtomFeed = NULL; + APPLICATION_UPDATE_CHAIN* pApupChain = NULL; + BOOL fStopProcessingUpdates = FALSE; + + hr = AtomInitialize(); + ExitOnFailure(hr, "Failed to initialize Atom."); + + hr = DownloadUpdateFeed(wzBundleId, pUX, pUpdate, &sczUpdateFeedTempFile); + ExitOnFailure(hr, "Failed to download update feed."); + + hr = AtomParseFromFile(sczUpdateFeedTempFile, &pAtomFeed); + ExitOnFailure(hr, "Failed to parse update atom feed: %ls.", sczUpdateFeedTempFile); + + hr = ApupAllocChainFromAtom(pAtomFeed, &pApupChain); + ExitOnFailure(hr, "Failed to allocate update chain from atom feed."); + + if (0 < pApupChain->cEntries) + { + for (DWORD i = 0; i < pApupChain->cEntries; ++i) + { + APPLICATION_UPDATE_ENTRY* pAppUpdateEntry = &pApupChain->rgEntries[i]; + + hr = UserExperienceOnDetectUpdate(pUX, pAppUpdateEntry->rgEnclosures ? pAppUpdateEntry->rgEnclosures->wzUrl : NULL, + pAppUpdateEntry->rgEnclosures ? pAppUpdateEntry->rgEnclosures->dw64Size : 0, + pAppUpdateEntry->pVersion, pAppUpdateEntry->wzTitle, + pAppUpdateEntry->wzSummary, pAppUpdateEntry->wzContentType, pAppUpdateEntry->wzContent, &fStopProcessingUpdates); + ExitOnRootFailure(hr, "BA aborted detect update."); + + if (fStopProcessingUpdates) + { + break; + } + } + } + +LExit: + if (sczUpdateFeedTempFile && *sczUpdateFeedTempFile) + { + FileEnsureDelete(sczUpdateFeedTempFile); + } + + ApupFreeChain(pApupChain); + AtomFreeFeed(pAtomFeed); + ReleaseStr(sczUpdateFeedTempFile); + AtomUninitialize(); + + return hr; +} diff --git a/src/burn/engine/detect.h b/src/burn/engine/detect.h new file mode 100644 index 00000000..9bc34882 --- /dev/null +++ b/src/burn/engine/detect.h @@ -0,0 +1,44 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + + +// structs + + +// functions + +void DetectReset( + __in BURN_REGISTRATION* pRegistration, + __in BURN_PACKAGES* pPackages + ); + +HRESULT DetectForwardCompatibleBundles( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_REGISTRATION* pRegistration + ); + +HRESULT DetectReportRelatedBundles( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BOOTSTRAPPER_ACTION action, + __out BOOL* pfEligibleForCleanup + ); + +HRESULT DetectUpdate( + __in_z LPCWSTR wzBundleId, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_UPDATE* pUpdate + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp new file mode 100644 index 00000000..9d1b8fc7 --- /dev/null +++ b/src/burn/engine/elevation.cpp @@ -0,0 +1,3239 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +const DWORD BURN_TIMEOUT = 5 * 60 * 1000; // TODO: is 5 minutes good? + +typedef enum _BURN_ELEVATION_MESSAGE_TYPE +{ + BURN_ELEVATION_MESSAGE_TYPE_UNKNOWN, + BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE, + BURN_ELEVATION_MESSAGE_TYPE_APPLY_UNINITIALIZE, + BURN_ELEVATION_MESSAGE_TYPE_SESSION_BEGIN, + BURN_ELEVATION_MESSAGE_TYPE_SESSION_RESUME, + BURN_ELEVATION_MESSAGE_TYPE_SESSION_END, + BURN_ELEVATION_MESSAGE_TYPE_SAVE_STATE, + BURN_ELEVATION_MESSAGE_TYPE_CACHE_COMPLETE_PAYLOAD, + BURN_ELEVATION_MESSAGE_TYPE_CACHE_VERIFY_PAYLOAD, + BURN_ELEVATION_MESSAGE_TYPE_CACHE_CLEANUP, + BURN_ELEVATION_MESSAGE_TYPE_PROCESS_DEPENDENT_REGISTRATION, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_EXE_PACKAGE, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_PACKAGE, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSP_PACKAGE, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSU_PACKAGE, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PACKAGE_PROVIDER, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PACKAGE_DEPENDENCY, + BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_EMBEDDED_CHILD, + BURN_ELEVATION_MESSAGE_TYPE_CLEAN_PACKAGE, + BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_APPROVED_EXE, + BURN_ELEVATION_MESSAGE_TYPE_BEGIN_MSI_TRANSACTION, + BURN_ELEVATION_MESSAGE_TYPE_COMMIT_MSI_TRANSACTION, + BURN_ELEVATION_MESSAGE_TYPE_ROLLBACK_MSI_TRANSACTION, + + BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_BEGIN, + BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_COMPLETE, + BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_SYSTEM_RESTORE_POINT_BEGIN, + BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_SYSTEM_RESTORE_POINT_COMPLETE, + BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_BEGIN, + BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE, + BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_MESSAGE, + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE, + BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_APPROVED_EXE_PROCESSID, + BURN_ELEVATION_MESSAGE_TYPE_PROGRESS_ROUTINE, +} BURN_ELEVATION_MESSAGE_TYPE; + + +// struct + +typedef struct _BURN_ELEVATION_APPLY_INITIALIZE_MESSAGE_CONTEXT +{ + BURN_USER_EXPERIENCE* pBA; + BOOL fPauseCompleteNeeded; + BOOL fSrpCompleteNeeded; +} BURN_ELEVATION_APPLY_INITIALIZE_MESSAGE_CONTEXT; + +typedef struct _BURN_ELEVATION_CACHE_MESSAGE_CONTEXT +{ + PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler; + LPPROGRESS_ROUTINE pfnProgress; + LPVOID pvContext; +} BURN_ELEVATION_CACHE_MESSAGE_CONTEXT; + +typedef struct _BURN_ELEVATION_GENERIC_MESSAGE_CONTEXT +{ + PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler; + LPVOID pvContext; +} BURN_ELEVATION_GENERIC_MESSAGE_CONTEXT; + +typedef struct _BURN_ELEVATION_MSI_MESSAGE_CONTEXT +{ + PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler; + LPVOID pvContext; +} BURN_ELEVATION_MSI_MESSAGE_CONTEXT; + +typedef struct _BURN_ELEVATION_LAUNCH_APPROVED_EXE_MESSAGE_CONTEXT +{ + DWORD dwProcessId; +} BURN_ELEVATION_LAUNCH_APPROVED_EXE_MESSAGE_CONTEXT; + +typedef struct _BURN_ELEVATION_CHILD_MESSAGE_CONTEXT +{ + DWORD dwLoggingTlsId; + HANDLE hPipe; + HANDLE* phLock; + BOOL* pfDisabledAutomaticUpdates; + BURN_APPROVED_EXES* pApprovedExes; + BURN_CONTAINERS* pContainers; + BURN_PACKAGES* pPackages; + BURN_PAYLOADS* pPayloads; + BURN_VARIABLES* pVariables; + BURN_REGISTRATION* pRegistration; + BURN_USER_EXPERIENCE* pUserExperience; +} BURN_ELEVATION_CHILD_MESSAGE_CONTEXT; + + +// internal function declarations + +static DWORD WINAPI ElevatedChildCacheThreadProc( + __in LPVOID lpThreadParameter + ); +static HRESULT WaitForElevatedChildCacheThread( + __in HANDLE hCacheThread, + __in DWORD dwExpectedExitCode + ); +static HRESULT ProcessApplyInitializeMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT ProcessBurnCacheMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT ProcessGenericExecuteMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT ProcessMsiPackageMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT ProcessLaunchApprovedExeMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT ProcessProgressRoutineMessage( + __in BURN_PIPE_MESSAGE* pMsg, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT ProcessElevatedChildMessage( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT ProcessElevatedChildCacheMessage( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT ProcessResult( + __in DWORD dwResult, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +static HRESULT OnApplyInitialize( + __in HANDLE hPipe, + __in BURN_VARIABLES* pVariables, + __in BURN_REGISTRATION* pRegistration, + __in HANDLE* phLock, + __in BOOL* pfDisabledWindowsUpdate, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnApplyUninitialize( + __in HANDLE* phLock + ); +static HRESULT OnSessionBegin( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnSessionResume( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnSessionEnd( + __in BURN_PACKAGES* pPackages, + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnSaveState( + __in BURN_REGISTRATION* pRegistration, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnCacheCompletePayload( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOADS* pPayloads, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnCacheVerifyPayload( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOADS* pPayloads, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static void OnCacheCleanup( + __in_z LPCWSTR wzBundleId + ); +static HRESULT OnProcessDependentRegistration( + __in const BURN_REGISTRATION* pRegistration, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnExecuteExePackage( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_RELATED_BUNDLES* pRelatedBundles, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnExecuteMsiPackage( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnExecuteMspPackage( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnExecuteMsuPackage( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnExecutePackageProviderAction( + __in BURN_PACKAGES* pPackages, + __in BURN_RELATED_BUNDLES* pRelatedBundles, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnExecutePackageDependencyAction( + __in BURN_PACKAGES* pPackages, + __in BURN_RELATED_BUNDLES* pRelatedBundles, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT CALLBACK BurnCacheMessageHandler( + __in BURN_CACHE_MESSAGE* pMessage, + __in LPVOID pvContext + ); +static DWORD CALLBACK ElevatedProgressRoutine( + __in LARGE_INTEGER TotalFileSize, + __in LARGE_INTEGER TotalBytesTransferred, + __in LARGE_INTEGER StreamSize, + __in LARGE_INTEGER StreamBytesTransferred, + __in DWORD dwStreamNumber, + __in DWORD dwCallbackReason, + __in HANDLE hSourceFile, + __in HANDLE hDestinationFile, + __in_opt LPVOID lpData + ); +static int GenericExecuteMessageHandler( + __in GENERIC_EXECUTE_MESSAGE* pMessage, + __in LPVOID pvContext + ); +static int MsiExecuteMessageHandler( + __in WIU_MSI_EXECUTE_MESSAGE* pMessage, + __in_opt LPVOID pvContext + ); +static HRESULT OnCleanPackage( + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnLaunchApprovedExe( + __in HANDLE hPipe, + __in BURN_APPROVED_EXES* pApprovedExes, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnMsiBeginTransaction( + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnMsiCommitTransaction( + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT OnMsiRollbackTransaction( + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ); +static HRESULT ElevatedOnPauseAUBegin( + __in HANDLE hPipe + ); +static HRESULT ElevatedOnPauseAUComplete( + __in HANDLE hPipe, + __in HRESULT hrStatus + ); +static HRESULT ElevatedOnSystemRestorePointBegin( + __in HANDLE hPipe + ); +static HRESULT ElevatedOnSystemRestorePointComplete( + __in HANDLE hPipe, + __in HRESULT hrStatus + ); + + +// function definitions + +extern "C" HRESULT ElevationElevate( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HWND hwndParent + ) +{ + Assert(BURN_MODE_ELEVATED != pEngineState->mode); + Assert(!pEngineState->companionConnection.sczName); + Assert(!pEngineState->companionConnection.sczSecret); + Assert(!pEngineState->companionConnection.hProcess); + Assert(!pEngineState->companionConnection.dwProcessId); + Assert(INVALID_HANDLE_VALUE == pEngineState->companionConnection.hPipe); + Assert(INVALID_HANDLE_VALUE == pEngineState->companionConnection.hCachePipe); + + HRESULT hr = S_OK; + int nResult = IDOK; + HANDLE hPipesCreatedEvent = INVALID_HANDLE_VALUE; + + hr = UserExperienceOnElevateBegin(&pEngineState->userExperience); + ExitOnRootFailure(hr, "BA aborted elevation requirement."); + + hr = PipeCreateNameAndSecret(&pEngineState->companionConnection.sczName, &pEngineState->companionConnection.sczSecret); + ExitOnFailure(hr, "Failed to create pipe name and client token."); + + hr = PipeCreatePipes(&pEngineState->companionConnection, TRUE, &hPipesCreatedEvent); + ExitOnFailure(hr, "Failed to create pipe and cache pipe."); + + LogId(REPORT_STANDARD, MSG_LAUNCH_ELEVATED_ENGINE_STARTING); + + do + { + nResult = IDOK; + + // Create the elevated process and if successful, wait for it to connect. + hr = PipeLaunchChildProcess(pEngineState->sczBundleEngineWorkingPath, &pEngineState->companionConnection, TRUE, hwndParent); + if (SUCCEEDED(hr)) + { + LogId(REPORT_STANDARD, MSG_LAUNCH_ELEVATED_ENGINE_SUCCESS); + + hr = PipeWaitForChildConnect(&pEngineState->companionConnection); + if (HRESULT_FROM_WIN32(ERROR_NO_DATA) == hr) + { + hr = E_SUSPECTED_AV_INTERFERENCE; + } + ExitOnFailure(hr, "Failed to connect to elevated child process."); + + LogId(REPORT_STANDARD, MSG_CONNECT_TO_ELEVATED_ENGINE_SUCCESS); + } + else if (HRESULT_FROM_WIN32(ERROR_CANCELLED) == hr) + { + // The user clicked "Cancel" on the elevation prompt or the elevation prompt timed out, provide the notification with the option to retry. + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + nResult = UserExperienceSendError(&pEngineState->userExperience, BOOTSTRAPPER_ERROR_TYPE_ELEVATE, NULL, hr, NULL, MB_ICONERROR | MB_RETRYCANCEL, IDNOACTION); + } + } while (IDRETRY == nResult); + ExitOnFailure(hr, "Failed to elevate."); + +LExit: + ReleaseHandle(hPipesCreatedEvent); + + if (FAILED(hr)) + { + PipeConnectionUninitialize(&pEngineState->companionConnection); + } + + UserExperienceOnElevateComplete(&pEngineState->userExperience, hr); + + return hr; +} + +extern "C" HRESULT ElevationApplyInitialize( + __in HANDLE hPipe, + __in BURN_USER_EXPERIENCE* pBA, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_ACTION action, + __in BURN_AU_PAUSE_ACTION auAction, + __in BOOL fTakeSystemRestorePoint + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + BURN_ELEVATION_APPLY_INITIALIZE_MESSAGE_CONTEXT context = { }; + + context.pBA = pBA; + + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)action); + ExitOnFailure(hr, "Failed to write action to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)auAction); + ExitOnFailure(hr, "Failed to write update action to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)fTakeSystemRestorePoint); + ExitOnFailure(hr, "Failed to write system restore point action to message buffer."); + + hr = VariableSerialize(pVariables, FALSE, &pbData, &cbData); + ExitOnFailure(hr, "Failed to write variables."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE, pbData, cbData, ProcessApplyInitializeMessages, &context, &dwResult); + ExitOnFailure(hr, "Failed to send message to per-machine process."); + + hr = (HRESULT)dwResult; + + // Best effort to keep the sequence of BA events sane. + if (context.fPauseCompleteNeeded) + { + UserExperienceOnPauseAUComplete(pBA, hr); + } + if (context.fSrpCompleteNeeded) + { + UserExperienceOnSystemRestorePointComplete(pBA, hr); + } + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +extern "C" HRESULT ElevationApplyUninitialize( + __in HANDLE hPipe + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_APPLY_UNINITIALIZE, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationSessionBegin - + +*******************************************************************/ +extern "C" HRESULT ElevationSessionBegin( + __in HANDLE hPipe, + __in_z LPCWSTR wzEngineWorkingPath, + __in_z LPCWSTR wzResumeCommandLine, + __in BOOL fDisableResume, + __in BURN_VARIABLES* pVariables, + __in DWORD dwRegistrationOperations, + __in BURN_DEPENDENCY_REGISTRATION_ACTION dependencyRegistrationAction, + __in DWORD64 qwEstimatedSize + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, wzEngineWorkingPath); + ExitOnFailure(hr, "Failed to write engine working path to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, wzResumeCommandLine); + ExitOnFailure(hr, "Failed to write resume command line to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, fDisableResume); + ExitOnFailure(hr, "Failed to write resume flag."); + + hr = BuffWriteNumber(&pbData, &cbData, dwRegistrationOperations); + ExitOnFailure(hr, "Failed to write registration operations to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)dependencyRegistrationAction); + ExitOnFailure(hr, "Failed to write dependency registration action to message buffer."); + + hr = BuffWriteNumber64(&pbData, &cbData, qwEstimatedSize); + ExitOnFailure(hr, "Failed to write estimated size to message buffer."); + + hr = VariableSerialize(pVariables, FALSE, &pbData, &cbData); + ExitOnFailure(hr, "Failed to write variables."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_SESSION_BEGIN, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationSessionResume - + +*******************************************************************/ +extern "C" HRESULT ElevationSessionResume( + __in HANDLE hPipe, + __in_z LPCWSTR wzResumeCommandLine, + __in BOOL fDisableResume, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, wzResumeCommandLine); + ExitOnFailure(hr, "Failed to write resume command line to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, fDisableResume); + ExitOnFailure(hr, "Failed to write resume flag."); + + hr = VariableSerialize(pVariables, FALSE, &pbData, &cbData); + ExitOnFailure(hr, "Failed to write variables."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_SESSION_RESUME, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationSessionEnd - + +*******************************************************************/ +extern "C" HRESULT ElevationSessionEnd( + __in HANDLE hPipe, + __in BURN_RESUME_MODE resumeMode, + __in BOOTSTRAPPER_APPLY_RESTART restart, + __in BURN_DEPENDENCY_REGISTRATION_ACTION dependencyRegistrationAction + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)resumeMode); + ExitOnFailure(hr, "Failed to write resume mode to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)restart); + ExitOnFailure(hr, "Failed to write restart enum to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)dependencyRegistrationAction); + ExitOnFailure(hr, "Failed to write dependency registration action to message buffer."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_SESSION_END, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationSaveState - + +*******************************************************************/ +HRESULT ElevationSaveState( + __in HANDLE hPipe, + __in_bcount(cbBuffer) BYTE* pbBuffer, + __in SIZE_T cbBuffer + ) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_SAVE_STATE, pbBuffer, cbBuffer, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + return hr; +} + +/******************************************************************* + ElevationCacheCompletePayload - + +*******************************************************************/ +extern "C" HRESULT ElevationCacheCompletePayload( + __in HANDLE hPipe, + __in BURN_PACKAGE* pPackage, + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzUnverifiedPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + BURN_ELEVATION_CACHE_MESSAGE_CONTEXT context = { }; + + context.pfnCacheMessageHandler = pfnCacheMessageHandler; + context.pfnProgress = pfnProgress; + context.pvContext = pContext; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pPackage->sczId); + ExitOnFailure(hr, "Failed to write package id to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pPayload->sczKey); + ExitOnFailure(hr, "Failed to write payload id to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, wzUnverifiedPath); + ExitOnFailure(hr, "Failed to write unverified path to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)fMove); + ExitOnFailure(hr, "Failed to write move flag to message buffer."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_CACHE_COMPLETE_PAYLOAD, pbData, cbData, ProcessBurnCacheMessages, &context, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_CACHE_COMPLETE_PAYLOAD message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +extern "C" HRESULT ElevationCacheVerifyPayload( + __in HANDLE hPipe, + __in BURN_PACKAGE* pPackage, + __in BURN_PAYLOAD* pPayload, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + BURN_ELEVATION_CACHE_MESSAGE_CONTEXT context = { }; + + context.pfnCacheMessageHandler = pfnCacheMessageHandler; + context.pfnProgress = pfnProgress; + context.pvContext = pContext; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pPackage->sczId); + ExitOnFailure(hr, "Failed to write package id to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pPayload->sczKey); + ExitOnFailure(hr, "Failed to write payload id to message buffer."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_CACHE_VERIFY_PAYLOAD, pbData, cbData, ProcessBurnCacheMessages, &context, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_CACHE_VERIFY_PAYLOAD message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationCacheCleanup - + +*******************************************************************/ +extern "C" HRESULT ElevationCacheCleanup( + __in HANDLE hPipe + ) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_CACHE_CLEANUP, NULL, 0, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_CACHE_CLEANUP message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + return hr; +} + +extern "C" HRESULT ElevationProcessDependentRegistration( + __in HANDLE hPipe, + __in const BURN_DEPENDENT_REGISTRATION_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, pAction->type); + ExitOnFailure(hr, "Failed to write action type to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pAction->sczBundleId); + ExitOnFailure(hr, "Failed to write bundle id to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pAction->sczDependentProviderKey); + ExitOnFailure(hr, "Failed to write dependent provider key to message buffer."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_PROCESS_DEPENDENT_REGISTRATION, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_PROCESS_DEPENDENT_REGISTRATION message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationExecuteExePackage - + +*******************************************************************/ +extern "C" HRESULT ElevationExecuteExePackage( + __in HANDLE hPipe, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + BURN_ELEVATION_GENERIC_MESSAGE_CONTEXT context = { }; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->exePackage.pPackage->sczId); + ExitOnFailure(hr, "Failed to write package id to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->exePackage.action); + ExitOnFailure(hr, "Failed to write action to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, fRollback); + ExitOnFailure(hr, "Failed to write rollback."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->exePackage.sczIgnoreDependencies); + ExitOnFailure(hr, "Failed to write the list of dependencies to ignore to the message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->exePackage.sczAncestors); + ExitOnFailure(hr, "Failed to write the list of ancestors to the message buffer."); + + hr = VariableSerialize(pVariables, FALSE, &pbData, &cbData); + ExitOnFailure(hr, "Failed to write variables."); + + // send message + context.pfnGenericMessageHandler = pfnGenericMessageHandler; + context.pvContext = pvContext; + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_EXE_PACKAGE, pbData, cbData, ProcessGenericExecuteMessages, &context, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_EXE_PACKAGE message to per-machine process."); + + hr = ProcessResult(dwResult, pRestart); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +extern "C" HRESULT ElevationMsiBeginTransaction( + __in HANDLE hPipe, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = ERROR_SUCCESS; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pRollbackBoundary->sczId); + ExitOnFailure(hr, "Failed to write transaction name to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pRollbackBoundary->sczLogPath); + ExitOnFailure(hr, "Failed to write transaction log path to message buffer."); + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_BEGIN_MSI_TRANSACTION, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_BEGIN_MSI_TRANSACTION message to per-machine process."); + + hr = static_cast(dwResult); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +extern "C" HRESULT ElevationMsiCommitTransaction( + __in HANDLE hPipe, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = ERROR_SUCCESS; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pRollbackBoundary->sczId); + ExitOnFailure(hr, "Failed to write transaction name to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pRollbackBoundary->sczLogPath); + ExitOnFailure(hr, "Failed to write transaction log path to message buffer."); + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_COMMIT_MSI_TRANSACTION, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_COMMIT_MSI_TRANSACTION message to per-machine process."); + + hr = static_cast(dwResult); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +extern "C" HRESULT ElevationMsiRollbackTransaction( + __in HANDLE hPipe, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = ERROR_SUCCESS; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pRollbackBoundary->sczId); + ExitOnFailure(hr, "Failed to write transaction name to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pRollbackBoundary->sczLogPath); + ExitOnFailure(hr, "Failed to write transaction log path to message buffer."); + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_ROLLBACK_MSI_TRANSACTION, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_ROLLBACK_MSI_TRANSACTION message to per-machine process."); + + hr = static_cast(dwResult); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + + + +/******************************************************************* + ElevationExecuteMsiPackage - + +*******************************************************************/ +extern "C" HRESULT ElevationExecuteMsiPackage( + __in HANDLE hPipe, + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + BURN_ELEVATION_MSI_MESSAGE_CONTEXT context = { }; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)fRollback); + ExitOnFailure(hr, "Failed to write rollback flag to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->msiPackage.pPackage->sczId); + ExitOnFailure(hr, "Failed to write package id to message buffer."); + + hr = BuffWritePointer(&pbData, &cbData, (DWORD_PTR)hwndParent); + ExitOnFailure(hr, "Failed to write parent hwnd to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->msiPackage.sczLogPath); + ExitOnFailure(hr, "Failed to write package log to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->msiPackage.actionMsiProperty); + ExitOnFailure(hr, "Failed to write actionMsiProperty to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->msiPackage.uiLevel); + ExitOnFailure(hr, "Failed to write UI level to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->msiPackage.fDisableExternalUiHandler); + ExitOnFailure(hr, "Failed to write fDisableExternalUiHandler to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->msiPackage.action); + ExitOnFailure(hr, "Failed to write action to message buffer."); + + // Feature actions. + for (DWORD i = 0; i < pExecuteAction->msiPackage.pPackage->Msi.cFeatures; ++i) + { + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->msiPackage.rgFeatures[i]); + ExitOnFailure(hr, "Failed to write feature action to message buffer."); + } + + // Slipstream patches actions. + for (DWORD i = 0; i < pExecuteAction->msiPackage.pPackage->Msi.cSlipstreamMspPackages; ++i) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pExecuteAction->msiPackage.pPackage->Msi.rgSlipstreamMsps + i; + BOOTSTRAPPER_ACTION_STATE* pAction = fRollback ? &pSlipstreamMsp->rollback : &pSlipstreamMsp->execute; + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)*pAction); + ExitOnFailure(hr, "Failed to write slipstream patch action to message buffer."); + } + + hr = VariableSerialize(pVariables, FALSE, &pbData, &cbData); + ExitOnFailure(hr, "Failed to write variables."); + + + // send message + context.pfnMessageHandler = pfnMessageHandler; + context.pvContext = pvContext; + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_PACKAGE, pbData, cbData, ProcessMsiPackageMessages, &context, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_PACKAGE message to per-machine process."); + + hr = ProcessResult(dwResult, pRestart); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationExecuteMspPackage - + +*******************************************************************/ +extern "C" HRESULT ElevationExecuteMspPackage( + __in HANDLE hPipe, + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + BURN_ELEVATION_MSI_MESSAGE_CONTEXT context = { }; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->mspTarget.pPackage->sczId); + ExitOnFailure(hr, "Failed to write package id to message buffer."); + + hr = BuffWritePointer(&pbData, &cbData, (DWORD_PTR)hwndParent); + ExitOnFailure(hr, "Failed to write parent hwnd to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->mspTarget.sczTargetProductCode); + ExitOnFailure(hr, "Failed to write target product code to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->mspTarget.sczLogPath); + ExitOnFailure(hr, "Failed to write package log to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->mspTarget.actionMsiProperty); + ExitOnFailure(hr, "Failed to write actionMsiProperty to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->mspTarget.uiLevel); + ExitOnFailure(hr, "Failed to write UI level to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->mspTarget.fDisableExternalUiHandler); + ExitOnFailure(hr, "Failed to write fDisableExternalUiHandler to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->mspTarget.action); + ExitOnFailure(hr, "Failed to write action to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, pExecuteAction->mspTarget.cOrderedPatches); + ExitOnFailure(hr, "Failed to write count of ordered patches to message buffer."); + + for (DWORD i = 0; i < pExecuteAction->mspTarget.cOrderedPatches; ++i) + { + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->mspTarget.rgOrderedPatches[i].pPackage->sczId); + ExitOnFailure(hr, "Failed to write ordered patch id to message buffer."); + } + + hr = VariableSerialize(pVariables, FALSE, &pbData, &cbData); + ExitOnFailure(hr, "Failed to write variables."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)fRollback); + ExitOnFailure(hr, "Failed to write rollback flag to message buffer."); + + // send message + context.pfnMessageHandler = pfnMessageHandler; + context.pvContext = pvContext; + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSP_PACKAGE, pbData, cbData, ProcessMsiPackageMessages, &context, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSP_PACKAGE message to per-machine process."); + + hr = ProcessResult(dwResult, pRestart); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationExecuteMsuPackage - + +*******************************************************************/ +extern "C" HRESULT ElevationExecuteMsuPackage( + __in HANDLE hPipe, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BOOL fRollback, + __in BOOL fStopWusaService, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + BURN_ELEVATION_GENERIC_MESSAGE_CONTEXT context = { }; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->msuPackage.pPackage->sczId); + ExitOnFailure(hr, "Failed to write package id to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->msuPackage.sczLogPath); + ExitOnFailure(hr, "Failed to write package log to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pExecuteAction->msuPackage.action); + ExitOnFailure(hr, "Failed to write action to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, fRollback); + ExitOnFailure(hr, "Failed to write rollback."); + + hr = BuffWriteNumber(&pbData, &cbData, fStopWusaService); + ExitOnFailure(hr, "Failed to write StopWusaService."); + + // send message + context.pfnGenericMessageHandler = pfnGenericMessageHandler; + context.pvContext = pvContext; + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSU_PACKAGE, pbData, cbData, ProcessGenericExecuteMessages, &context, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSU_PACKAGE message to per-machine process."); + + hr = ProcessResult(dwResult, pRestart); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +extern "C" HRESULT ElevationExecutePackageProviderAction( + __in HANDLE hPipe, + __in BURN_EXECUTE_ACTION* pExecuteAction + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE; + + // Serialize the message data. + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->packageProvider.pPackage->sczId); + ExitOnFailure(hr, "Failed to write package id to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, pExecuteAction->packageProvider.action); + ExitOnFailure(hr, "Failed to write action to message buffer."); + + // Send the message. + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PACKAGE_PROVIDER, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PACKAGE_PROVIDER message to per-machine process."); + + // Ignore the restart since this action only results in registry writes. + hr = ProcessResult(dwResult, &restart); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +extern "C" HRESULT ElevationExecutePackageDependencyAction( + __in HANDLE hPipe, + __in BURN_EXECUTE_ACTION* pExecuteAction + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE; + + // Serialize the message data. + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->packageDependency.pPackage->sczId); + ExitOnFailure(hr, "Failed to write package id to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pExecuteAction->packageDependency.sczBundleProviderKey); + ExitOnFailure(hr, "Failed to write bundle dependency key to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, pExecuteAction->packageDependency.action); + ExitOnFailure(hr, "Failed to write action to message buffer."); + + // Send the message. + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PACKAGE_DEPENDENCY, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PACKAGE_DEPENDENCY message to per-machine process."); + + // Ignore the restart since this action only results in registry writes. + hr = ProcessResult(dwResult, &restart); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationCleanPackage - + +*******************************************************************/ +extern "C" HRESULT ElevationCleanPackage( + __in HANDLE hPipe, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // serialize message data + hr = BuffWriteString(&pbData, &cbData, pPackage->sczId); + ExitOnFailure(hr, "Failed to write clean package id to message buffer."); + + // send message + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_CLEAN_PACKAGE, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_CLEAN_PACKAGE message to per-machine process."); + + hr = (HRESULT)dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +extern "C" HRESULT ElevationLaunchApprovedExe( + __in HANDLE hPipe, + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe, + __out DWORD* pdwProcessId + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + BURN_ELEVATION_LAUNCH_APPROVED_EXE_MESSAGE_CONTEXT context = { }; + + // Serialize message data. + hr = BuffWriteString(&pbData, &cbData, pLaunchApprovedExe->sczId); + ExitOnFailure(hr, "Failed to write approved exe id to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pLaunchApprovedExe->sczArguments); + ExitOnFailure(hr, "Failed to write approved exe arguments to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, pLaunchApprovedExe->dwWaitForInputIdleTimeout); + ExitOnFailure(hr, "Failed to write approved exe WaitForInputIdle timeout to message buffer."); + + // Send the message. + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_APPROVED_EXE, pbData, cbData, ProcessLaunchApprovedExeMessages, &context, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_APPROVED_EXE message to per-machine process."); + + hr = (HRESULT)dwResult; + *pdwProcessId = context.dwProcessId; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +/******************************************************************* + ElevationChildPumpMessages - + +*******************************************************************/ +extern "C" HRESULT ElevationChildPumpMessages( + __in DWORD dwLoggingTlsId, + __in HANDLE hPipe, + __in HANDLE hCachePipe, + __in BURN_APPROVED_EXES* pApprovedExes, + __in BURN_CONTAINERS* pContainers, + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOADS* pPayloads, + __in BURN_VARIABLES* pVariables, + __in BURN_REGISTRATION* pRegistration, + __in BURN_USER_EXPERIENCE* pUserExperience, + __out HANDLE* phLock, + __out BOOL* pfDisabledAutomaticUpdates, + __out DWORD* pdwChildExitCode, + __out BOOL* pfRestart + ) +{ + HRESULT hr = S_OK; + BURN_ELEVATION_CHILD_MESSAGE_CONTEXT cacheContext = { }; + BURN_ELEVATION_CHILD_MESSAGE_CONTEXT context = { }; + HANDLE hCacheThread = NULL; + BURN_PIPE_RESULT result = { }; + + cacheContext.dwLoggingTlsId = dwLoggingTlsId; + cacheContext.hPipe = hCachePipe; + cacheContext.pContainers = pContainers; + cacheContext.pPackages = pPackages; + cacheContext.pPayloads = pPayloads; + cacheContext.pVariables = pVariables; + cacheContext.pRegistration = pRegistration; + cacheContext.pUserExperience = pUserExperience; + + context.dwLoggingTlsId = dwLoggingTlsId; + context.hPipe = hPipe; + context.phLock = phLock; + context.pfDisabledAutomaticUpdates = pfDisabledAutomaticUpdates; + context.pApprovedExes = pApprovedExes; + context.pContainers = pContainers; + context.pPackages = pPackages; + context.pPayloads = pPayloads; + context.pVariables = pVariables; + context.pRegistration = pRegistration; + context.pUserExperience = pUserExperience; + + hCacheThread = ::CreateThread(NULL, 0, ElevatedChildCacheThreadProc, &cacheContext, 0, NULL); + ExitOnNullWithLastError(hCacheThread, hr, "Failed to create elevated cache thread."); + + hr = PipePumpMessages(hPipe, ProcessElevatedChildMessage, &context, &result); + ExitOnFailure(hr, "Failed to pump messages in child process."); + + // Wait for the cache thread and verify it gets the right result but don't fail if things + // don't work out. + WaitForElevatedChildCacheThread(hCacheThread, result.dwResult); + + *pdwChildExitCode = result.dwResult; + *pfRestart = result.fRestart; + +LExit: + ReleaseHandle(hCacheThread); + + return hr; +} + +extern "C" HRESULT ElevationChildResumeAutomaticUpdates() +{ + HRESULT hr = S_OK; + + LogId(REPORT_STANDARD, MSG_RESUME_AU_STARTING); + + hr = WuaResumeAutomaticUpdates(); + ExitOnFailure(hr, "Failed to resume automatic updates after pausing them, continuing..."); + + LogId(REPORT_STANDARD, MSG_RESUME_AU_SUCCEEDED); + +LExit: + return hr; +} + +// internal function definitions + +static DWORD WINAPI ElevatedChildCacheThreadProc( + __in LPVOID lpThreadParameter + ) +{ + HRESULT hr = S_OK; + BURN_ELEVATION_CHILD_MESSAGE_CONTEXT* pContext = reinterpret_cast(lpThreadParameter); + BOOL fComInitialized = FALSE; + BURN_PIPE_RESULT result = { }; + + if (!::TlsSetValue(pContext->dwLoggingTlsId, pContext->hPipe)) + { + ExitWithLastError(hr, "Failed to set elevated cache pipe into thread local storage for logging."); + } + + // initialize COM + hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); + ExitOnFailure(hr, "Failed to initialize COM."); + fComInitialized = TRUE; + + hr = PipePumpMessages(pContext->hPipe, ProcessElevatedChildCacheMessage, pContext, &result); + ExitOnFailure(hr, "Failed to pump messages in child process."); + + hr = (HRESULT)result.dwResult; + +LExit: + if (fComInitialized) + { + ::CoUninitialize(); + } + + return (DWORD)hr; +} + +static HRESULT WaitForElevatedChildCacheThread( + __in HANDLE hCacheThread, + __in DWORD dwExpectedExitCode + ) +{ + UNREFERENCED_PARAMETER(dwExpectedExitCode); + + HRESULT hr = S_OK; + DWORD dwExitCode = ERROR_SUCCESS; + + if (WAIT_OBJECT_0 != ::WaitForSingleObject(hCacheThread, BURN_TIMEOUT)) + { + ExitWithLastError(hr, "Failed to wait for cache thread to terminate."); + } + + if (!::GetExitCodeThread(hCacheThread, &dwExitCode)) + { + ExitWithLastError(hr, "Failed to get cache thread exit code."); + } + + AssertSz(dwExitCode == dwExpectedExitCode, "Cache thread should have exited with the expected exit code."); + +LExit: + return hr; +} + +static HRESULT ProcessApplyInitializeMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + BURN_ELEVATION_APPLY_INITIALIZE_MESSAGE_CONTEXT* pContext = static_cast(pvContext); + BYTE* pbData = (BYTE*)pMsg->pvData; + SIZE_T iData = 0; + HRESULT hrStatus = S_OK; + HRESULT hrBA = S_OK; + + // Process the message. + switch (pMsg->dwMessage) + { + case BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_BEGIN: + pContext->fPauseCompleteNeeded = TRUE; + hrBA = UserExperienceOnPauseAUBegin(pContext->pBA); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_COMPLETE: + // read hrStatus + hr = BuffReadNumber(pbData, pMsg->cbData, &iData, reinterpret_cast(&hrStatus)); + ExitOnFailure(hr, "Failed to read pause AU hrStatus."); + + pContext->fPauseCompleteNeeded = FALSE; + hrBA = UserExperienceOnPauseAUComplete(pContext->pBA, hrStatus); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_SYSTEM_RESTORE_POINT_BEGIN: + if (pContext->fPauseCompleteNeeded) + { + pContext->fPauseCompleteNeeded = FALSE; + hrBA = UserExperienceOnPauseAUComplete(pContext->pBA, E_INVALIDSTATE); + } + + pContext->fSrpCompleteNeeded = TRUE; + hrBA = UserExperienceOnSystemRestorePointBegin(pContext->pBA); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_SYSTEM_RESTORE_POINT_COMPLETE: + // read hrStatus + hr = BuffReadNumber(pbData, pMsg->cbData, &iData, reinterpret_cast(&hrStatus)); + ExitOnFailure(hr, "Failed to read system restore point hrStatus."); + + pContext->fSrpCompleteNeeded = FALSE; + hrBA = UserExperienceOnSystemRestorePointComplete(pContext->pBA, hrStatus); + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid apply initialize message."); + break; + } + + *pdwResult = static_cast(hrBA); + +LExit: + return hr; +} + +static HRESULT ProcessBurnCacheMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + BURN_ELEVATION_CACHE_MESSAGE_CONTEXT* pContext = static_cast(pvContext); + BURN_CACHE_MESSAGE message = { }; + BOOL fProgressRoutine = FALSE; + + // Process the message. + switch (pMsg->dwMessage) + { + case BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_BEGIN: + // read message parameters + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, reinterpret_cast(&message.begin.cacheStep)); + ExitOnFailure(hr, "Failed to read begin cache step."); + + message.type = BURN_CACHE_MESSAGE_BEGIN; + break; + + case BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE: + // read message parameters + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, reinterpret_cast(&message.complete.hrStatus)); + ExitOnFailure(hr, "Failed to read complete hresult."); + + message.type = BURN_CACHE_MESSAGE_COMPLETE; + break; + + case BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS: + // read message parameters + hr = BuffReadNumber64((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.success.qwFileSize); + ExitOnFailure(hr, "Failed to read begin cache step."); + + message.type = BURN_CACHE_MESSAGE_SUCCESS; + break; + + case BURN_ELEVATION_MESSAGE_TYPE_PROGRESS_ROUTINE: + fProgressRoutine = TRUE; + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid burn cache message."); + break; + } + + if (fProgressRoutine) + { + hr = ProcessProgressRoutineMessage(pMsg, pContext->pfnProgress, pContext->pvContext, pdwResult); + } + else + { + hr = pContext->pfnCacheMessageHandler(&message, pContext->pvContext); + *pdwResult = static_cast(hr); + } + +LExit: + return hr; +} + +static HRESULT ProcessGenericExecuteMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + BURN_ELEVATION_GENERIC_MESSAGE_CONTEXT* pContext = static_cast(pvContext); + LPWSTR sczMessage = NULL; + DWORD cFiles = 0; + LPWSTR* rgwzFiles = NULL; + GENERIC_EXECUTE_MESSAGE message = { }; + + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.dwAllowedResults); + ExitOnFailure(hr, "Failed to allowed results."); + + // Process the message. + switch (pMsg->dwMessage) + { + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS: + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + + // read message parameters + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.progress.dwPercentage); + ExitOnFailure(hr, "Failed to progress."); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR: + message.type = GENERIC_EXECUTE_MESSAGE_ERROR; + + // read message parameters + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.error.dwErrorCode); + ExitOnFailure(hr, "Failed to read error code."); + + hr = BuffReadString((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &sczMessage); + ExitOnFailure(hr, "Failed to read message."); + + message.error.wzMessage = sczMessage; + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE: + message.type = GENERIC_EXECUTE_MESSAGE_FILES_IN_USE; + + // read message parameters + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &cFiles); + ExitOnFailure(hr, "Failed to read file count."); + + rgwzFiles = (LPWSTR*)MemAlloc(sizeof(LPWSTR*) * cFiles, TRUE); + ExitOnNull(rgwzFiles, hr, E_OUTOFMEMORY, "Failed to allocate buffer for files in use."); + + for (DWORD i = 0; i < cFiles; ++i) + { + hr = BuffReadString((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &rgwzFiles[i]); + ExitOnFailure(hr, "Failed to read file name: %u", i); + } + + message.filesInUse.cFiles = cFiles; + message.filesInUse.rgwzFiles = (LPCWSTR*)rgwzFiles; + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package message."); + break; + } + + // send message + *pdwResult = (DWORD)pContext->pfnGenericMessageHandler(&message, pContext->pvContext); + +LExit: + ReleaseStr(sczMessage); + + if (rgwzFiles) + { + for (DWORD i = 0; i < cFiles; ++i) + { + ReleaseStr(rgwzFiles[i]); + } + MemFree(rgwzFiles); + } + return hr; +} + +static HRESULT ProcessMsiPackageMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + WIU_MSI_EXECUTE_MESSAGE message = { }; + DWORD cMsiData = 0; + LPWSTR* rgwzMsiData = NULL; + BURN_ELEVATION_MSI_MESSAGE_CONTEXT* pContext = static_cast(pvContext); + LPWSTR sczMessage = NULL; + + // Read MSI extended message data. + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &cMsiData); + ExitOnFailure(hr, "Failed to read MSI data count."); + + if (cMsiData) + { + rgwzMsiData = (LPWSTR*)MemAlloc(sizeof(LPWSTR*) * cMsiData, TRUE); + ExitOnNull(rgwzMsiData, hr, E_OUTOFMEMORY, "Failed to allocate buffer to read MSI data."); + + for (DWORD i = 0; i < cMsiData; ++i) + { + hr = BuffReadString((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &rgwzMsiData[i]); + ExitOnFailure(hr, "Failed to read MSI data: %u", i); + } + + message.cData = cMsiData; + message.rgwzData = (LPCWSTR*)rgwzMsiData; + } + + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.dwAllowedResults); + ExitOnFailure(hr, "Failed to read UI flags."); + + // Process the rest of the message. + switch (pMsg->dwMessage) + { + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS: + // read message parameters + message.type = WIU_MSI_EXECUTE_MESSAGE_PROGRESS; + + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.progress.dwPercentage); + ExitOnFailure(hr, "Failed to read progress."); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR: + // read message parameters + message.type = WIU_MSI_EXECUTE_MESSAGE_ERROR; + + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.error.dwErrorCode); + ExitOnFailure(hr, "Failed to read error code."); + + hr = BuffReadString((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &sczMessage); + ExitOnFailure(hr, "Failed to read message."); + message.error.wzMessage = sczMessage; + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_MESSAGE: + // read message parameters + message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE; + + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, (DWORD*)&message.msiMessage.mt); + ExitOnFailure(hr, "Failed to read message type."); + + hr = BuffReadString((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &sczMessage); + ExitOnFailure(hr, "Failed to read message."); + message.msiMessage.wzMessage = sczMessage; + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE: + message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE; + message.msiFilesInUse.cFiles = cMsiData; + message.msiFilesInUse.rgwzFiles = (LPCWSTR*)rgwzMsiData; + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package message."); + break; + } + + // send message + *pdwResult = (DWORD)pContext->pfnMessageHandler(&message, pContext->pvContext); + +LExit: + ReleaseStr(sczMessage); + + if (rgwzMsiData) + { + for (DWORD i = 0; i < cMsiData; ++i) + { + ReleaseStr(rgwzMsiData[i]); + } + + MemFree(rgwzMsiData); + } + + return hr; +} + +static HRESULT ProcessLaunchApprovedExeMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + BURN_ELEVATION_LAUNCH_APPROVED_EXE_MESSAGE_CONTEXT* pContext = static_cast(pvContext); + DWORD dwProcessId = 0; + + // Process the message. + switch (pMsg->dwMessage) + { + case BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_APPROVED_EXE_PROCESSID: + // read message parameters + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &dwProcessId); + ExitOnFailure(hr, "Failed to read approved exe process id."); + pContext->dwProcessId = dwProcessId; + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid launch approved exe message."); + break; + } + + *pdwResult = static_cast(hr); + +LExit: + return hr; +} + +static HRESULT ProcessProgressRoutineMessage( + __in BURN_PIPE_MESSAGE* pMsg, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LARGE_INTEGER liTotalFileSize = { }; + LARGE_INTEGER liTotalBytesTransferred = { }; + LARGE_INTEGER liStreamSize = { }; + LARGE_INTEGER liStreamBytesTransferred = { }; + DWORD dwStreamNumber = 0; + DWORD dwCallbackReason = CALLBACK_CHUNK_FINISHED; + HANDLE hSourceFile = INVALID_HANDLE_VALUE; + HANDLE hDestinationFile = INVALID_HANDLE_VALUE; + + hr = BuffReadNumber64((BYTE*)pMsg->pvData, pMsg->cbData, &iData, reinterpret_cast(&liTotalFileSize.QuadPart)); + ExitOnFailure(hr, "Failed to read total file size for progress."); + + hr = BuffReadNumber64((BYTE*)pMsg->pvData, pMsg->cbData, &iData, reinterpret_cast(&liTotalBytesTransferred.QuadPart)); + ExitOnFailure(hr, "Failed to read total bytes transferred for progress."); + + *pdwResult = pfnProgress(liTotalFileSize, liTotalBytesTransferred, liStreamSize, liStreamBytesTransferred, dwStreamNumber, dwCallbackReason, hSourceFile, hDestinationFile, pvContext); + +LExit: + return hr; +} + +static HRESULT ProcessElevatedChildMessage( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + BURN_ELEVATION_CHILD_MESSAGE_CONTEXT* pContext = static_cast(pvContext); + HRESULT hrResult = S_OK; + DWORD dwPid = 0; + + switch (pMsg->dwMessage) + { + case BURN_ELEVATION_MESSAGE_TYPE_BEGIN_MSI_TRANSACTION: + hrResult = OnMsiBeginTransaction(pContext->pPackages, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_COMMIT_MSI_TRANSACTION: + hrResult = OnMsiCommitTransaction(pContext->pPackages, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_ROLLBACK_MSI_TRANSACTION: + hrResult = OnMsiRollbackTransaction(pContext->pPackages, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE: + hrResult = OnApplyInitialize(pContext->hPipe, pContext->pVariables, pContext->pRegistration, pContext->phLock, pContext->pfDisabledAutomaticUpdates, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_APPLY_UNINITIALIZE: + hrResult = OnApplyUninitialize(pContext->phLock); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_SESSION_BEGIN: + hrResult = OnSessionBegin(pContext->pRegistration, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_SESSION_RESUME: + hrResult = OnSessionResume(pContext->pRegistration, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_SESSION_END: + hrResult = OnSessionEnd(pContext->pPackages, pContext->pRegistration, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_SAVE_STATE: + hrResult = OnSaveState(pContext->pRegistration, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_PROCESS_DEPENDENT_REGISTRATION: + hrResult = OnProcessDependentRegistration(pContext->pRegistration, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_EXE_PACKAGE: + hrResult = OnExecuteExePackage(pContext->hPipe, pContext->pPackages, &pContext->pRegistration->relatedBundles, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_PACKAGE: + hrResult = OnExecuteMsiPackage(pContext->hPipe, pContext->pPackages, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSP_PACKAGE: + hrResult = OnExecuteMspPackage(pContext->hPipe, pContext->pPackages, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSU_PACKAGE: + hrResult = OnExecuteMsuPackage(pContext->hPipe, pContext->pPackages, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PACKAGE_PROVIDER: + hrResult = OnExecutePackageProviderAction(pContext->pPackages, &pContext->pRegistration->relatedBundles, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PACKAGE_DEPENDENCY: + hrResult = OnExecutePackageDependencyAction(pContext->pPackages, &pContext->pRegistration->relatedBundles, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_CLEAN_PACKAGE: + hrResult = OnCleanPackage(pContext->pPackages, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_APPROVED_EXE: + hrResult = OnLaunchApprovedExe(pContext->hPipe, pContext->pApprovedExes, pContext->pVariables, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Unexpected elevated message sent to child process, msg: %u", pMsg->dwMessage); + } + + *pdwResult = dwPid ? dwPid : (DWORD)hrResult; + +LExit: + return hr; +} + +static HRESULT ProcessElevatedChildCacheMessage( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + BURN_ELEVATION_CHILD_MESSAGE_CONTEXT* pContext = static_cast(pvContext); + HRESULT hrResult = S_OK; + + switch (pMsg->dwMessage) + { + case BURN_ELEVATION_MESSAGE_TYPE_CACHE_COMPLETE_PAYLOAD: + hrResult = OnCacheCompletePayload(pContext->hPipe, pContext->pPackages, pContext->pPayloads, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_CACHE_VERIFY_PAYLOAD: + hrResult = OnCacheVerifyPayload(pContext->hPipe, pContext->pPackages, pContext->pPayloads, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + case BURN_ELEVATION_MESSAGE_TYPE_CACHE_CLEANUP: + OnCacheCleanup(pContext->pRegistration->sczId); + hrResult = S_OK; + break; + + case BURN_ELEVATION_MESSAGE_TYPE_CLEAN_PACKAGE: + hrResult = OnCleanPackage(pContext->pPackages, (BYTE*)pMsg->pvData, pMsg->cbData); + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Unexpected elevated cache message sent to child process, msg: %u", pMsg->dwMessage); + } + + *pdwResult = (DWORD)hrResult; + +LExit: + return hr; +} + +static HRESULT ProcessResult( + __in DWORD dwResult, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = static_cast(dwResult); + if (HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED) == hr) + { + *pRestart = BOOTSTRAPPER_APPLY_RESTART_REQUIRED; + hr = S_OK; + } + else if (HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED) == hr) + { + *pRestart = BOOTSTRAPPER_APPLY_RESTART_INITIATED; + hr = S_OK; + } + + return hr; +} + +static HRESULT OnApplyInitialize( + __in HANDLE hPipe, + __in BURN_VARIABLES* pVariables, + __in BURN_REGISTRATION* pRegistration, + __in HANDLE* phLock, + __in BOOL* pfDisabledWindowsUpdate, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + DWORD dwAction = 0; + DWORD dwAUAction = 0; + DWORD dwTakeSystemRestorePoint = 0; + LPWSTR sczBundleName = NULL; + HRESULT hrStatus = S_OK; + + // Deserialize message data. + hr = BuffReadNumber(pbData, cbData, &iData, &dwAction); + ExitOnFailure(hr, "Failed to read action."); + + hr = BuffReadNumber(pbData, cbData, &iData, &dwAUAction); + ExitOnFailure(hr, "Failed to read update action."); + + hr = BuffReadNumber(pbData, cbData, &iData, &dwTakeSystemRestorePoint); + ExitOnFailure(hr, "Failed to read system restore point action."); + + hr = VariableDeserialize(pVariables, FALSE, pbData, cbData, &iData); + ExitOnFailure(hr, "Failed to read variables."); + + // Initialize. + hr = ApplyLock(TRUE, phLock); + ExitOnFailure(hr, "Failed to acquire lock due to setup in other session."); + + // Reset and reload the related bundles. + RelatedBundlesUninitialize(&pRegistration->relatedBundles); + + hr = RelatedBundlesInitializeForScope(TRUE, pRegistration, &pRegistration->relatedBundles); + ExitOnFailure(hr, "Failed to initialize per-machine related bundles."); + + // Attempt to pause AU with best effort. + if (BURN_AU_PAUSE_ACTION_IFELEVATED == dwAUAction || BURN_AU_PAUSE_ACTION_IFELEVATED_NORESUME == dwAUAction) + { + hr = ElevatedOnPauseAUBegin(hPipe); + ExitOnFailure(hr, "ElevatedOnPauseAUBegin failed."); + + LogId(REPORT_STANDARD, MSG_PAUSE_AU_STARTING); + + hrStatus = hr = WuaPauseAutomaticUpdates(); + if (FAILED(hr)) + { + LogId(REPORT_STANDARD, MSG_FAILED_PAUSE_AU, hr); + hr = S_OK; + } + else + { + LogId(REPORT_STANDARD, MSG_PAUSE_AU_SUCCEEDED); + if (BURN_AU_PAUSE_ACTION_IFELEVATED == dwAUAction) + { + *pfDisabledWindowsUpdate = TRUE; + } + } + + hr = ElevatedOnPauseAUComplete(hPipe, hrStatus); + ExitOnFailure(hr, "ElevatedOnPauseAUComplete failed."); + } + + if (dwTakeSystemRestorePoint) + { + hr = VariableGetString(pVariables, BURN_BUNDLE_NAME, &sczBundleName); + if (FAILED(hr)) + { + hr = S_OK; + ExitFunction(); + } + + hr = ElevatedOnSystemRestorePointBegin(hPipe); + ExitOnFailure(hr, "ElevatedOnSystemRestorePointBegin failed."); + + LogId(REPORT_STANDARD, MSG_SYSTEM_RESTORE_POINT_STARTING); + + BOOTSTRAPPER_ACTION action = static_cast(dwAction); + SRP_ACTION restoreAction = (BOOTSTRAPPER_ACTION_INSTALL == action) ? SRP_ACTION_INSTALL : (BOOTSTRAPPER_ACTION_UNINSTALL == action) ? SRP_ACTION_UNINSTALL : SRP_ACTION_MODIFY; + hrStatus = hr = SrpCreateRestorePoint(sczBundleName, restoreAction); + if (SUCCEEDED(hr)) + { + LogId(REPORT_STANDARD, MSG_SYSTEM_RESTORE_POINT_SUCCEEDED); + } + else if (E_NOTIMPL == hr) + { + LogId(REPORT_STANDARD, MSG_SYSTEM_RESTORE_POINT_DISABLED); + hr = S_OK; + } + else + { + LogId(REPORT_STANDARD, MSG_SYSTEM_RESTORE_POINT_FAILED, hr); + hr = S_OK; + } + + hr = ElevatedOnSystemRestorePointComplete(hPipe, hrStatus); + ExitOnFailure(hr, "ElevatedOnSystemRestorePointComplete failed."); + } + +LExit: + ReleaseStr(sczBundleName); + return hr; +} + +static HRESULT OnApplyUninitialize( + __in HANDLE* phLock + ) +{ + Assert(phLock); + + // TODO: end system restore point. + + if (*phLock) + { + ::ReleaseMutex(*phLock); + ::CloseHandle(*phLock); + *phLock = NULL; + } + + return S_OK; +} + +static HRESULT OnSessionBegin( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczEngineWorkingPath = NULL; + DWORD dwRegistrationOperations = 0; + DWORD dwDependencyRegistrationAction = 0; + DWORD64 qwEstimatedSize = 0; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &sczEngineWorkingPath); + ExitOnFailure(hr, "Failed to read engine working path."); + + hr = BuffReadString(pbData, cbData, &iData, &pRegistration->sczResumeCommandLine); + ExitOnFailure(hr, "Failed to read resume command line."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&pRegistration->fDisableResume); + ExitOnFailure(hr, "Failed to read resume flag."); + + hr = BuffReadNumber(pbData, cbData, &iData, &dwRegistrationOperations); + ExitOnFailure(hr, "Failed to read registration operations."); + + hr = BuffReadNumber(pbData, cbData, &iData, &dwDependencyRegistrationAction); + ExitOnFailure(hr, "Failed to read dependency registration action."); + + hr = BuffReadNumber64(pbData, cbData, &iData, &qwEstimatedSize); + ExitOnFailure(hr, "Failed to read estimated size."); + + hr = VariableDeserialize(pVariables, FALSE, pbData, cbData, &iData); + ExitOnFailure(hr, "Failed to read variables."); + + // Begin session in per-machine process. + hr = RegistrationSessionBegin(sczEngineWorkingPath, pRegistration, pVariables, dwRegistrationOperations, (BURN_DEPENDENCY_REGISTRATION_ACTION)dwDependencyRegistrationAction, qwEstimatedSize); + ExitOnFailure(hr, "Failed to begin registration session."); + +LExit: + ReleaseStr(sczEngineWorkingPath); + + return hr; +} + +static HRESULT OnSessionResume( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &pRegistration->sczResumeCommandLine); + ExitOnFailure(hr, "Failed to read resume command line."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&pRegistration->fDisableResume); + ExitOnFailure(hr, "Failed to read resume flag."); + + hr = VariableDeserialize(pVariables, FALSE, pbData, cbData, &iData); + ExitOnFailure(hr, "Failed to read variables."); + + // resume session in per-machine process + hr = RegistrationSessionResume(pRegistration, pVariables); + ExitOnFailure(hr, "Failed to resume registration session."); + +LExit: + return hr; +} + +static HRESULT OnSessionEnd( + __in BURN_PACKAGES* pPackages, + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + DWORD dwResumeMode = 0; + DWORD dwRestart = 0; + DWORD dwDependencyRegistrationAction = 0; + + // Deserialize message data. + hr = BuffReadNumber(pbData, cbData, &iData, &dwResumeMode); + ExitOnFailure(hr, "Failed to read resume mode enum."); + + hr = BuffReadNumber(pbData, cbData, &iData, &dwRestart); + ExitOnFailure(hr, "Failed to read restart enum."); + + hr = BuffReadNumber(pbData, cbData, &iData, &dwDependencyRegistrationAction); + ExitOnFailure(hr, "Failed to read dependency registration action."); + + // suspend session in per-machine process + hr = RegistrationSessionEnd(pRegistration, pVariables, pPackages, (BURN_RESUME_MODE)dwResumeMode, (BOOTSTRAPPER_APPLY_RESTART)dwRestart, (BURN_DEPENDENCY_REGISTRATION_ACTION)dwDependencyRegistrationAction); + ExitOnFailure(hr, "Failed to suspend registration session."); + +LExit: + return hr; +} + +static HRESULT OnSaveState( + __in BURN_REGISTRATION* pRegistration, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + + // save state in per-machine process + hr = RegistrationSaveState(pRegistration, pbData, cbData); + ExitOnFailure(hr, "Failed to save state."); + +LExit: + return hr; +} + +static HRESULT OnCacheCompletePayload( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOADS* pPayloads, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR scz = NULL; + BURN_PACKAGE* pPackage = NULL; + BURN_PAYLOAD* pPayload = NULL; + LPWSTR sczUnverifiedPath = NULL; + BOOL fMove = FALSE; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &scz); + ExitOnFailure(hr, "Failed to read package id."); + + if (scz && *scz) + { + hr = PackageFindById(pPackages, scz, &pPackage); + ExitOnFailure(hr, "Failed to find package: %ls", scz); + } + + hr = BuffReadString(pbData, cbData, &iData, &scz); + ExitOnFailure(hr, "Failed to read payload id."); + + if (scz && *scz) + { + hr = PayloadFindById(pPayloads, scz, &pPayload); + ExitOnFailure(hr, "Failed to find payload: %ls", scz); + } + + hr = BuffReadString(pbData, cbData, &iData, &sczUnverifiedPath); + ExitOnFailure(hr, "Failed to read unverified path."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fMove); + ExitOnFailure(hr, "Failed to read move flag."); + + if (pPackage && pPayload) // complete payload. + { + hr = CacheCompletePayload(pPackage->fPerMachine, pPayload, pPackage->sczCacheId, sczUnverifiedPath, fMove, BurnCacheMessageHandler, ElevatedProgressRoutine, hPipe); + ExitOnFailure(hr, "Failed to cache payload: %ls", pPayload->sczKey); + } + else + { + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid data passed to cache complete payload."); + } + +LExit: + ReleaseStr(sczUnverifiedPath); + ReleaseStr(scz); + + return hr; +} + +static HRESULT OnCacheVerifyPayload( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOADS* pPayloads, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR scz = NULL; + BURN_PACKAGE* pPackage = NULL; + BURN_PAYLOAD* pPayload = NULL; + LPWSTR sczCacheDirectory = NULL; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &scz); + ExitOnFailure(hr, "Failed to read package id."); + + if (scz && *scz) + { + hr = PackageFindById(pPackages, scz, &pPackage); + ExitOnFailure(hr, "Failed to find package: %ls", scz); + } + + hr = BuffReadString(pbData, cbData, &iData, &scz); + ExitOnFailure(hr, "Failed to read payload id."); + + if (scz && *scz) + { + hr = PayloadFindById(pPayloads, scz, &pPayload); + ExitOnFailure(hr, "Failed to find payload: %ls", scz); + } + + if (pPackage && pPayload) + { + hr = CacheGetCompletedPath(TRUE, pPackage->sczCacheId, &sczCacheDirectory); + ExitOnFailure(hr, "Failed to get cached path for package with cache id: %ls", pPackage->sczCacheId); + + hr = CacheVerifyPayload(pPayload, sczCacheDirectory, BurnCacheMessageHandler, ElevatedProgressRoutine, hPipe); + } + else + { + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid data passed to cache verify payload."); + } + // Nothing should be logged on failure. + +LExit: + ReleaseStr(sczCacheDirectory); + ReleaseStr(scz); + + return hr; +} + +static void OnCacheCleanup( + __in_z LPCWSTR wzBundleId + ) +{ + CacheCleanup(TRUE, wzBundleId); +} + +static HRESULT OnProcessDependentRegistration( + __in const BURN_REGISTRATION* pRegistration, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + BURN_DEPENDENT_REGISTRATION_ACTION action = { }; + + // Deserialize message data. + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&action.type); + ExitOnFailure(hr, "Failed to read action type."); + + hr = BuffReadString(pbData, cbData, &iData, &action.sczBundleId); + ExitOnFailure(hr, "Failed to read bundle id."); + + hr = BuffReadString(pbData, cbData, &iData, &action.sczDependentProviderKey); + ExitOnFailure(hr, "Failed to read dependent provider key."); + + // Execute the registration action. + hr = DependencyProcessDependentRegistration(pRegistration, &action); + ExitOnFailure(hr, "Failed to execute dependent registration action for provider key: %ls", action.sczDependentProviderKey); + +LExit: + // TODO: do the right thing here. + //DependencyUninitializeRegistrationAction(&action); + ReleaseStr(action.sczDependentProviderKey); + ReleaseStr(action.sczBundleId) + + return hr; +} + +static HRESULT OnExecuteExePackage( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_RELATED_BUNDLES* pRelatedBundles, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczPackage = NULL; + DWORD dwRollback = 0; + BURN_EXECUTE_ACTION executeAction = { }; + LPWSTR sczIgnoreDependencies = NULL; + LPWSTR sczAncestors = NULL; + BOOTSTRAPPER_APPLY_RESTART exeRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; + + executeAction.type = BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &sczPackage); + ExitOnFailure(hr, "Failed to read EXE package id."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.exePackage.action); + ExitOnFailure(hr, "Failed to read action."); + + hr = BuffReadNumber(pbData, cbData, &iData, &dwRollback); + ExitOnFailure(hr, "Failed to read rollback."); + + hr = BuffReadString(pbData, cbData, &iData, &sczIgnoreDependencies); + ExitOnFailure(hr, "Failed to read the list of dependencies to ignore."); + + hr = BuffReadString(pbData, cbData, &iData, &sczAncestors); + ExitOnFailure(hr, "Failed to read the list of ancestors."); + + hr = VariableDeserialize(pVariables, FALSE, pbData, cbData, &iData); + ExitOnFailure(hr, "Failed to read variables."); + + hr = PackageFindById(pPackages, sczPackage, &executeAction.exePackage.pPackage); + if (E_NOTFOUND == hr) + { + hr = PackageFindRelatedById(pRelatedBundles, sczPackage, &executeAction.exePackage.pPackage); + } + ExitOnFailure(hr, "Failed to find package: %ls", sczPackage); + + // Pass the list of dependencies to ignore, if any, to the related bundle. + if (sczIgnoreDependencies && *sczIgnoreDependencies) + { + hr = StrAllocString(&executeAction.exePackage.sczIgnoreDependencies, sczIgnoreDependencies, 0); + ExitOnFailure(hr, "Failed to allocate the list of dependencies to ignore."); + } + + // Pass the list of ancestors, if any, to the related bundle. + if (sczAncestors && *sczAncestors) + { + hr = StrAllocString(&executeAction.exePackage.sczAncestors, sczAncestors, 0); + ExitOnFailure(hr, "Failed to allocate the list of ancestors."); + } + + // Execute EXE package. + hr = ExeEngineExecutePackage(&executeAction, pVariables, static_cast(dwRollback), GenericExecuteMessageHandler, hPipe, &exeRestart); + ExitOnFailure(hr, "Failed to execute EXE package."); + +LExit: + ReleaseStr(sczAncestors); + ReleaseStr(sczIgnoreDependencies); + ReleaseStr(sczPackage); + PlanUninitializeExecuteAction(&executeAction); + + if (SUCCEEDED(hr)) + { + if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == exeRestart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED); + } + else if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == exeRestart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED); + } + } + + return hr; +} + +static HRESULT OnExecuteMsiPackage( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczPackage = NULL; + HWND hwndParent = NULL; + BOOL fRollback = 0; + BURN_EXECUTE_ACTION executeAction = { }; + BOOTSTRAPPER_APPLY_RESTART msiRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; + + executeAction.type = BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE; + + // Deserialize message data. + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fRollback); + ExitOnFailure(hr, "Failed to read rollback flag."); + + hr = BuffReadString(pbData, cbData, &iData, &sczPackage); + ExitOnFailure(hr, "Failed to read MSI package id."); + + hr = PackageFindById(pPackages, sczPackage, &executeAction.msiPackage.pPackage); + ExitOnFailure(hr, "Failed to find package: %ls", sczPackage); + + hr = BuffReadPointer(pbData, cbData, &iData, (DWORD_PTR*)&hwndParent); + ExitOnFailure(hr, "Failed to read parent hwnd."); + + hr = BuffReadString(pbData, cbData, &iData, &executeAction.msiPackage.sczLogPath); + ExitOnFailure(hr, "Failed to read package log."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.msiPackage.actionMsiProperty); + ExitOnFailure(hr, "Failed to read actionMsiProperty."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.msiPackage.uiLevel); + ExitOnFailure(hr, "Failed to read UI level."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.msiPackage.fDisableExternalUiHandler); + ExitOnFailure(hr, "Failed to read fDisableExternalUiHandler."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.msiPackage.action); + ExitOnFailure(hr, "Failed to read action."); + + // Read feature actions. + if (executeAction.msiPackage.pPackage->Msi.cFeatures) + { + executeAction.msiPackage.rgFeatures = (BOOTSTRAPPER_FEATURE_ACTION*)MemAlloc(executeAction.msiPackage.pPackage->Msi.cFeatures * sizeof(BOOTSTRAPPER_FEATURE_ACTION), TRUE); + ExitOnNull(executeAction.msiPackage.rgFeatures, hr, E_OUTOFMEMORY, "Failed to allocate memory for feature actions."); + + for (DWORD i = 0; i < executeAction.msiPackage.pPackage->Msi.cFeatures; ++i) + { + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.msiPackage.rgFeatures[i]); + ExitOnFailure(hr, "Failed to read feature action."); + } + } + + // Read slipstream patches actions. + if (executeAction.msiPackage.pPackage->Msi.cSlipstreamMspPackages) + { + for (DWORD i = 0; i < executeAction.msiPackage.pPackage->Msi.cSlipstreamMspPackages; ++i) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = executeAction.msiPackage.pPackage->Msi.rgSlipstreamMsps + i; + BOOTSTRAPPER_ACTION_STATE* pAction = fRollback ? &pSlipstreamMsp->rollback : &pSlipstreamMsp->execute; + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)pAction); + ExitOnFailure(hr, "Failed to read slipstream action."); + } + } + + hr = VariableDeserialize(pVariables, FALSE, pbData, cbData, &iData); + ExitOnFailure(hr, "Failed to read variables."); + + // Execute MSI package. + hr = MsiEngineExecutePackage(hwndParent, &executeAction, pVariables, fRollback, MsiExecuteMessageHandler, hPipe, &msiRestart); + ExitOnFailure(hr, "Failed to execute MSI package."); + +LExit: + ReleaseStr(sczPackage); + PlanUninitializeExecuteAction(&executeAction); + + if (SUCCEEDED(hr)) + { + if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == msiRestart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED); + } + else if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == msiRestart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED); + } + } + + return hr; +} + +static HRESULT OnExecuteMspPackage( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczPackage = NULL; + HWND hwndParent = NULL; + BOOL fRollback = 0; + BURN_EXECUTE_ACTION executeAction = { }; + BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE; + + executeAction.type = BURN_EXECUTE_ACTION_TYPE_MSP_TARGET; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &sczPackage); + ExitOnFailure(hr, "Failed to read MSP package id."); + + hr = PackageFindById(pPackages, sczPackage, &executeAction.mspTarget.pPackage); + ExitOnFailure(hr, "Failed to find package: %ls", sczPackage); + + hr = BuffReadPointer(pbData, cbData, &iData, (DWORD_PTR*)&hwndParent); + ExitOnFailure(hr, "Failed to read parent hwnd."); + + executeAction.mspTarget.fPerMachineTarget = TRUE; // we're in the elevated process, clearly we're targeting a per-machine product. + + hr = BuffReadString(pbData, cbData, &iData, &executeAction.mspTarget.sczTargetProductCode); + ExitOnFailure(hr, "Failed to read target product code."); + + hr = BuffReadString(pbData, cbData, &iData, &executeAction.mspTarget.sczLogPath); + ExitOnFailure(hr, "Failed to read package log."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.mspTarget.actionMsiProperty); + ExitOnFailure(hr, "Failed to read actionMsiProperty."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.mspTarget.uiLevel); + ExitOnFailure(hr, "Failed to read UI level."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.mspTarget.fDisableExternalUiHandler); + ExitOnFailure(hr, "Failed to read fDisableExternalUiHandler."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.mspTarget.action); + ExitOnFailure(hr, "Failed to read action."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&executeAction.mspTarget.cOrderedPatches); + ExitOnFailure(hr, "Failed to read count of ordered patches."); + + if (executeAction.mspTarget.cOrderedPatches) + { + executeAction.mspTarget.rgOrderedPatches = (BURN_ORDERED_PATCHES*)MemAlloc(executeAction.mspTarget.cOrderedPatches * sizeof(BURN_ORDERED_PATCHES), TRUE); + ExitOnNull(executeAction.mspTarget.rgOrderedPatches, hr, E_OUTOFMEMORY, "Failed to allocate memory for ordered patches."); + + for (DWORD i = 0; i < executeAction.mspTarget.cOrderedPatches; ++i) + { + hr = BuffReadString(pbData, cbData, &iData, &sczPackage); + ExitOnFailure(hr, "Failed to read ordered patch package id."); + + hr = PackageFindById(pPackages, sczPackage, &executeAction.mspTarget.rgOrderedPatches[i].pPackage); + ExitOnFailure(hr, "Failed to find ordered patch package: %ls", sczPackage); + } + } + + hr = VariableDeserialize(pVariables, FALSE, pbData, cbData, &iData); + ExitOnFailure(hr, "Failed to read variables."); + + hr = BuffReadNumber(pbData, cbData, &iData, (DWORD*)&fRollback); + ExitOnFailure(hr, "Failed to read rollback flag."); + + // Execute MSP package. + hr = MspEngineExecutePackage(hwndParent, &executeAction, pVariables, fRollback, MsiExecuteMessageHandler, hPipe, &restart); + ExitOnFailure(hr, "Failed to execute MSP package."); + +LExit: + ReleaseStr(sczPackage); + PlanUninitializeExecuteAction(&executeAction); + + if (SUCCEEDED(hr)) + { + if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == restart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED); + } + else if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == restart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED); + } + } + + return hr; +} + +static HRESULT OnExecuteMsuPackage( + __in HANDLE hPipe, + __in BURN_PACKAGES* pPackages, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczPackage = NULL; + DWORD dwRollback = 0; + DWORD dwStopWusaService = 0; + BURN_EXECUTE_ACTION executeAction = { }; + BOOTSTRAPPER_APPLY_RESTART restart = BOOTSTRAPPER_APPLY_RESTART_NONE; + + executeAction.type = BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &sczPackage); + ExitOnFailure(hr, "Failed to read MSU package id."); + + hr = BuffReadString(pbData, cbData, &iData, &executeAction.msuPackage.sczLogPath); + ExitOnFailure(hr, "Failed to read package log."); + + hr = BuffReadNumber(pbData, cbData, &iData, reinterpret_cast(&executeAction.msuPackage.action)); + ExitOnFailure(hr, "Failed to read action."); + + hr = BuffReadNumber(pbData, cbData, &iData, &dwRollback); + ExitOnFailure(hr, "Failed to read rollback."); + + hr = BuffReadNumber(pbData, cbData, &iData, &dwStopWusaService); + ExitOnFailure(hr, "Failed to read StopWusaService."); + + hr = PackageFindById(pPackages, sczPackage, &executeAction.msuPackage.pPackage); + ExitOnFailure(hr, "Failed to find package: %ls", sczPackage); + + // execute MSU package + hr = MsuEngineExecutePackage(&executeAction, pVariables, static_cast(dwRollback), static_cast(dwStopWusaService), GenericExecuteMessageHandler, hPipe, &restart); + ExitOnFailure(hr, "Failed to execute MSU package."); + +LExit: + ReleaseStr(sczPackage); + PlanUninitializeExecuteAction(&executeAction); + + if (SUCCEEDED(hr)) + { + if (BOOTSTRAPPER_APPLY_RESTART_REQUIRED == restart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED); + } + else if (BOOTSTRAPPER_APPLY_RESTART_INITIATED == restart) + { + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED); + } + } + + return hr; +} + +static HRESULT OnExecutePackageProviderAction( + __in BURN_PACKAGES* pPackages, + __in BURN_RELATED_BUNDLES* pRelatedBundles, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczPackage = NULL; + BURN_EXECUTE_ACTION executeAction = { }; + + executeAction.type = BURN_EXECUTE_ACTION_TYPE_PACKAGE_PROVIDER; + + // Deserialize the message data. + hr = BuffReadString(pbData, cbData, &iData, &sczPackage); + ExitOnFailure(hr, "Failed to read package id from message buffer."); + + hr = BuffReadNumber(pbData, cbData, &iData, reinterpret_cast(&executeAction.packageProvider.action)); + ExitOnFailure(hr, "Failed to read action."); + + // Find the package again. + hr = PackageFindById(pPackages, sczPackage, &executeAction.packageProvider.pPackage); + if (E_NOTFOUND == hr) + { + hr = PackageFindRelatedById(pRelatedBundles, sczPackage, &executeAction.packageProvider.pPackage); + } + ExitOnFailure(hr, "Failed to find package: %ls", sczPackage); + + // Execute the package provider action. + hr = DependencyExecutePackageProviderAction(&executeAction); + ExitOnFailure(hr, "Failed to execute package provider action."); + +LExit: + ReleaseStr(sczPackage); + PlanUninitializeExecuteAction(&executeAction); + + return hr; +} + +static HRESULT OnExecutePackageDependencyAction( + __in BURN_PACKAGES* pPackages, + __in BURN_RELATED_BUNDLES* pRelatedBundles, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczPackage = NULL; + BURN_EXECUTE_ACTION executeAction = { }; + + executeAction.type = BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY; + + // Deserialize the message data. + hr = BuffReadString(pbData, cbData, &iData, &sczPackage); + ExitOnFailure(hr, "Failed to read package id from message buffer."); + + hr = BuffReadString(pbData, cbData, &iData, &executeAction.packageDependency.sczBundleProviderKey); + ExitOnFailure(hr, "Failed to read bundle dependency key from message buffer."); + + hr = BuffReadNumber(pbData, cbData, &iData, reinterpret_cast(&executeAction.packageDependency.action)); + ExitOnFailure(hr, "Failed to read action."); + + // Find the package again. + hr = PackageFindById(pPackages, sczPackage, &executeAction.packageDependency.pPackage); + if (E_NOTFOUND == hr) + { + hr = PackageFindRelatedById(pRelatedBundles, sczPackage, &executeAction.packageDependency.pPackage); + } + ExitOnFailure(hr, "Failed to find package: %ls", sczPackage); + + // Execute the package dependency action. + hr = DependencyExecutePackageDependencyAction(TRUE, &executeAction); + ExitOnFailure(hr, "Failed to execute package dependency action."); + +LExit: + ReleaseStr(sczPackage); + PlanUninitializeExecuteAction(&executeAction); + + return hr; +} + +static HRESULT CALLBACK BurnCacheMessageHandler( + __in BURN_CACHE_MESSAGE* pMessage, + __in LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + HANDLE hPipe = (HANDLE)pvContext; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwMessage = 0; + + switch (pMessage->type) + { + case BURN_CACHE_MESSAGE_BEGIN: + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, pMessage->begin.cacheStep); + ExitOnFailure(hr, "Failed to write progress percentage to message buffer."); + + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_BEGIN; + break; + + case BURN_CACHE_MESSAGE_COMPLETE: + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, pMessage->complete.hrStatus); + ExitOnFailure(hr, "Failed to write error code to message buffer."); + + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE; + break; + + case BURN_CACHE_MESSAGE_SUCCESS: + hr = BuffWriteNumber64(&pbData, &cbData, pMessage->success.qwFileSize); + ExitOnFailure(hr, "Failed to count of files in use to message buffer."); + + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS; + break; + } + + // send message + hr = PipeSendMessage(hPipe, dwMessage, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send burn cache message to per-user process."); + + hr = dwResult; + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +static DWORD CALLBACK ElevatedProgressRoutine( + __in LARGE_INTEGER TotalFileSize, + __in LARGE_INTEGER TotalBytesTransferred, + __in LARGE_INTEGER /*StreamSize*/, + __in LARGE_INTEGER /*StreamBytesTransferred*/, + __in DWORD /*dwStreamNumber*/, + __in DWORD /*dwCallbackReason*/, + __in HANDLE /*hSourceFile*/, + __in HANDLE /*hDestinationFile*/, + __in_opt LPVOID lpData + ) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + HANDLE hPipe = (HANDLE)lpData; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwMessage = BURN_ELEVATION_MESSAGE_TYPE_PROGRESS_ROUTINE; + + hr = BuffWriteNumber64(&pbData, &cbData, TotalFileSize.QuadPart); + ExitOnFailure(hr, "Failed to write total file size progress to message buffer."); + + hr = BuffWriteNumber64(&pbData, &cbData, TotalBytesTransferred.QuadPart); + ExitOnFailure(hr, "Failed to write total bytes transferred progress to message buffer."); + + // send message + hr = PipeSendMessage(hPipe, dwMessage, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send progress routine message to per-user process."); + +LExit: + ReleaseBuffer(pbData); + + return dwResult; +} + +static int GenericExecuteMessageHandler( + __in GENERIC_EXECUTE_MESSAGE* pMessage, + __in LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + int nResult = IDOK; + HANDLE hPipe = (HANDLE)pvContext; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwMessage = 0; + + hr = BuffWriteNumber(&pbData, &cbData, pMessage->dwAllowedResults); + ExitOnFailure(hr, "Failed to write UI flags."); + + switch(pMessage->type) + { + case GENERIC_EXECUTE_MESSAGE_PROGRESS: + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, pMessage->progress.dwPercentage); + ExitOnFailure(hr, "Failed to write progress percentage to message buffer."); + + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS; + break; + + case GENERIC_EXECUTE_MESSAGE_ERROR: + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, pMessage->error.dwErrorCode); + ExitOnFailure(hr, "Failed to write error code to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pMessage->error.wzMessage); + ExitOnFailure(hr, "Failed to write message to message buffer."); + + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR; + break; + + case GENERIC_EXECUTE_MESSAGE_FILES_IN_USE: + hr = BuffWriteNumber(&pbData, &cbData, pMessage->filesInUse.cFiles); + ExitOnFailure(hr, "Failed to count of files in use to message buffer."); + + for (DWORD i = 0; i < pMessage->filesInUse.cFiles; ++i) + { + hr = BuffWriteString(&pbData, &cbData, pMessage->filesInUse.rgwzFiles[i]); + ExitOnFailure(hr, "Failed to write file in use to message buffer."); + } + + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE; + break; + } + + // send message + hr = PipeSendMessage(hPipe, dwMessage, pbData, cbData, NULL, NULL, reinterpret_cast(&nResult)); + ExitOnFailure(hr, "Failed to send message to per-user process."); + +LExit: + ReleaseBuffer(pbData); + + return nResult; +} + +static int MsiExecuteMessageHandler( + __in WIU_MSI_EXECUTE_MESSAGE* pMessage, + __in_opt LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + int nResult = IDOK; + HANDLE hPipe = (HANDLE)pvContext; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwMessage = 0; + + // Always send any extra data via the struct first. + hr = BuffWriteNumber(&pbData, &cbData, pMessage->cData); + ExitOnFailure(hr, "Failed to write MSI data count to message buffer."); + + for (DWORD i = 0; i < pMessage->cData; ++i) + { + hr = BuffWriteString(&pbData, &cbData, pMessage->rgwzData[i]); + ExitOnFailure(hr, "Failed to write MSI data to message buffer."); + } + + hr = BuffWriteNumber(&pbData, &cbData, pMessage->dwAllowedResults); + ExitOnFailure(hr, "Failed to write UI flags."); + + switch (pMessage->type) + { + case WIU_MSI_EXECUTE_MESSAGE_PROGRESS: + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, pMessage->progress.dwPercentage); + ExitOnFailure(hr, "Failed to write progress percentage to message buffer."); + + // set message id + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS; + break; + + case WIU_MSI_EXECUTE_MESSAGE_ERROR: + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, pMessage->error.dwErrorCode); + ExitOnFailure(hr, "Failed to write error code to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pMessage->error.wzMessage); + ExitOnFailure(hr, "Failed to write message to message buffer."); + + // set message id + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR; + break; + + case WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE: + // serialize message data + hr = BuffWriteNumber(&pbData, &cbData, (DWORD)pMessage->msiMessage.mt); + ExitOnFailure(hr, "Failed to write MSI message type to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, pMessage->msiMessage.wzMessage); + ExitOnFailure(hr, "Failed to write message to message buffer."); + + // set message id + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_MESSAGE; + break; + + case WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE: + // NOTE: we do not serialize other message data here because all the "files in use" are in the data above. + + // set message id + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_FILES_IN_USE; + break; + + default: + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Invalid message type: %d", pMessage->type); + } + + // send message + hr = PipeSendMessage(hPipe, dwMessage, pbData, cbData, NULL, NULL, (DWORD*)&nResult); + ExitOnFailure(hr, "Failed to send msi message to per-user process."); + +LExit: + ReleaseBuffer(pbData); + + return nResult; +} + +static HRESULT OnCleanPackage( + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczPackage = NULL; + BURN_PACKAGE* pPackage = NULL; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &sczPackage); + ExitOnFailure(hr, "Failed to read package id."); + + hr = PackageFindById(pPackages, sczPackage, &pPackage); + ExitOnFailure(hr, "Failed to find package: %ls", sczPackage); + + // Remove the package from the cache. + hr = CacheRemovePackage(TRUE, pPackage->sczId, pPackage->sczCacheId); + ExitOnFailure(hr, "Failed to remove from cache package: %ls", pPackage->sczId); + +LExit: + ReleaseStr(sczPackage); + return hr; +} + +static HRESULT OnLaunchApprovedExe( + __in HANDLE hPipe, + __in BURN_APPROVED_EXES* pApprovedExes, + __in BURN_VARIABLES* pVariables, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe = NULL; + BURN_APPROVED_EXE* pApprovedExe = NULL; + REGSAM samDesired = KEY_QUERY_VALUE; + HKEY hKey = NULL; + DWORD dwProcessId = 0; + BYTE* pbSendData = NULL; + SIZE_T cbSendData = 0; + DWORD dwResult = 0; + + pLaunchApprovedExe = (BURN_LAUNCH_APPROVED_EXE*)MemAlloc(sizeof(BURN_LAUNCH_APPROVED_EXE), TRUE); + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &pLaunchApprovedExe->sczId); + ExitOnFailure(hr, "Failed to read approved exe id."); + + hr = BuffReadString(pbData, cbData, &iData, &pLaunchApprovedExe->sczArguments); + ExitOnFailure(hr, "Failed to read approved exe arguments."); + + hr = BuffReadNumber(pbData, cbData, &iData, &pLaunchApprovedExe->dwWaitForInputIdleTimeout); + ExitOnFailure(hr, "Failed to read approved exe WaitForInputIdle timeout."); + + hr = ApprovedExesFindById(pApprovedExes, pLaunchApprovedExe->sczId, &pApprovedExe); + ExitOnFailure(hr, "The per-user process requested unknown approved exe with id: %ls", pLaunchApprovedExe->sczId); + + LogId(REPORT_STANDARD, MSG_LAUNCH_APPROVED_EXE_SEARCH, pApprovedExe->sczKey, pApprovedExe->sczValueName ? pApprovedExe->sczValueName : L"", pApprovedExe->fWin64 ? L"yes" : L"no"); + + if (pApprovedExe->fWin64) + { + samDesired |= KEY_WOW64_64KEY; + } + + hr = RegOpen(HKEY_LOCAL_MACHINE, pApprovedExe->sczKey, samDesired, &hKey); + ExitOnFailure(hr, "Failed to open the registry key for the approved exe path."); + + hr = RegReadString(hKey, pApprovedExe->sczValueName, &pLaunchApprovedExe->sczExecutablePath); + ExitOnFailure(hr, "Failed to read the value for the approved exe path."); + + hr = ApprovedExesVerifySecureLocation(pVariables, pLaunchApprovedExe); + ExitOnFailure(hr, "Failed to verify the executable path is in a secure location: %ls", pLaunchApprovedExe->sczExecutablePath); + if (S_FALSE == hr) + { + LogStringLine(REPORT_STANDARD, "The executable path is not in a secure location: %ls", pLaunchApprovedExe->sczExecutablePath); + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)); + } + + hr = ApprovedExesLaunch(pVariables, pLaunchApprovedExe, &dwProcessId); + ExitOnFailure(hr, "Failed to launch approved exe: %ls", pLaunchApprovedExe->sczExecutablePath); + + //send process id over pipe + hr = BuffWriteNumber(&pbSendData, &cbSendData, dwProcessId); + ExitOnFailure(hr, "Failed to write the approved exe process id to message buffer."); + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_APPROVED_EXE_PROCESSID, pbSendData, cbSendData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_LAUNCH_APPROVED_EXE_PROCESSID message to per-user process."); + +LExit: + ReleaseBuffer(pbSendData); + ApprovedExesUninitializeLaunch(pLaunchApprovedExe); + return hr; +} + +static HRESULT OnMsiBeginTransaction( + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczId = NULL; + LPWSTR sczLogPath = NULL; + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &sczId); + ExitOnFailure(hr, "Failed to read rollback boundary id."); + + hr = BuffReadString(pbData, cbData, &iData, &sczLogPath); + ExitOnFailure(hr, "Failed to read transaction log path."); + + hr = PackageFindRollbackBoundaryById(pPackages, sczId, &pRollbackBoundary); + ExitOnFailure(hr, "Failed to find rollback boundary: %ls", sczId); + + pRollbackBoundary->sczLogPath = sczLogPath; + + hr = MsiEngineBeginTransaction(pRollbackBoundary); + +LExit: + ReleaseStr(sczId); + ReleaseStr(sczLogPath); + + if (pRollbackBoundary) + { + pRollbackBoundary->sczLogPath = NULL; + } + + return hr; +} + +static HRESULT OnMsiCommitTransaction( + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczId = NULL; + LPWSTR sczLogPath = NULL; + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &sczId); + ExitOnFailure(hr, "Failed to read rollback boundary id."); + + hr = BuffReadString(pbData, cbData, &iData, &sczLogPath); + ExitOnFailure(hr, "Failed to read transaction log path."); + + hr = PackageFindRollbackBoundaryById(pPackages, sczId, &pRollbackBoundary); + ExitOnFailure(hr, "Failed to find rollback boundary: %ls", sczId); + + pRollbackBoundary->sczLogPath = sczLogPath; + + hr = MsiEngineCommitTransaction(pRollbackBoundary); + +LExit: + ReleaseStr(sczId); + ReleaseStr(sczLogPath); + + if (pRollbackBoundary) + { + pRollbackBoundary->sczLogPath = NULL; + } + + return hr; +} + +static HRESULT OnMsiRollbackTransaction( + __in BURN_PACKAGES* pPackages, + __in BYTE* pbData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + LPWSTR sczId = NULL; + LPWSTR sczLogPath = NULL; + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL; + + // Deserialize message data. + hr = BuffReadString(pbData, cbData, &iData, &sczId); + ExitOnFailure(hr, "Failed to read rollback boundary id."); + + hr = BuffReadString(pbData, cbData, &iData, &sczLogPath); + ExitOnFailure(hr, "Failed to read transaction log path."); + + hr = PackageFindRollbackBoundaryById(pPackages, sczId, &pRollbackBoundary); + ExitOnFailure(hr, "Failed to find rollback boundary: %ls", sczId); + + pRollbackBoundary->sczLogPath = sczLogPath; + + hr = MsiEngineRollbackTransaction(pRollbackBoundary); + +LExit: + ReleaseStr(sczId); + ReleaseStr(sczLogPath); + + if (pRollbackBoundary) + { + pRollbackBoundary->sczLogPath = NULL; + } + + return hr; +} + +static HRESULT ElevatedOnPauseAUBegin( + __in HANDLE hPipe + ) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_BEGIN, NULL, 0, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_BEGIN message to per-user process."); + +LExit: + return hr; +} + +static HRESULT ElevatedOnPauseAUComplete( + __in HANDLE hPipe, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BYTE* pbSendData = NULL; + SIZE_T cbSendData = 0; + DWORD dwResult = 0; + + hr = BuffWriteNumber(&pbSendData, &cbSendData, hrStatus); + ExitOnFailure(hr, "Failed to write the pause au status to message buffer."); + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_COMPLETE, pbSendData, cbSendData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_PAUSE_AU_COMPLETE message to per-user process."); + +LExit: + ReleaseBuffer(pbSendData); + + return hr; +} + +static HRESULT ElevatedOnSystemRestorePointBegin( + __in HANDLE hPipe + ) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_SYSTEM_RESTORE_POINT_BEGIN, NULL, 0, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_SYSTEM_RESTORE_POINT_BEGIN message to per-user process."); + +LExit: + return hr; +} + +static HRESULT ElevatedOnSystemRestorePointComplete( + __in HANDLE hPipe, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BYTE* pbSendData = NULL; + SIZE_T cbSendData = 0; + DWORD dwResult = 0; + + hr = BuffWriteNumber(&pbSendData, &cbSendData, hrStatus); + ExitOnFailure(hr, "Failed to write the system restore point status to message buffer."); + + hr = PipeSendMessage(hPipe, BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_SYSTEM_RESTORE_POINT_COMPLETE, pbSendData, cbSendData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send BURN_ELEVATION_MESSAGE_TYPE_APPLY_INITIALIZE_SYSTEM_RESTORE_POINT_COMPLETE message to per-user process."); + +LExit: + ReleaseBuffer(pbSendData); + + return hr; +} diff --git a/src/burn/engine/elevation.h b/src/burn/engine/elevation.h new file mode 100644 index 00000000..9244f36c --- /dev/null +++ b/src/burn/engine/elevation.h @@ -0,0 +1,176 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#ifdef __cplusplus +extern "C" { +#endif + + +// Parent (per-user process) side functions. +HRESULT ElevationElevate( + __in BURN_ENGINE_STATE* pEngineState, + __in_opt HWND hwndParent + ); +HRESULT ElevationApplyInitialize( + __in HANDLE hPipe, + __in BURN_USER_EXPERIENCE* pBA, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_ACTION action, + __in BURN_AU_PAUSE_ACTION auAction, + __in BOOL fTakeSystemRestorePoint + ); +HRESULT ElevationApplyUninitialize( + __in HANDLE hPipe + ); +HRESULT ElevationSessionBegin( + __in HANDLE hPipe, + __in_z LPCWSTR wzEngineWorkingPath, + __in_z LPCWSTR wzResumeCommandLine, + __in BOOL fDisableResume, + __in BURN_VARIABLES* pVariables, + __in DWORD dwRegistrationOperations, + __in BURN_DEPENDENCY_REGISTRATION_ACTION dependencyRegistrationAction, + __in DWORD64 qwEstimatedSize + ); +HRESULT ElevationSessionResume( + __in HANDLE hPipe, + __in_z LPCWSTR wzResumeCommandLine, + __in BOOL fDisableResume, + __in BURN_VARIABLES* pVariables + ); +HRESULT ElevationSessionEnd( + __in HANDLE hPipe, + __in BURN_RESUME_MODE resumeMode, + __in BOOTSTRAPPER_APPLY_RESTART restart, + __in BURN_DEPENDENCY_REGISTRATION_ACTION dependencyRegistrationAction + ); +HRESULT ElevationSaveState( + __in HANDLE hPipe, + __in_bcount(cbBuffer) BYTE* pbBuffer, + __in SIZE_T cbBuffer + ); +HRESULT ElevationCacheCompletePayload( + __in HANDLE hPipe, + __in BURN_PACKAGE* pPackage, + __in BURN_PAYLOAD* pPayload, + __in_z LPCWSTR wzUnverifiedPath, + __in BOOL fMove, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +HRESULT ElevationCacheVerifyPayload( + __in HANDLE hPipe, + __in BURN_PACKAGE* pPackage, + __in BURN_PAYLOAD* pPayload, + __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler, + __in LPPROGRESS_ROUTINE pfnProgress, + __in LPVOID pContext + ); +HRESULT ElevationCacheCleanup( + __in HANDLE hPipe + ); +HRESULT ElevationProcessDependentRegistration( + __in HANDLE hPipe, + __in const BURN_DEPENDENT_REGISTRATION_ACTION* pAction + ); +HRESULT ElevationExecuteExePackage( + __in HANDLE hPipe, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_GENERICMESSAGEHANDLER pfnGenericExecuteProgress, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +HRESULT ElevationExecuteMsiPackage( + __in HANDLE hPipe, + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +HRESULT ElevationExecuteMspPackage( + __in HANDLE hPipe, + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +HRESULT ElevationExecuteMsuPackage( + __in HANDLE hPipe, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BOOL fRollback, + __in BOOL fStopWusaService, + __in PFN_GENERICMESSAGEHANDLER pfnGenericExecuteProgress, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +HRESULT ElevationExecutePackageProviderAction( + __in HANDLE hPipe, + __in BURN_EXECUTE_ACTION* pExecuteAction + ); +HRESULT ElevationExecutePackageDependencyAction( + __in HANDLE hPipe, + __in BURN_EXECUTE_ACTION* pExecuteAction + ); +HRESULT ElevationLaunchElevatedChild( + __in HANDLE hPipe, + __in BURN_PACKAGE* pPackage, + __in LPCWSTR wzPipeName, + __in LPCWSTR wzPipeToken, + __out DWORD* pdwChildPid + ); +HRESULT ElevationCleanPackage( + __in HANDLE hPipe, + __in BURN_PACKAGE* pPackage + ); +HRESULT ElevationLaunchApprovedExe( + __in HANDLE hPipe, + __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe, + __out DWORD* pdwProcessId + ); + +// Child (per-machine process) side functions. +HRESULT ElevationChildPumpMessages( + __in DWORD dwLoggingTlsId, + __in HANDLE hPipe, + __in HANDLE hCachePipe, + __in BURN_APPROVED_EXES* pApprovedExes, + __in BURN_CONTAINERS* pContainers, + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOADS* pPayloads, + __in BURN_VARIABLES* pVariables, + __in BURN_REGISTRATION* pRegistration, + __in BURN_USER_EXPERIENCE* pUserExperience, + __out HANDLE* phLock, + __out BOOL* pfDisabledAutomaticUpdates, + __out DWORD* pdwChildExitCode, + __out BOOL* pfRestart + ); +HRESULT ElevationChildResumeAutomaticUpdates(); + + +HRESULT ElevationMsiBeginTransaction( + __in HANDLE hPipe, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ); +HRESULT ElevationMsiCommitTransaction( + __in HANDLE hPipe, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ); +HRESULT ElevationMsiRollbackTransaction( + __in HANDLE hPipe, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ); + +#ifdef __cplusplus +} +#endif diff --git a/src/burn/engine/embedded.cpp b/src/burn/engine/embedded.cpp new file mode 100644 index 00000000..03898ebd --- /dev/null +++ b/src/burn/engine/embedded.cpp @@ -0,0 +1,197 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// struct + +struct BURN_EMBEDDED_CALLBACK_CONTEXT +{ + PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler; + LPVOID pvContext; +}; + +// internal function declarations + +static HRESULT ProcessEmbeddedMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT OnEmbeddedErrorMessage( + __in PFN_GENERICMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __in_bcount(cbData) BYTE* pbData, + __in SIZE_T cbData, + __out DWORD* pdwResult + ); +static HRESULT OnEmbeddedProgress( + __in PFN_GENERICMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __in_bcount(cbData) BYTE* pbData, + __in SIZE_T cbData, + __out DWORD* pdwResult + ); + +// function definitions + +/******************************************************************* + EmbeddedLaunchChildProcess - + +*******************************************************************/ +extern "C" HRESULT EmbeddedRunBundle( + __in LPCWSTR wzExecutablePath, + __in LPCWSTR wzArguments, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out DWORD* pdwExitCode + ) +{ + HRESULT hr = S_OK; + DWORD dwCurrentProcessId = ::GetCurrentProcessId(); + HANDLE hCreatedPipesEvent = NULL; + LPWSTR sczCommand = NULL; + STARTUPINFOW si = { }; + PROCESS_INFORMATION pi = { }; + BURN_PIPE_RESULT result = { }; + + BURN_PIPE_CONNECTION connection = { }; + PipeConnectionInitialize(&connection); + + BURN_EMBEDDED_CALLBACK_CONTEXT context = { }; + context.pfnGenericMessageHandler = pfnGenericMessageHandler; + context.pvContext = pvContext; + + hr = PipeCreateNameAndSecret(&connection.sczName, &connection.sczSecret); + ExitOnFailure(hr, "Failed to create embedded pipe name and client token."); + + hr = PipeCreatePipes(&connection, FALSE, &hCreatedPipesEvent); + ExitOnFailure(hr, "Failed to create embedded pipe."); + + hr = StrAllocFormattedSecure(&sczCommand, L"%ls -%ls %ls %ls %u", wzArguments, BURN_COMMANDLINE_SWITCH_EMBEDDED, connection.sczName, connection.sczSecret, dwCurrentProcessId); + ExitOnFailure(hr, "Failed to allocate embedded command."); + + if (!::CreateProcessW(wzExecutablePath, sczCommand, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) + { + ExitWithLastError(hr, "Failed to create embedded process at path: %ls", wzExecutablePath); + } + + connection.dwProcessId = ::GetProcessId(pi.hProcess); + connection.hProcess = pi.hProcess; + pi.hProcess = NULL; + + hr = PipeWaitForChildConnect(&connection); + ExitOnFailure(hr, "Failed to wait for embedded process to connect to pipe."); + + hr = PipePumpMessages(connection.hPipe, ProcessEmbeddedMessages, &context, &result); + ExitOnFailure(hr, "Failed to process messages from embedded message."); + + // Get the return code from the embedded process. + hr = ProcWaitForCompletion(connection.hProcess, INFINITE, pdwExitCode); + ExitOnFailure(hr, "Failed to wait for embedded executable: %ls", wzExecutablePath); + +LExit: + ReleaseHandle(pi.hThread); + ReleaseHandle(pi.hProcess); + + StrSecureZeroFreeString(sczCommand); + ReleaseHandle(hCreatedPipesEvent); + PipeConnectionUninitialize(&connection); + + return hr; +} + + +// internal function definitions + +static HRESULT ProcessEmbeddedMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + BURN_EMBEDDED_CALLBACK_CONTEXT* pContext = static_cast(pvContext); + DWORD dwResult = 0; + + // Process the message. + switch (pMsg->dwMessage) + { + case BURN_EMBEDDED_MESSAGE_TYPE_ERROR: + hr = OnEmbeddedErrorMessage(pContext->pfnGenericMessageHandler, pContext->pvContext, static_cast(pMsg->pvData), pMsg->cbData, &dwResult); + ExitOnFailure(hr, "Failed to process embedded error message."); + break; + + case BURN_EMBEDDED_MESSAGE_TYPE_PROGRESS: + hr = OnEmbeddedProgress(pContext->pfnGenericMessageHandler, pContext->pvContext, static_cast(pMsg->pvData), pMsg->cbData, &dwResult); + ExitOnFailure(hr, "Failed to process embedded progress message."); + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Unexpected embedded message sent to child process, msg: %u", pMsg->dwMessage); + } + + *pdwResult = dwResult; + +LExit: + return hr; +} + +static HRESULT OnEmbeddedErrorMessage( + __in PFN_GENERICMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __in_bcount(cbData) BYTE* pbData, + __in SIZE_T cbData, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + GENERIC_EXECUTE_MESSAGE message = { }; + LPWSTR sczMessage = NULL; + + message.type = GENERIC_EXECUTE_MESSAGE_ERROR; + + hr = BuffReadNumber(pbData, cbData, &iData, &message.error.dwErrorCode); + ExitOnFailure(hr, "Failed to read error code from buffer."); + + hr = BuffReadString(pbData, cbData, &iData, &sczMessage); + ExitOnFailure(hr, "Failed to read error message from buffer."); + + message.error.wzMessage = sczMessage; + + hr = BuffReadNumber(pbData, cbData, &iData, &message.dwAllowedResults); + ExitOnFailure(hr, "Failed to read UI hint from buffer."); + + *pdwResult = (DWORD)pfnMessageHandler(&message, pvContext); + +LExit: + ReleaseStr(sczMessage); + + return hr; +} + +static HRESULT OnEmbeddedProgress( + __in PFN_GENERICMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __in_bcount(cbData) BYTE* pbData, + __in SIZE_T cbData, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + SIZE_T iData = 0; + GENERIC_EXECUTE_MESSAGE message = { }; + + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwAllowedResults = MB_OKCANCEL; + + hr = BuffReadNumber(pbData, cbData, &iData, &message.progress.dwPercentage); + ExitOnFailure(hr, "Failed to read progress from buffer."); + + *pdwResult = (DWORD)pfnMessageHandler(&message, pvContext); + +LExit: + return hr; +} diff --git a/src/burn/engine/embedded.h b/src/burn/engine/embedded.h new file mode 100644 index 00000000..08adeae0 --- /dev/null +++ b/src/burn/engine/embedded.h @@ -0,0 +1,27 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum _BURN_EMBEDDED_MESSAGE_TYPE +{ + BURN_EMBEDDED_MESSAGE_TYPE_UNKNOWN, + BURN_EMBEDDED_MESSAGE_TYPE_ERROR, + BURN_EMBEDDED_MESSAGE_TYPE_PROGRESS, +} BURN_EMBEDDED_MESSAGE_TYPE; + + +HRESULT EmbeddedRunBundle( + __in LPCWSTR wzExecutablePath, + __in LPCWSTR wzArguments, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out DWORD* pdwExitCode + ); + +#ifdef __cplusplus +} +#endif diff --git a/src/burn/engine/engine.cpp b/src/burn/engine/engine.cpp new file mode 100644 index 00000000..8f024e98 --- /dev/null +++ b/src/burn/engine/engine.cpp @@ -0,0 +1,992 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// constants + +const DWORD RESTART_RETRIES = 10; + +// internal function declarations + +static HRESULT InitializeEngineState( + __in BURN_ENGINE_STATE* pEngineState, + __in HANDLE hEngineFile + ); +static void UninitializeEngineState( + __in BURN_ENGINE_STATE* pEngineState + ); +static HRESULT RunUntrusted( + __in LPCWSTR wzCommandLine, + __in BURN_ENGINE_STATE* pEngineState + ); +static HRESULT RunNormal( + __in HINSTANCE hInstance, + __in BURN_ENGINE_STATE* pEngineState + ); +static HRESULT RunElevated( + __in HINSTANCE hInstance, + __in LPCWSTR wzCommandLine, + __in BURN_ENGINE_STATE* pEngineState + ); +static HRESULT RunEmbedded( + __in HINSTANCE hInstance, + __in BURN_ENGINE_STATE* pEngineState + ); +static HRESULT RunRunOnce( + __in const BURN_REGISTRATION* pRegistration, + __in int nCmdShow + ); +static HRESULT RunApplication( + __in BURN_ENGINE_STATE* pEngineState, + __out BOOL* pfReloadApp, + __out BOOL* pfSkipCleanup + ); +static HRESULT ProcessMessage( + __in BURN_ENGINE_STATE* pEngineState, + __in const MSG* pmsg + ); +static HRESULT DAPI RedirectLoggingOverPipe( + __in_z LPCSTR szString, + __in_opt LPVOID pvContext + ); +static HRESULT Restart(); + + +// function definitions + +extern "C" BOOL EngineInCleanRoom( + __in_z_opt LPCWSTR wzCommandLine + ) +{ + // Be very careful with the functions you call from here. + // This function will be called before ::SetDefaultDllDirectories() + // has been called so dependencies outside of kernel32.dll are + // very likely to introduce DLL hijacking opportunities. + + static DWORD cchCleanRoomSwitch = lstrlenW(BURN_COMMANDLINE_SWITCH_CLEAN_ROOM); + + // This check is wholly dependent on the clean room command line switch being + // present at the beginning of the command line. Since Burn is the only thing + // that should be setting this command line option, that is in our control. + BOOL fInCleanRoom = (wzCommandLine && + (wzCommandLine[0] == L'-' || wzCommandLine[0] == L'/') && + CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzCommandLine + 1, cchCleanRoomSwitch, BURN_COMMANDLINE_SWITCH_CLEAN_ROOM, cchCleanRoomSwitch) && + wzCommandLine[1 + cchCleanRoomSwitch] == L'=' + ); + + return fInCleanRoom; +} + +extern "C" HRESULT EngineRun( + __in HINSTANCE hInstance, + __in HANDLE hEngineFile, + __in_z_opt LPCWSTR wzCommandLine, + __in int nCmdShow, + __out DWORD* pdwExitCode + ) +{ + HRESULT hr = S_OK; + BOOL fComInitialized = FALSE; + BOOL fLogInitialized = FALSE; + BOOL fCrypInitialized = FALSE; + BOOL fDpiuInitialized = FALSE; + BOOL fRegInitialized = FALSE; + BOOL fWiuInitialized = FALSE; + BOOL fXmlInitialized = FALSE; + SYSTEM_INFO si = { }; + RTL_OSVERSIONINFOEXW ovix = { }; + LPWSTR sczExePath = NULL; + BOOL fRunNormal = FALSE; + BOOL fRestart = FALSE; + + BURN_ENGINE_STATE engineState = { }; + engineState.command.cbSize = sizeof(BOOTSTRAPPER_COMMAND); + + // Always initialize logging first + LogInitialize(::GetModuleHandleW(NULL)); + fLogInitialized = TRUE; + + // Ensure that log contains approriate level of information +#ifdef _DEBUG + LogSetLevel(REPORT_DEBUG, FALSE); +#else + LogSetLevel(REPORT_VERBOSE, FALSE); // FALSE means don't write an additional text line to the log saying the level changed +#endif + + hr = AppParseCommandLine(wzCommandLine, &engineState.argc, &engineState.argv); + ExitOnFailure(hr, "Failed to parse command line."); + + hr = InitializeEngineState(&engineState, hEngineFile); + ExitOnFailure(hr, "Failed to initialize engine state."); + + engineState.command.nCmdShow = nCmdShow; + + // initialize platform layer + PlatformInitialize(); + + // initialize COM + hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); + ExitOnFailure(hr, "Failed to initialize COM."); + fComInitialized = TRUE; + + // Initialize dutil. + hr = CrypInitialize(); + ExitOnFailure(hr, "Failed to initialize Cryputil."); + fCrypInitialized = TRUE; + + DpiuInitialize(); + fDpiuInitialized = TRUE; + + hr = RegInitialize(); + ExitOnFailure(hr, "Failed to initialize Regutil."); + fRegInitialized = TRUE; + + hr = WiuInitialize(); + ExitOnFailure(hr, "Failed to initialize Wiutil."); + fWiuInitialized = TRUE; + + hr = XmlInitialize(); + ExitOnFailure(hr, "Failed to initialize XML util."); + fXmlInitialized = TRUE; + + hr = OsRtlGetVersion(&ovix); + ExitOnFailure(hr, "Failed to get OS info."); + +#if defined(_M_ARM64) + LPCSTR szBurnPlatform = "ARM64"; +#elif defined(_M_AMD64) + LPCSTR szBurnPlatform = "x64"; +#else + LPCSTR szBurnPlatform = "x86"; +#endif + + LPCSTR szMachinePlatform = "unknown architecture"; + ::GetNativeSystemInfo(&si); + switch (si.wProcessorArchitecture) + { + case PROCESSOR_ARCHITECTURE_AMD64: + szMachinePlatform = "x64"; + break; + case PROCESSOR_ARCHITECTURE_ARM: + szMachinePlatform = "ARM"; + break; + case PROCESSOR_ARCHITECTURE_ARM64: + szMachinePlatform = "ARM64"; + break; + case PROCESSOR_ARCHITECTURE_INTEL: + szMachinePlatform = "x86"; + break; + } + + PathForCurrentProcess(&sczExePath, NULL); // Ignore failure. + LogId(REPORT_STANDARD, MSG_BURN_INFO, szVerMajorMinorBuild, ovix.dwMajorVersion, ovix.dwMinorVersion, ovix.dwBuildNumber, ovix.wServicePackMajor, sczExePath, szBurnPlatform, szMachinePlatform); + ReleaseNullStr(sczExePath); + + // initialize core + hr = CoreInitialize(&engineState); + ExitOnFailure(hr, "Failed to initialize core."); + + // Select run mode. + switch (engineState.mode) + { + case BURN_MODE_UNTRUSTED: + hr = RunUntrusted(wzCommandLine, &engineState); + ExitOnFailure(hr, "Failed to run untrusted mode."); + break; + + case BURN_MODE_NORMAL: + fRunNormal = TRUE; + + hr = RunNormal(hInstance, &engineState); + ExitOnFailure(hr, "Failed to run per-user mode."); + break; + + case BURN_MODE_ELEVATED: + hr = RunElevated(hInstance, wzCommandLine, &engineState); + ExitOnFailure(hr, "Failed to run per-machine mode."); + break; + + case BURN_MODE_EMBEDDED: + fRunNormal = TRUE; + + hr = RunEmbedded(hInstance, &engineState); + ExitOnFailure(hr, "Failed to run embedded mode."); + break; + + case BURN_MODE_RUNONCE: + hr = RunRunOnce(&engineState.registration, nCmdShow); + ExitOnFailure(hr, "Failed to run RunOnce mode."); + break; + + default: + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Invalid run mode."); + } + + // set exit code and remember if we are supposed to restart. + *pdwExitCode = engineState.userExperience.dwExitCode; + fRestart = engineState.fRestart; + +LExit: + ReleaseStr(sczExePath); + + // If anything went wrong but the log was never open, try to open a "failure" log + // and that will dump anything captured in the log memory buffer to the log. + if (FAILED(hr) && BURN_LOGGING_STATE_CLOSED == engineState.log.state) + { + LoggingOpenFailed(); + } + + UserExperienceRemove(&engineState.userExperience); + + CacheRemoveWorkingFolder(engineState.registration.sczId); + CacheUninitialize(); + + // If this is a related bundle (but not an update) suppress restart and return the standard restart error code. + if (fRestart && BOOTSTRAPPER_RELATION_NONE != engineState.command.relationType && BOOTSTRAPPER_RELATION_UPDATE != engineState.command.relationType) + { + LogId(REPORT_STANDARD, MSG_RESTART_ABORTED, LoggingRelationTypeToString(engineState.command.relationType)); + + fRestart = FALSE; + hr = HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED); + } + + UninitializeEngineState(&engineState); + + if (fXmlInitialized) + { + XmlUninitialize(); + } + + if (fWiuInitialized) + { + WiuUninitialize(); + } + + if (fRegInitialized) + { + RegUninitialize(); + } + + if (fDpiuInitialized) + { + DpiuUninitialize(); + } + + if (fCrypInitialized) + { + CrypUninitialize(); + } + + if (fComInitialized) + { + ::CoUninitialize(); + } + + if (fRunNormal) + { + LogId(REPORT_STANDARD, MSG_EXITING, FAILED(hr) ? (int)hr : *pdwExitCode, LoggingBoolToString(fRestart)); + + if (fRestart) + { + LogId(REPORT_STANDARD, MSG_RESTARTING); + } + } + + if (fLogInitialized) + { + LogClose(FALSE); + } + + if (fRestart) + { + Restart(); + } + + if (fLogInitialized) + { + LogUninitialize(FALSE); + } + + return hr; +} + + +// internal function definitions + +static HRESULT InitializeEngineState( + __in BURN_ENGINE_STATE* pEngineState, + __in HANDLE hEngineFile + ) +{ + HRESULT hr = S_OK; + LPCWSTR wzParam = NULL; + HANDLE hSectionFile = hEngineFile; + HANDLE hSourceEngineFile = INVALID_HANDLE_VALUE; + DWORD64 qw = 0; + + pEngineState->automaticUpdates = BURN_AU_PAUSE_ACTION_IFELEVATED; + pEngineState->dwElevatedLoggingTlsId = TLS_OUT_OF_INDEXES; + ::InitializeCriticalSection(&pEngineState->userExperience.csEngineActive); + PipeConnectionInitialize(&pEngineState->companionConnection); + PipeConnectionInitialize(&pEngineState->embeddedConnection); + + for (int i = 0; i < pEngineState->argc; ++i) + { + if (pEngineState->argv[i][0] == L'-') + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &pEngineState->argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED), BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED, lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED))) + { + wzParam = &pEngineState->argv[i][2 + lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED)]; + if (L'=' != wzParam[-1] || L'\0' == wzParam[0]) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Missing required parameter for switch: %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED); + } + + hr = StrStringToUInt64(wzParam, 0, &qw); + ExitOnFailure(hr, "Failed to parse file handle: '%ls'", (wzParam)); + + hSourceEngineFile = (HANDLE)qw; + } + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &pEngineState->argv[i][1], lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF), BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF, lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF))) + { + wzParam = &pEngineState->argv[i][2 + lstrlenW(BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF)]; + if (L'=' != wzParam[-1] || L'\0' == wzParam[0]) + { + ExitOnRootFailure(hr = E_INVALIDARG, "Missing required parameter for switch: %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF); + } + + hr = StrStringToUInt64(wzParam, 0, &qw); + ExitOnFailure(hr, "Failed to parse file handle: '%ls'", (wzParam)); + + hSectionFile = (HANDLE)qw; + } + } + } + + hr = SectionInitialize(&pEngineState->section, hSectionFile, hSourceEngineFile); + ExitOnFailure(hr, "Failed to initialize engine section."); + +LExit: + return hr; +} + +static void UninitializeEngineState( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + if (pEngineState->argv) + { + AppFreeCommandLineArgs(pEngineState->argv); + } + + ReleaseStr(pEngineState->sczIgnoreDependencies); + + PipeConnectionUninitialize(&pEngineState->embeddedConnection); + PipeConnectionUninitialize(&pEngineState->companionConnection); + ReleaseStr(pEngineState->sczBundleEngineWorkingPath) + + ReleaseHandle(pEngineState->hMessageWindowThread); + + BurnExtensionUninitialize(&pEngineState->extensions); + + ::DeleteCriticalSection(&pEngineState->userExperience.csEngineActive); + UserExperienceUninitialize(&pEngineState->userExperience); + + ApprovedExesUninitialize(&pEngineState->approvedExes); + UpdateUninitialize(&pEngineState->update); + VariablesUninitialize(&pEngineState->variables); + SearchesUninitialize(&pEngineState->searches); + RegistrationUninitialize(&pEngineState->registration); + PayloadsUninitialize(&pEngineState->payloads); + PackagesUninitialize(&pEngineState->packages); + SectionUninitialize(&pEngineState->section); + ContainersUninitialize(&pEngineState->containers); + + ReleaseStr(pEngineState->command.wzBootstrapperApplicationDataPath); + ReleaseStr(pEngineState->command.wzBootstrapperWorkingFolder); + ReleaseStr(pEngineState->command.wzLayoutDirectory); + ReleaseStr(pEngineState->command.wzCommandLine); + + ReleaseStr(pEngineState->log.sczExtension); + ReleaseStr(pEngineState->log.sczPrefix); + ReleaseStr(pEngineState->log.sczPath); + ReleaseStr(pEngineState->log.sczPathVariable); + + if (TLS_OUT_OF_INDEXES != pEngineState->dwElevatedLoggingTlsId) + { + ::TlsFree(pEngineState->dwElevatedLoggingTlsId); + } + + // clear struct + memset(pEngineState, 0, sizeof(BURN_ENGINE_STATE)); +} + +static HRESULT RunUntrusted( + __in LPCWSTR wzCommandLine, + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCurrentProcessPath = NULL; + LPWSTR wzCleanRoomBundlePath = NULL; + LPWSTR sczCachedCleanRoomBundlePath = NULL; + LPWSTR sczParameters = NULL; + LPWSTR sczFullCommandLine = NULL; + STARTUPINFOW si = { }; + PROCESS_INFORMATION pi = { }; + HANDLE hFileAttached = NULL; + HANDLE hFileSelf = NULL; + HANDLE hProcess = NULL; + + hr = PathForCurrentProcess(&sczCurrentProcessPath, NULL); + ExitOnFailure(hr, "Failed to get path for current process."); + + BOOL fRunningFromCache = CacheBundleRunningFromCache(); + + // If we're running from the package cache, we're in a secure + // folder (DLLs cannot be inserted here for hijacking purposes) + // so just launch the current process's path as the clean room + // process. Technically speaking, we'd be able to skip creating + // a clean room process at all (since we're already running from + // a secure folder) but it makes the code that only wants to run + // in clean room more complicated if we don't launch an explicit + // clean room process. + if (fRunningFromCache) + { + wzCleanRoomBundlePath = sczCurrentProcessPath; + } + else + { + hr = CacheBundleToCleanRoom(&pEngineState->section, &sczCachedCleanRoomBundlePath); + ExitOnFailure(hr, "Failed to cache to clean room."); + + wzCleanRoomBundlePath = sczCachedCleanRoomBundlePath; + } + + // The clean room switch must always be at the front of the command line so + // the EngineInCleanRoom function will operate correctly. + hr = StrAllocFormatted(&sczParameters, L"-%ls=\"%ls\"", BURN_COMMANDLINE_SWITCH_CLEAN_ROOM, sczCurrentProcessPath); + ExitOnFailure(hr, "Failed to allocate parameters for unelevated process."); + + // Send a file handle for the child Burn process to access the attached container. + hr = CoreAppendFileHandleAttachedToCommandLine(pEngineState->section.hEngineFile, &hFileAttached, &sczParameters); + ExitOnFailure(hr, "Failed to append %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_ATTACHED); + + // Grab a file handle for the child Burn process. + hr = CoreAppendFileHandleSelfToCommandLine(wzCleanRoomBundlePath, &hFileSelf, &sczParameters, NULL); + ExitOnFailure(hr, "Failed to append %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF); + + hr = StrAllocFormattedSecure(&sczParameters, L"%ls %ls", sczParameters, wzCommandLine); + ExitOnFailure(hr, "Failed to append original command line."); + +#ifdef ENABLE_UNELEVATE + // TODO: Pass file handle to unelevated process if this ever gets reenabled. + if (!pEngineState->fDisableUnelevate) + { + // Try to launch unelevated and if that fails for any reason, we'll launch our process normally (even though that may make it elevated). + hr = ProcExecuteAsInteractiveUser(wzCleanRoomBundlePath, sczParameters, &hProcess); + } +#endif + + if (!hProcess) + { + hr = StrAllocFormattedSecure(&sczFullCommandLine, L"\"%ls\" %ls", wzCleanRoomBundlePath, sczParameters); + ExitOnFailure(hr, "Failed to allocate full command-line."); + + si.cb = sizeof(si); + si.wShowWindow = static_cast(pEngineState->command.nCmdShow); + if (!::CreateProcessW(wzCleanRoomBundlePath, sczFullCommandLine, NULL, NULL, TRUE, 0, 0, NULL, &si, &pi)) + { + ExitWithLastError(hr, "Failed to launch clean room process: %ls", sczFullCommandLine); + } + + hProcess = pi.hProcess; + pi.hProcess = NULL; + } + + hr = ProcWaitForCompletion(hProcess, INFINITE, &pEngineState->userExperience.dwExitCode); + ExitOnFailure(hr, "Failed to wait for clean room process: %ls", wzCleanRoomBundlePath); + +LExit: + ReleaseHandle(pi.hThread); + ReleaseFileHandle(hFileSelf); + ReleaseFileHandle(hFileAttached); + ReleaseHandle(hProcess); + StrSecureZeroFreeString(sczFullCommandLine); + StrSecureZeroFreeString(sczParameters); + ReleaseStr(sczCachedCleanRoomBundlePath); + ReleaseStr(sczCurrentProcessPath); + + return hr; +} + +static HRESULT RunNormal( + __in HINSTANCE hInstance, + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + LPWSTR sczOriginalSource = NULL; + LPWSTR sczCopiedOriginalSource = NULL; + BOOL fContinueExecution = TRUE; + BOOL fReloadApp = FALSE; + BOOL fSkipCleanup = FALSE; + BURN_EXTENSION_ENGINE_CONTEXT extensionEngineContext = { }; + + // Initialize logging. + hr = LoggingOpen(&pEngineState->log, &pEngineState->variables, pEngineState->command.display, pEngineState->registration.sczDisplayName); + ExitOnFailure(hr, "Failed to open log."); + + // Ensure we're on a supported operating system. + hr = ConditionGlobalCheck(&pEngineState->variables, &pEngineState->condition, pEngineState->command.display, pEngineState->registration.sczDisplayName, &pEngineState->userExperience.dwExitCode, &fContinueExecution); + ExitOnFailure(hr, "Failed to check global conditions"); + + if (!fContinueExecution) + { + LogId(REPORT_STANDARD, MSG_FAILED_CONDITION_CHECK); + + // If the block told us to abort, abort! + ExitFunction1(hr = S_OK); + } + + if (pEngineState->userExperience.fSplashScreen && BOOTSTRAPPER_DISPLAY_NONE < pEngineState->command.display) + { + SplashScreenCreate(hInstance, NULL, &pEngineState->command.hwndSplashScreen); + } + + // Create a top-level window to handle system messages. + hr = UiCreateMessageWindow(hInstance, pEngineState); + ExitOnFailure(hr, "Failed to create the message window."); + + // Query registration state. + hr = CoreQueryRegistration(pEngineState); + ExitOnFailure(hr, "Failed to query registration."); + + // Best effort to set the source of attached containers to BURN_BUNDLE_ORIGINAL_SOURCE. + hr = VariableGetString(&pEngineState->variables, BURN_BUNDLE_ORIGINAL_SOURCE, &sczOriginalSource); + if (SUCCEEDED(hr)) + { + for (DWORD i = 0; i < pEngineState->containers.cContainers; ++i) + { + BURN_CONTAINER* pContainer = pEngineState->containers.rgContainers + i; + if (pContainer->fAttached) + { + hr = StrAllocString(&sczCopiedOriginalSource, sczOriginalSource, 0); + if (SUCCEEDED(hr)) + { + ReleaseNullStr(pContainer->sczSourcePath); + pContainer->sczSourcePath = sczCopiedOriginalSource; + sczCopiedOriginalSource = NULL; + } + } + } + } + hr = S_OK; + + // Set some built-in variables before loading the BA. + hr = PlanSetVariables(pEngineState->command.action, &pEngineState->variables); + ExitOnFailure(hr, "Failed to set action variables."); + + hr = RegistrationSetVariables(&pEngineState->registration, &pEngineState->variables); + ExitOnFailure(hr, "Failed to set registration variables."); + + // If a layout directory was specified on the command-line, set it as a well-known variable. + if (pEngineState->command.wzLayoutDirectory && *pEngineState->command.wzLayoutDirectory) + { + hr = VariableSetString(&pEngineState->variables, BURN_BUNDLE_LAYOUT_DIRECTORY, pEngineState->command.wzLayoutDirectory, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set layout directory variable to value provided from command-line."); + } + + // Setup the extension engine. + extensionEngineContext.pEngineState = pEngineState; + + // Load the extensions. + hr = BurnExtensionLoad(&pEngineState->extensions, &extensionEngineContext); + ExitOnFailure(hr, "Failed to load BundleExtensions."); + + do + { + fReloadApp = FALSE; + pEngineState->fQuit = FALSE; + + hr = RunApplication(pEngineState, &fReloadApp, &fSkipCleanup); + ExitOnFailure(hr, "Failed while running "); + } while (fReloadApp); + +LExit: + if (!fSkipCleanup) + { + CoreCleanup(pEngineState); + } + + BurnExtensionUnload(&pEngineState->extensions); + + // If the message window is still around, close it. + UiCloseMessageWindow(pEngineState); + + VariablesDump(&pEngineState->variables); + + // end per-machine process if running + if (INVALID_HANDLE_VALUE != pEngineState->companionConnection.hPipe) + { + PipeTerminateChildProcess(&pEngineState->companionConnection, pEngineState->userExperience.dwExitCode, FALSE); + } + + // If the splash screen is still around, close it. + if (::IsWindow(pEngineState->command.hwndSplashScreen)) + { + ::PostMessageW(pEngineState->command.hwndSplashScreen, WM_CLOSE, 0, 0); + } + + ReleaseStr(sczOriginalSource); + ReleaseStr(sczCopiedOriginalSource); + + return hr; +} + +static HRESULT RunElevated( + __in HINSTANCE hInstance, + __in LPCWSTR /*wzCommandLine*/, + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + HANDLE hLock = NULL; + BOOL fDisabledAutomaticUpdates = FALSE; + + // connect to per-user process + hr = PipeChildConnect(&pEngineState->companionConnection, TRUE); + ExitOnFailure(hr, "Failed to connect to unelevated process."); + + // Set up the thread local storage to store the correct pipe to communicate logging then + // override logging to write over the pipe. + pEngineState->dwElevatedLoggingTlsId = ::TlsAlloc(); + if (TLS_OUT_OF_INDEXES == pEngineState->dwElevatedLoggingTlsId) + { + ExitWithLastError(hr, "Failed to allocate thread local storage for logging."); + } + + if (!::TlsSetValue(pEngineState->dwElevatedLoggingTlsId, pEngineState->companionConnection.hPipe)) + { + ExitWithLastError(hr, "Failed to set elevated pipe into thread local storage for logging."); + } + + LogRedirect(RedirectLoggingOverPipe, pEngineState); + + // Create a top-level window to prevent shutting down the elevated process. + hr = UiCreateMessageWindow(hInstance, pEngineState); + ExitOnFailure(hr, "Failed to create the message window."); + + SrpInitialize(TRUE); + + // Pump messages from parent process. + hr = ElevationChildPumpMessages(pEngineState->dwElevatedLoggingTlsId, pEngineState->companionConnection.hPipe, pEngineState->companionConnection.hCachePipe, &pEngineState->approvedExes, &pEngineState->containers, &pEngineState->packages, &pEngineState->payloads, &pEngineState->variables, &pEngineState->registration, &pEngineState->userExperience, &hLock, &fDisabledAutomaticUpdates, &pEngineState->userExperience.dwExitCode, &pEngineState->fRestart); + LogRedirect(NULL, NULL); // reset logging so the next failure gets written to "log buffer" for the failure log. + ExitOnFailure(hr, "Failed to pump messages from parent process."); + +LExit: + LogRedirect(NULL, NULL); // we're done talking to the child so always reset logging now. + + // If the message window is still around, close it. + UiCloseMessageWindow(pEngineState); + + if (fDisabledAutomaticUpdates) + { + ElevationChildResumeAutomaticUpdates(); + } + + if (hLock) + { + ::ReleaseMutex(hLock); + ::CloseHandle(hLock); + } + + return hr; +} + +static HRESULT RunEmbedded( + __in HINSTANCE hInstance, + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + + // Disable system restore since the parent bundle may have done it. + pEngineState->fDisableSystemRestore = TRUE; + + // Connect to parent process. + hr = PipeChildConnect(&pEngineState->embeddedConnection, FALSE); + ExitOnFailure(hr, "Failed to connect to parent of embedded process."); + + // Do not register the bundle to automatically restart if embedded. + if (BOOTSTRAPPER_DISPLAY_EMBEDDED == pEngineState->command.display) + { + pEngineState->registration.fDisableResume = TRUE; + } + + // Now run the application like normal. + hr = RunNormal(hInstance, pEngineState); + ExitOnFailure(hr, "Failed to run bootstrapper application embedded."); + +LExit: + return hr; +} + +static HRESULT RunRunOnce( + __in const BURN_REGISTRATION* pRegistration, + __in int nCmdShow + ) +{ + HRESULT hr = S_OK; + LPWSTR sczNewCommandLine = NULL; + LPWSTR sczBurnPath = NULL; + HANDLE hProcess = NULL; + + hr = RegistrationGetResumeCommandLine(pRegistration, &sczNewCommandLine); + ExitOnFailure(hr, "Unable to get resume command line from the registry"); + + // and re-launch + hr = PathForCurrentProcess(&sczBurnPath, NULL); + ExitOnFailure(hr, "Failed to get current process path."); + + hr = ProcExec(sczBurnPath, 0 < sczNewCommandLine ? sczNewCommandLine : L"", nCmdShow, &hProcess); + ExitOnFailure(hr, "Failed to re-launch bundle process after RunOnce: %ls", sczBurnPath); + +LExit: + ReleaseHandle(hProcess); + ReleaseStr(sczNewCommandLine); + ReleaseStr(sczBurnPath); + + return hr; +} + +static HRESULT RunApplication( + __in BURN_ENGINE_STATE* pEngineState, + __out BOOL* pfReloadApp, + __out BOOL* pfSkipCleanup + ) +{ + HRESULT hr = S_OK; + BOOTSTRAPPER_ENGINE_CONTEXT engineContext = { }; + BOOL fStartupCalled = FALSE; + BOOL fRet = FALSE; + MSG msg = { }; + BOOTSTRAPPER_SHUTDOWN_ACTION shutdownAction = BOOTSTRAPPER_SHUTDOWN_ACTION_NONE; + + ::PeekMessageW(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); + + // Setup the bootstrapper engine. + engineContext.dwThreadId = ::GetCurrentThreadId(); + engineContext.pEngineState = pEngineState; + + // Load the bootstrapper application. + hr = UserExperienceLoad(&pEngineState->userExperience, &engineContext, &pEngineState->command); + ExitOnFailure(hr, "Failed to load BA."); + + fStartupCalled = TRUE; + hr = UserExperienceOnStartup(&pEngineState->userExperience); + ExitOnFailure(hr, "Failed to start bootstrapper application."); + + // Enter the message pump. + while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) + { + if (-1 == fRet) + { + hr = E_UNEXPECTED; + ExitOnRootFailure(hr, "Unexpected return value from message pump."); + } + else + { + // When the BA makes a request from its own thread, it's common for the PostThreadMessage in externalengine.cpp + // to block until this thread waits on something. It's also common for Detect and Plan to never wait on something. + // In the extreme case, the engine could be elevating in Apply before the Detect call returned to the BA. + // This helps to avoid that situation, which could be blocking a UI thread. + ::Sleep(0); + + ProcessMessage(pEngineState, &msg); + } + } + + // Get exit code. + pEngineState->userExperience.dwExitCode = (DWORD)msg.wParam; + +LExit: + if (fStartupCalled) + { + UserExperienceOnShutdown(&pEngineState->userExperience, &shutdownAction); + if (BOOTSTRAPPER_SHUTDOWN_ACTION_RESTART == shutdownAction) + { + LogId(REPORT_STANDARD, MSG_BA_REQUESTED_RESTART, LoggingBoolToString(pEngineState->fRestart)); + pEngineState->fRestart = TRUE; + } + else if (BOOTSTRAPPER_SHUTDOWN_ACTION_RELOAD_BOOTSTRAPPER == shutdownAction) + { + LogId(REPORT_STANDARD, MSG_BA_REQUESTED_RELOAD); + *pfReloadApp = TRUE; + } + else if (BOOTSTRAPPER_SHUTDOWN_ACTION_SKIP_CLEANUP == shutdownAction) + { + LogId(REPORT_STANDARD, MSG_BA_REQUESTED_SKIP_CLEANUP); + *pfSkipCleanup = TRUE; + } + } + + // Unload BA. + UserExperienceUnload(&pEngineState->userExperience); + + return hr; +} + +static HRESULT ProcessMessage( + __in BURN_ENGINE_STATE* pEngineState, + __in const MSG* pmsg + ) +{ + HRESULT hr = S_OK; + + UserExperienceActivateEngine(&pEngineState->userExperience); + + if (pEngineState->fQuit) + { + LogId(REPORT_WARNING, MSG_IGNORE_OPERATION_AFTER_QUIT, LoggingBurnMessageToString(pmsg->message)); + ExitFunction1(hr = E_INVALIDSTATE); + } + + switch (pmsg->message) + { + case WM_BURN_DETECT: + hr = CoreDetect(pEngineState, reinterpret_cast(pmsg->lParam)); + break; + + case WM_BURN_PLAN: + hr = CorePlan(pEngineState, static_cast(pmsg->lParam)); + break; + + case WM_BURN_ELEVATE: + hr = CoreElevate(pEngineState, reinterpret_cast(pmsg->lParam)); + break; + + case WM_BURN_APPLY: + hr = CoreApply(pEngineState, reinterpret_cast(pmsg->lParam)); + break; + + case WM_BURN_LAUNCH_APPROVED_EXE: + hr = CoreLaunchApprovedExe(pEngineState, reinterpret_cast(pmsg->lParam)); + break; + + case WM_BURN_QUIT: + hr = CoreQuit(pEngineState, static_cast(pmsg->wParam)); + break; + } + +LExit: + UserExperienceDeactivateEngine(&pEngineState->userExperience); + + return hr; +} + +static HRESULT DAPI RedirectLoggingOverPipe( + __in_z LPCSTR szString, + __in_opt LPVOID pvContext + ) +{ + static BOOL s_fCurrentlyLoggingToPipe = FALSE; + + HRESULT hr = S_OK; + BURN_ENGINE_STATE* pEngineState = static_cast(pvContext); + BOOL fStartedLogging = FALSE; + HANDLE hPipe = INVALID_HANDLE_VALUE; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = 0; + + // Prevent this function from being called recursively. + if (s_fCurrentlyLoggingToPipe) + { + ExitFunction(); + } + + s_fCurrentlyLoggingToPipe = TRUE; + fStartedLogging = TRUE; + + // Make sure the current thread set the pipe in TLS. + hPipe = ::TlsGetValue(pEngineState->dwElevatedLoggingTlsId); + if (!hPipe || INVALID_HANDLE_VALUE == hPipe) + { + hr = HRESULT_FROM_WIN32(ERROR_PIPE_NOT_CONNECTED); + ExitFunction(); + } + + // Do not log or use ExitOnFailure() macro here because they will be discarded + // by the recursive block at the top of this function. + hr = BuffWriteStringAnsi(&pbData, &cbData, szString); + if (SUCCEEDED(hr)) + { + hr = PipeSendMessage(hPipe, static_cast(BURN_PIPE_MESSAGE_TYPE_LOG), pbData, cbData, NULL, NULL, &dwResult); + if (SUCCEEDED(hr)) + { + hr = (HRESULT)dwResult; + } + } + +LExit: + ReleaseBuffer(pbData); + + // We started logging so remember to say we are no longer logging. + if (fStartedLogging) + { + s_fCurrentlyLoggingToPipe = FALSE; + } + + return hr; +} + +static HRESULT Restart() +{ + HRESULT hr = S_OK; + HANDLE hProcessToken = NULL; + TOKEN_PRIVILEGES priv = { }; + DWORD dwRetries = 0; + + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken)) + { + ExitWithLastError(hr, "Failed to get process token."); + } + + priv.PrivilegeCount = 1; + priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (!::LookupPrivilegeValueW(NULL, L"SeShutdownPrivilege", &priv.Privileges[0].Luid)) + { + ExitWithLastError(hr, "Failed to get shutdown privilege LUID."); + } + + if (!::AdjustTokenPrivileges(hProcessToken, FALSE, &priv, sizeof(TOKEN_PRIVILEGES), NULL, 0)) + { + ExitWithLastError(hr, "Failed to adjust token to add shutdown privileges."); + } + + do + { + hr = S_OK; + + // Wait a second to let the companion process (assuming we did an elevated install) to get to the + // point where it too is thinking about restarting the computer. Only one will schedule the restart + // but both will have their log files closed and otherwise be ready to exit. + // + // On retry, we'll also wait a second to let the OS try to get to a place where the restart can + // be initiated. + ::Sleep(1000); + + if (!vpfnInitiateSystemShutdownExW(NULL, NULL, 0, FALSE, TRUE, SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_INSTALLATION | SHTDN_REASON_FLAG_PLANNED)) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + } + } while (dwRetries++ < RESTART_RETRIES && (HRESULT_FROM_WIN32(ERROR_MACHINE_LOCKED) == hr || HRESULT_FROM_WIN32(ERROR_NOT_READY) == hr)); + ExitOnRootFailure(hr, "Failed to schedule restart."); + +LExit: + ReleaseHandle(hProcessToken); + return hr; +} diff --git a/src/burn/engine/engine.mc b/src/burn/engine/engine.mc new file mode 100644 index 00000000..25d5b4e4 --- /dev/null +++ b/src/burn/engine/engine.mc @@ -0,0 +1,1090 @@ +; // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +MessageIdTypedef=DWORD + +LanguageNames=(English=0x409:MSG00409) + + +; // message definitions + +; // MessageId=# +; // Severity=Success +; // SymbolicName=MSG_SUCCESS +; // Language=English +; // Success %1. +; // . +; +; // MessageId=# +; // Severity=Warning +; // SymbolicName=MSG_WARNING +; // Language=English +; // Warning %1. +; // . +; +; // MessageId=# +; // Severity=Error +; // SymbolicName=MSG_ERROR +; // Language=English +; // Error %1. +; // . + +MessageId=1 +Severity=Success +SymbolicName=MSG_BURN_INFO +Language=English +Burn %7!hs! v%1!hs!, Windows v%2!d!.%3!d! %8!hs! (Build %4!d!: Service Pack %5!d!), path: %6!ls! +. + +MessageId=2 +Severity=Warning +SymbolicName=MSG_BURN_UNKNOWN_PRIVATE_SWITCH +Language=English +Unknown burn internal command-line switch encountered: '%1!ls!'. +. + +MessageId=3 +Severity=Success +SymbolicName=MSG_BURN_RUN_BY_RELATED_BUNDLE +Language=English +This bundle is being run by a related bundle as type '%1!hs!'. +. + +MessageId=4 +Severity=Success +SymbolicName=MSG_BA_REQUESTED_RESTART +Language=English +Bootstrapper application requested restart at shutdown. Planned to restart already: %1!hs!. +. + +MessageId=5 +Severity=Warning +SymbolicName=MSG_RESTARTING +Language=English +Restarting computer... +======================================= +. + +MessageId=6 +Severity=Success +SymbolicName=MSG_BA_REQUESTED_RELOAD +Language=English +Bootstrapper application requested to be reloaded. +. + +MessageId=7 +Severity=Success +SymbolicName=MSG_EXITING +Language=English +Exit code: 0x%1!x!, restarting: %2!hs! +. + +MessageId=8 +Severity=Warning +SymbolicName=MSG_RESTART_ABORTED +Language=English +Preventing requested restart because bundle is related: '%1!hs!'. Returning restart requested to parent bundle. +. + +MessageId=9 +Severity=Success +SymbolicName=MSG_BURN_COMMAND_LINE +Language=English +Command Line: '%1!ls!' +. + +MessageId=10 +Severity=Success +SymbolicName=MSG_LAUNCH_ELEVATED_ENGINE_STARTING +Language=English +Launching elevated engine process. +. + +MessageId=11 +Severity=Success +SymbolicName=MSG_LAUNCH_ELEVATED_ENGINE_SUCCESS +Language=English +Launched elevated engine process. +. + +MessageId=12 +Severity=Success +SymbolicName=MSG_CONNECT_TO_ELEVATED_ENGINE_SUCCESS +Language=English +Connected to elevated engine. +. + +MessageId=13 +Severity=Warning +SymbolicName=MSG_MANIFEST_INVALID_VERSION +Language=English +The manifest contains an invalid version string: '%1!ls!' +. + +MessageId=14 +Severity=Success +SymbolicName=MSG_BA_REQUESTED_SKIP_CLEANUP +Language=English +Bootstrapper application opted out of any engine behavior to automatically uninstall the bundle during shutdown. +. + +MessageId=51 +Severity=Error +SymbolicName=MSG_FAILED_PARSE_CONDITION +Language=English +Error %1!hs!. Failed to parse condition %2!ls!. Unexpected symbol at position %3!hs! +. + +MessageId=52 +Severity=Success +SymbolicName=MSG_CONDITION_RESULT +Language=English +Condition '%1!ls!' evaluates to %2!hs!. +. + +MessageId=53 +Severity=Error +SymbolicName=MSG_FAILED_CONDITION_CHECK +Language=English +Bundle global condition check didn't succeed - aborting without loading application. +. + +MessageId=54 +Severity=Error +SymbolicName=MSG_RESOLVE_SOURCE_FAILED +Language=English +Failed to resolve source for payload: %2!ls!, package: %3!ls!, container: %4!ls!, error: %1!ls!. +. + +MessageId=55 +Severity=Warning +SymbolicName=MSG_CANNOT_LOAD_STATE_FILE +Language=English +Could not load or read state file: %2!ls!, error: 0x%1!x!. +. + +MessageId=56 +Severity=Error +SymbolicName=MSG_USER_CANCELED +Language=English +Application canceled operation: %2!ls!, error: %1!ls! +. + +MessageId=57 +Severity=Warning +SymbolicName=MSG_CONDITION_INVALID_VERSION +Language=English +Condition '%1!ls!' contains invalid version string '%2!ls!'. +. + +MessageId=58 +Severity=Warning +SymbolicName=MSG_IGNORE_OPERATION_AFTER_QUIT +Language=English +Bootstrapper application already requested to quit, ignoring request: '%1!hs!'. +. + +MessageId=100 +Severity=Success +SymbolicName=MSG_DETECT_BEGIN +Language=English +Detect begin, %1!u! packages +. + +MessageId=101 +Severity=Success +SymbolicName=MSG_DETECTED_PACKAGE +Language=English +Detected package: %1!ls!, state: %2!hs!, cached: %3!hs!, install registration state: %4!hs!, cache registration state: %5!hs! +. + +MessageId=102 +Severity=Success +SymbolicName=MSG_DETECTED_RELATED_BUNDLE +Language=English +Detected related bundle: %1!ls!, type: %2!hs!, scope: %3!hs!, version: %4!ls!, operation: %5!hs!, cached: %6!hs! +. + +MessageId=103 +Severity=Success +SymbolicName=MSG_DETECTED_RELATED_PACKAGE +Language=English +Detected related package: %1!ls!, scope: %2!hs!, version: %3!ls!, language: %4!u! operation: %5!hs! +. + +MessageId=104 +Severity=Success +SymbolicName=MSG_DETECTED_MSI_FEATURE +Language=English +Detected package: %1!ls!, feature: %2!ls!, state: %3!hs! +. + +MessageId=105 +Severity=Success +SymbolicName=MSG_DETECTED_MSP_TARGET +Language=English +Detected package: %1!ls! target: %2!ls!, state: %3!hs! +. + +MessageId=106 +Severity=Success +SymbolicName=MSG_DETECT_CALCULATE_PATCH_APPLICABILITY +Language=English +Calculating patch applicability for target product code: %1!ls!, context: %2!hs! +. + +MessageId=107 +Severity=Success +SymbolicName=MSG_DETECTED_FORWARD_COMPATIBLE_BUNDLE +Language=English +Detected forward compatible bundle: %1!ls!, type: %2!hs!, scope: %3!hs!, version: %4!ls!, cached: %5!hs! +. + +MessageId=108 +Severity=Warning +SymbolicName=MSG_DETECT_RELATED_BUNDLE_NOT_CACHED +Language=English +Detected related bundle missing from cache: %1!ls!, cache path: %2!ls! +. + +MessageId=120 +Severity=Warning +SymbolicName=MSG_DETECT_PACKAGE_NOT_FULLY_CACHED +Language=English +Detected partially cached package: %1!ls!, missing payload: %2!ls! +. + +MessageId=121 +Severity=Warning +SymbolicName=MSG_DETECT_FAILED_CALCULATE_PATCH_APPLICABILITY +Language=English +Could not calculate patch applicability for target product code: %1!ls!, context: %2!hs!, reason: 0x%3!x! +. + +MessageId=122 +Severity=Warning +SymbolicName=MSG_RELATED_PACKAGE_INVALID_VERSION +Language=English +Related package: '%1!ls!' has invalid version: %2!ls! +. + +MessageId=123 +Severity=Warning +SymbolicName=MSG_DETECTED_MSI_PACKAGE_INVALID_VERSION +Language=English +Detected msi package with invalid version, product code: '%1!ls!', version: '%2!ls!' +. + +MessageId=151 +Severity=Error +SymbolicName=MSG_FAILED_DETECT_PACKAGE +Language=English +Detect failed for package: %2!ls!, error: %1!ls! +. + +MessageId=152 +Severity=Error +SymbolicName=MSG_FAILED_READ_RELATED_PACKAGE_LANGUAGE +Language=English +Detected related package: %2!ls!, but failed to read language: %3!hs!, error: 0x%1!x! +. + +MessageId=170 +Severity=Warning +SymbolicName=MSG_DETECT_BAD_PRODUCT_CONFIGURATION +Language=English +Detected bad configuration for product: %1!ls! +. + +MessageId=199 +Severity=Success +SymbolicName=MSG_DETECT_COMPLETE +Language=English +Detect complete, result: 0x%1!x!, installed: %2!hs!, cached: %3!hs!, eligible for cleanup: %4!hs! +. + +MessageId=200 +Severity=Success +SymbolicName=MSG_PLAN_BEGIN +Language=English +Plan begin, %1!u! packages, action: %2!hs! +. + +MessageId=201 +Severity=Success +SymbolicName=MSG_PLANNED_PACKAGE +Language=English +Planned package: %1!ls!, state: %2!hs!, default requested: %3!hs!, ba requested: %4!hs!, execute: %5!hs!, rollback: %6!hs!, cache: %7!hs!, uncache: %8!hs!, dependency: %9!hs!, expected install registration state: %10!hs!, expected cache registration state: %11!hs! +. + +MessageId=202 +Severity=Success +SymbolicName=MSG_PLANNED_BUNDLE_UX_CHANGED_REQUEST +Language=English +Planned bundle: %1!ls!, ba requested state: %2!hs! over default: %3!hs! +. + +MessageId=203 +Severity=Success +SymbolicName=MSG_PLANNED_MSI_FEATURE +Language=English + Planned feature: %1!ls!, state: %2!hs!, default requested: %3!hs!, ba requested: %4!hs!, execute action: %5!hs!, rollback action: %6!hs! +. + +MessageId=204 +Severity=Success +SymbolicName=MSG_PLANNED_MSI_FEATURES +Language=English + Plan %1!u! msi features for package: %2!ls! +. + +MessageId=205 +Severity=Warning +SymbolicName=MSG_PLAN_SKIP_PATCH_ACTION +Language=English +Plan %5!hs! skipped patch: %1!ls!, action: %2!hs! because chained target package: %3!ls! being uninstalled +. + +MessageId=206 +Severity=Warning +SymbolicName=MSG_PLAN_SKIP_SLIPSTREAM_ACTION +Language=English +Plan %5!hs! skipped patch: %1!ls!, action: %2!hs! because slipstreamed into chained target package: %3!ls!, action: %4!hs! +. + +MessageId=207 +Severity=Success +SymbolicName=MSG_PLANNED_RELATED_BUNDLE +Language=English +Planned related bundle: %1!ls!, type: %2!hs!, default requested: %3!hs!, ba requested: %4!hs!, execute: %5!hs!, rollback: %6!hs!, dependency: %7!hs! +. + +MessageId=208 +Severity=Warning +SymbolicName=MSG_PLAN_DISABLING_ROLLBACK_NO_CACHE +Language=English +Plan disabled rollback due to incomplete cache for package: %1!ls!, original rollback action: %2!hs! +. + +MessageId=209 +Severity=Warning +SymbolicName=MSG_PLAN_SKIPPED_PROVIDER_KEY_REMOVAL +Language=English +Plan skipped removal of provider key: %1!ls! because it is registered to a different bundle: %2!ls! +. + +MessageId=210 +Severity=Warning +SymbolicName=MSG_PLAN_SKIPPED_DUE_TO_DEPENDENTS +Language=English +Plan skipped due to remaining dependents: +. + +MessageId=211 +Severity=Success +SymbolicName=MSG_PLANNED_UPGRADE_BUNDLE +Language=English +Planned upgrade bundle: %1!ls!, default requested: %2!hs!, ba requested: %3!hs!, execute: %4!hs!, rollback: %5!hs!, dependency: %6!hs! +. + +MessageId=212 +Severity=Success +SymbolicName=MSG_PLANNED_FORWARD_COMPATIBLE_BUNDLE +Language=English +Planned forward compatible bundle: %1!ls!, default requested: %2!hs!, ba requested: %3!hs!, execute: %4!hs!, rollback: %5!hs!, dependency: %6!hs! +. + +MessageId=213 +Severity=Success +SymbolicName=MSG_PLAN_SKIPPED_RELATED_BUNDLE_DEPENDENT +Language=English +Plan skipped related bundle: %1!ls!, type: %2!hs!, because it was dependent and the current bundle is being executed as type: %3!hs!. +. + +MessageId=214 +Severity=Success +SymbolicName=MSG_PLAN_SKIPPED_RELATED_BUNDLE_SCHEDULED +Language=English +Plan skipped related bundle: %1!ls!, type: %2!hs!, because it was previously scheduled. +. + +MessageId=216 +Severity=Success +SymbolicName=MSG_PLAN_SKIPPED_RELATED_BUNDLE_EMBEDDED_BUNDLE_NEWER +Language=English +Plan skipped related bundle: %1!ls!, type: %2!hs!, provider key: %3!ls!, because an embedded bundle with the same provider key is being installed. +. + +MessageId=217 +Severity=Success +SymbolicName=MSG_PLAN_SKIPPED_DEPENDENT_BUNDLE_REPAIR +Language=English +Plan skipped dependent bundle repair: %1!ls!, type: %2!hs!, because no packages are being executed during this uninstall operation. +. + +MessageId=218 +Severity=Success +SymbolicName=MSG_PLANNED_MSP_TARGETS +Language=English + Plan %1!u! patch targets for package: %2!ls! +. + +MessageId=219 +Severity=Success +SymbolicName=MSG_PLANNED_MSP_TARGET +Language=English + Planned patch target: %1!ls!, state: %2!hs!, default requested: %3!hs!, ba requested: %4!hs!, execute: %5!hs!, rollback: %6!hs! +. + +MessageId=220 +Severity=Success +SymbolicName=MSG_PLANNED_SLIPSTREAMED_MSP_TARGETS +Language=English + Plan %1!u! slipstream patches for package: %2!ls! +. + +MessageId=221 +Severity=Success +SymbolicName=MSG_PLANNED_SLIPSTREAMED_MSP_TARGET +Language=English + Planned slipstreamed patch: %1!ls!, execute: %2!hs!, rollback: %3!hs! +. + +MessageId=299 +Severity=Success +SymbolicName=MSG_PLAN_COMPLETE +Language=English +Plan complete, result: 0x%1!x! +. + +MessageId=300 +Severity=Success +SymbolicName=MSG_APPLY_BEGIN +Language=English +Apply begin +. + +MessageId=301 +Severity=Success +SymbolicName=MSG_APPLYING_PACKAGE +Language=English +Applying %1!hs! package: %2!ls!, action: %3!hs!, path: %4!ls!, arguments: '%5!ls!' +. + +MessageId=302 +Severity=Success +SymbolicName=MSG_ACQUIRED_PAYLOAD +Language=English +Acquired payload: %1!ls! to working path: %2!ls! from: %4!ls!. +. + +MessageId=303 +Severity=Success +SymbolicName=MSG_VERIFIED_EXISTING_CONTAINER +Language=English +Verified existing container: %1!ls! at path: %2!ls!. +. + +MessageId=304 +Severity=Success +SymbolicName=MSG_VERIFIED_EXISTING_PAYLOAD +Language=English +Verified existing payload: %1!ls! at path: %2!ls!. +. + +MessageId=305 +Severity=Success +SymbolicName=MSG_VERIFIED_ACQUIRED_PAYLOAD +Language=English +Verified acquired payload: %1!ls! at path: %2!ls!, %3!hs! to: %4!ls!. +. + +MessageId=306 +Severity=Success +SymbolicName=MSG_APPLYING_PATCH_PACKAGE +Language=English +Applying package: %1!ls!, target: %5!ls!, action: %2!hs!, path: %3!ls!, arguments: '%4!ls!' +. + +MessageId=307 +Severity=Warning +SymbolicName=MSG_ATTEMPTED_UNINSTALL_ABSENT_PACKAGE +Language=English +Attempted to uninstall absent package: %1!ls!. Continuing... +. + +MessageId=308 +Severity=Warning +SymbolicName=MSG_FAILED_PAUSE_AU +Language=English +Automatic updates could not be paused due to error: 0x%1!x!. Continuing... +. + +MessageId=309 +Severity=Warning +SymbolicName=MSG_APPLY_SKIPPED_FAILED_CACHED_PACKAGE +Language=English +Skipping apply of package: %1!ls! due to cache error: 0x%2!x!. Continuing... +. + +MessageId=310 +Severity=Error +SymbolicName=MSG_FAILED_VERIFY_PAYLOAD +Language=English +Failed to verify payload: %2!ls! at path: %3!ls!, error: %1!ls!. Deleting file. +. + +MessageId=311 +Severity=Error +SymbolicName=MSG_FAILED_ACQUIRE_CONTAINER +Language=English +Failed to acquire container: %2!ls! to working path: %3!ls!, error: %1!ls!. +. + +MessageId=312 +Severity=Error +SymbolicName=MSG_FAILED_EXTRACT_CONTAINER +Language=English +Failed to extract payloads from container: %2!ls! to working path: %3!ls!, error: %1!ls!. +. + +MessageId=313 +Severity=Error +SymbolicName=MSG_FAILED_ACQUIRE_PAYLOAD +Language=English +Failed to acquire payload: %2!ls! to working path: %3!ls!, error: %1!ls!. +. + +MessageId=314 +Severity=Error +SymbolicName=MSG_FAILED_CACHE_PAYLOAD +Language=English +Failed to cache payload: %2!ls! from working path: %4!ls!, error: %1!ls!. +. + +MessageId=315 +Severity=Error +SymbolicName=MSG_FAILED_LAYOUT_BUNDLE +Language=English +Failed to layout bundle: %2!ls! to layout directory: %3!ls!, error: %1!ls!. +. + +MessageId=316 +Severity=Error +SymbolicName=MSG_FAILED_LAYOUT_CONTAINER +Language=English +Failed to layout container: %2!ls! to layout directory: %3!ls!, error: %1!ls!. +. + + +MessageId=317 +Severity=Error +SymbolicName=MSG_FAILED_LAYOUT_PAYLOAD +Language=English +Failed to layout payload: %2!ls! from working path: %4!ls! to layout directory: %3!ls!, error: %1!ls!. +. + +MessageId=318 +Severity=Success +SymbolicName=MSG_ROLLBACK_PACKAGE_SKIPPED +Language=English +Skipped rollback of package: %1!ls!, action: %2!hs!, already: %3!hs! +. + +MessageId=319 +Severity=Success +SymbolicName=MSG_APPLY_COMPLETED_PACKAGE +Language=English +Applied %1!hs! package: %2!ls!, result: 0x%3!x!, restart: %4!hs! +. + +MessageId=320 +Severity=Success +SymbolicName=MSG_DEPENDENCY_BUNDLE_REGISTER +Language=English +Registering bundle dependency provider: %1!ls!, version: %2!ls! +. + +MessageId=321 +Severity=Warning +SymbolicName=MSG_DEPENDENCY_PACKAGE_SKIP_NOPROVIDERS +Language=English +Skipping dependency registration on package with no dependency providers: %1!ls! +. + +MessageId=322 +Severity=Warning +SymbolicName=MSG_DEPENDENCY_PACKAGE_SKIP_WRONGSCOPE +Language=English +Skipping cross-scope dependency registration on package: %1!ls!, bundle scope: %2!hs!, package scope: %3!hs! +. + +MessageId=323 +Severity=Success +SymbolicName=MSG_DEPENDENCY_PACKAGE_REGISTER +Language=English +Registering package dependency provider: %1!ls!, version: %2!ls!, package: %3!ls! +. + +MessageId=324 +Severity=Warning +SymbolicName=MSG_DEPENDENCY_PACKAGE_SKIP_MISSING +Language=English +Skipping dependency registration on missing package provider: %1!ls!, package: %2!ls! +. + +MessageId=325 +Severity=Success +SymbolicName=MSG_DEPENDENCY_PACKAGE_REGISTER_DEPENDENCY +Language=English +Registering dependency: %1!ls! on package provider: %2!ls!, package: %3!ls! +. + +MessageId=326 +Severity=Success +SymbolicName=MSG_DEPENDENCY_PACKAGE_UNREGISTERED_DEPENDENCY +Language=English +Removed dependency: %1!ls! on package provider: %2!ls!, package %3!ls! +. + +MessageId=327 +Severity=Warning +SymbolicName=MSG_DEPENDENCY_PACKAGE_HASDEPENDENTS +Language=English +Will not uninstall package: %1!ls!, found dependents: +. + +MessageId=328 +Severity=Warning +SymbolicName=MSG_DEPENDENCY_PACKAGE_DEPENDENT +Language=English +Found dependent: %1!ls!, name: %2!ls! +. + +MessageId=329 +Severity=Success +SymbolicName=MSG_DEPENDENCY_PACKAGE_UNREGISTERED +Language=English +Removed package dependency provider: %1!ls!, package: %2!ls! +. + +MessageId=330 +Severity=Success +SymbolicName=MSG_DEPENDENCY_BUNDLE_UNREGISTERED +Language=English +Removed bundle dependency provider: %1!ls! +. + +MessageId=331 +Severity=Warning +SymbolicName=MSG_DEPENDENCY_PACKAGE_UNREGISTERED_DEPENDENCY_FAILED +Language=English +Could not remove dependency: %1!ls! on package provider: %2!ls!, package %3!ls!, error: 0x%4!x! +. + +MessageId=332 +Severity=Warning +SymbolicName=MSG_DEPENDENCY_PACKAGE_UNREGISTERED_FAILED +Language=English +Could not remove package dependency provider: %1!ls!, package: %2!ls!, error: 0x%3!x! +. + +MessageId=333 +Severity=Warning +SymbolicName=MSG_DEPENDENCY_BUNDLE_UNREGISTERED_FAILED +Language=English +Could not remove bundle dependency provider: %1!ls!, error: 0x%2!x! +. + +MessageId=334 +Severity=Warning +SymbolicName=MSG_DEPENDENCY_BUNDLE_DEPENDENT +Language=English +Found dependent: %1!ls!, name: %2!ls! +. + +MessageId=335 +Severity=Success +SymbolicName=MSG_ACQUIRE_BUNDLE_PAYLOAD +Language=English +Acquiring bundle payload: %2!ls!, %3!hs! from: %4!ls! +. + +MessageId=336 +Severity=Success +SymbolicName=MSG_ACQUIRE_CONTAINER +Language=English +Acquiring container: %1!ls!, %3!hs! from: %4!ls! +. + +MessageId=338 +Severity=Success +SymbolicName=MSG_ACQUIRE_PACKAGE_PAYLOAD +Language=English +Acquiring package: %1!ls!, payload: %2!ls!, %3!hs! from: %4!ls! +. + +MessageId=339 +Severity=Error +SymbolicName=MSG_FAILED_VERIFY_CONTAINER +Language=English +Failed to verify container: %2!ls! at path: %3!ls!, error: %1!ls!. Deleting file. +. + +MessageId=340 +Severity=Warning +SymbolicName=MSG_CACHE_CONTINUING_NONVITAL_PACKAGE +Language=English +Cached non-vital package: %1!ls!, encountered error: 0x%2!x!. Continuing... +. + +MessageId=346 +Severity=Warning +SymbolicName=MSG_CACHE_RETRYING_PACKAGE +Language=English +Application requested retry of caching package: %1!ls!, encountered error: 0x%2!x!. Retrying... +. + +MessageId=347 +Severity=Warning +SymbolicName=MSG_CACHE_RETRYING_CONTAINER +Language=English +Application requested retry of caching container: %2!ls!, encountered error: %1!ls!. Retrying... +. + +MessageId=348 +Severity=Warning +SymbolicName=MSG_APPLY_RETRYING_PACKAGE +Language=English +Application requested retry of executing package: %1!ls!, encountered error: 0x%2!x!. Retrying... +. + +MessageId=349 +Severity=Warning +SymbolicName=MSG_CACHE_RETRYING_PAYLOAD +Language=English +Application requested retry of caching payload: %2!ls!, encountered error: %1!ls!. Retrying... +. + +MessageId=350 +Severity=Warning +SymbolicName=MSG_APPLY_CONTINUING_NONVITAL_PACKAGE +Language=English +Applied non-vital package: %1!ls!, encountered error: 0x%2!x!. Continuing... +. + +MessageId=351 +Severity=Success +SymbolicName=MSG_UNCACHE_PACKAGE +Language=English +Removing cached package: %1!ls!, from path: %2!ls! +. + +MessageId=352 +Severity=Success +SymbolicName=MSG_UNCACHE_BUNDLE +Language=English +Removing cached bundle: %1!ls!, from path: %2!ls! +. + +MessageId=353 +Severity=Warning +SymbolicName=MSG_UNABLE_UNCACHE_PACKAGE +Language=English +Unable to remove cached package: %1!ls!, from path: %2!ls!, reason: 0x%3!x!. Continuing... +. + +MessageId=354 +Severity=Warning +SymbolicName=MSG_UNABLE_UNCACHE_BUNDLE +Language=English +Unable to remove cached bundle: %1!ls!, from path: %2!ls!, reason: 0x%3!x!. Continuing... +. + +MessageId=355 +Severity=Warning +SymbolicName=MSG_SOURCELIST_REGISTER +Language=English +Unable to register source directory: %1!ls!, product: %2!ls!, reason: 0x%3!x!. Continuing... +. + +MessageId=356 +Severity=Warning +SymbolicName=MSG_APPLY_RETRYING_ACQUIRE_CONTAINER +Language=English +Application requested retry acquire of container: %2!ls!, encountered error: %1!ls!. Retrying... +. + +MessageId=357 +Severity=Warning +SymbolicName=MSG_APPLY_RETRYING_ACQUIRE_PAYLOAD +Language=English +Application requested retry acquire of payload: %2!ls!, encountered error: %1!ls!. Retrying... +. + +MessageId=358 +Severity=Success +SymbolicName=MSG_PAUSE_AU_STARTING +Language=English +Pausing automatic updates. +. + +MessageId=359 +Severity=Success +SymbolicName=MSG_PAUSE_AU_SUCCEEDED +Language=English +Paused automatic updates. +. + +MessageId=360 +Severity=Success +SymbolicName=MSG_SYSTEM_RESTORE_POINT_STARTING +Language=English +Creating a system restore point. +. + +MessageId=361 +Severity=Success +SymbolicName=MSG_SYSTEM_RESTORE_POINT_SUCCEEDED +Language=English +Created a system restore point. +. + +MessageId=362 +Severity=Success +SymbolicName=MSG_SYSTEM_RESTORE_POINT_DISABLED +Language=English +System restore disabled, system restore point not created. +. + +MessageId=363 +Severity=Warning +SymbolicName=MSG_SYSTEM_RESTORE_POINT_FAILED +Language=English +Could not create system restore point, error: 0x%1!x!. Continuing... +. + +MessageId=370 +Severity=Success +SymbolicName=MSG_SESSION_BEGIN +Language=English +Session begin, registration key: %1!ls!, options: 0x%2!x!, disable resume: %3!hs! +. + +MessageId=371 +Severity=Success +SymbolicName=MSG_SESSION_UPDATE +Language=English +Updating session, registration key: %1!ls!, resume: %2!hs!, restart initiated: %3!hs!, disable resume: %4!hs! +. + +MessageId=372 +Severity=Success +SymbolicName=MSG_SESSION_END +Language=English +Session end, registration key: %1!ls!, resume: %2!hs!, restart: %3!hs!, disable resume: %4!hs! +. + +MessageId=373 +Severity=Success +SymbolicName=MSG_POST_APPLY_CALCULATE_REGISTRATION +Language=English +Calculating whether to keep registration +. + + +MessageId=374 +Severity=Success +SymbolicName=MSG_POST_APPLY_PACKAGE +Language=English + package: %1!ls!, install registration state: %2!hs!, cache registration state: %3!hs! +. + +MessageId=380 +Severity=Warning +SymbolicName=MSG_APPLY_SKIPPED +Language=English +Apply skipped, no planned actions +. + +MessageId=381 +Severity=Warning +SymbolicName=MSG_APPLY_CANCEL_IGNORED_DURING_ROLLBACK +Language=English +Ignoring application request to cancel from %1!ls! during rollback. +. + +MessageId=382 +Severity=Warning +SymbolicName=MSG_PLAN_ROLLBACK_DISABLED +Language=English +Rollback is disabled for this bundle. +. + +MessageId=383 +Severity=Error +SymbolicName=MSG_MSI_TRANSACTIONS_DISABLED +Language=English +Windows Installer rollback is disabled on this computer. It must be enabled for this bundle to proceed. +. + +MessageId=384 +Severity=Success +SymbolicName=MSG_MSI_TRANSACTION_BEGIN +Language=English +Starting a new MSI transaction, id: %1!ls! +. + +MessageId=385 +Severity=Success +SymbolicName=MSG_MSI_TRANSACTION_COMMIT +Language=English +Committing MSI transaction, id: %1!ls! +. + +MessageId=386 +Severity=Warning +SymbolicName=MSG_MSI_TRANSACTION_ROLLBACK +Language=English +Rolling back MSI transaction, id: %1!ls! +. + +MessageId=387 +Severity=Error +SymbolicName=MSG_RESTART_REQUEST_DURING_MSI_TRANSACTION +Language=English +Illegal state: Reboot requested within an MSI transaction, id: %1!ls! +. + +MessageId=399 +Severity=Success +SymbolicName=MSG_APPLY_COMPLETE +Language=English +Apply complete, result: 0x%1!x!, restart: %2!hs!, ba requested restart: %3!hs! +. + +MessageId=400 +Severity=Success +SymbolicName=MSG_SYSTEM_SHUTDOWN +Language=English +Received system request to shut down the process: critical: %1!hs!, elevated: %2!hs!, allowed: %3!hs! +. + +MessageId=410 +Severity=Success +SymbolicName=MSG_VARIABLE_DUMP +Language=English +Variable: %1!ls! +. + +MessageId=411 +Severity=Warning +SymbolicName=MSG_VARIABLE_INVALID_VERSION +Language=English +The variable '%1!ls!' is being set with an invalid version string. +. + +MessageId=412 +Severity=Warning +SymbolicName=MSG_INVALID_VERSION_COERSION +Language=English +The string '%1!ls!' could not be coerced to a valid version. +. + +MessageId=420 +Severity=Success +SymbolicName=MSG_RESUME_AU_STARTING +Language=English +Resuming automatic updates. +. + +MessageId=421 +Severity=Success +SymbolicName=MSG_RESUME_AU_SUCCEEDED +Language=English +Resumed automatic updates. +. + +MessageId=500 +Severity=Success +SymbolicName=MSG_QUIT +Language=English +Shutting down, exit code: 0x%1!x! +. + +MessageId=501 +Severity=Warning +SymbolicName=MSG_STATE_NOT_SAVED +Language=English +The state file could not be saved, error: %1!ls!. Continuing... +. + +MessageId=502 +Severity=Success +SymbolicName=MSG_CLEANUP_BEGIN +Language=English +Cleanup begin. +. + +MessageId=503 +Severity=Success +SymbolicName=MSG_CLEANUP_SKIPPED_APPLY +Language=English +Cleanup not required due to running Apply. +. + +MessageId=504 +Severity=Success +SymbolicName=MSG_CLEANUP_SKIPPED_ELEVATION_REQUIRED +Language=English +Cleanup check skipped since this per-machine bundle would require elevation. +. + +MessageId=599 +Severity=Success +SymbolicName=MSG_CLEANUP_COMPLETE +Language=English +Cleanup complete, result: 0x%1!x! +. + +MessageId=600 +Severity=Success +SymbolicName=MSG_LAUNCH_APPROVED_EXE_BEGIN +Language=English +LaunchApprovedExe begin, id: %1!ls! +. + +MessageId=601 +Severity=Success +SymbolicName=MSG_LAUNCH_APPROVED_EXE_SEARCH +Language=English +Searching registry for approved exe path, key: %1!ls!, value: '%2!ls!', win64: %3!ls! +. + +MessageId=602 +Severity=Success +SymbolicName=MSG_LAUNCHING_APPROVED_EXE +Language=English +Launching approved exe, path: '%1!ls!', 'command: %2!ls!' +. + +MessageId=699 +Severity=Success +SymbolicName=MSG_LAUNCH_APPROVED_EXE_COMPLETE +Language=English +LaunchApprovedExe complete, result: 0x%1!x!, processId: %2!lu! +. + +MessageId=700 +Severity=Success +SymbolicName=MSG_MSI_PROPERTY_CONDITION_FAILED +Language=English +Skipping MSI property '%1!ls!' because condition '%2!ls!' evaluates to %3!hs!. +. + +MessageId=701 +Severity=Warning +SymbolicName=MSG_PENDING_REBOOT_DETECTED +Language=English +A reboot is pending from a prior execution of this bundle: %1!ls!. Apply will be blocked. Continuing... +. diff --git a/src/burn/engine/engine.vcxproj b/src/burn/engine/engine.vcxproj new file mode 100644 index 00000000..b3a0f81b --- /dev/null +++ b/src/burn/engine/engine.vcxproj @@ -0,0 +1,186 @@ + + + + + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + {8119537D-E1D9-6591-D51A-49768A2F9C37} + StaticLibrary + engine + v142 + Unicode + Native component of WixToolset.Burn + + + + + + + $(ProjectDir)..\..\..\balutil\src\WixToolset.BootstrapperCore.Native\inc;$(ProjectAdditionalIncludeDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Compiling message file... + mc.exe -h "$(IntDir)." -r "$(IntDir)." -A -c -z engine.messages "$(InputDir)engine.mc" +rc.exe -fo "$(OutDir)engine.res" "$(IntDir)engine.messages.rc" + $(IntDir)engine.messages.h;$(IntDir)engine.messages.rc;$(OutDir)engine.res + + + + + + $(MajorMinorVersion.Split(`.`)[0]) + $(MajorMinorVersion.Split(`.`)[1]) + 0 + $(BuildNumber) + $(rmj).$(rmm).$(rup).$(rpr) + rmj=$(rmj);rmm=$(rmm);rup=$(rup);rpr=$(rpr);szVerMajorMinorBuild="$(szVerMajorMinorBuild)" + + + + + $(wixver);%(PreprocessorDefinitions) + + + + + + + + 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/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp new file mode 100644 index 00000000..c0ba93e0 --- /dev/null +++ b/src/burn/engine/exeengine.cpp @@ -0,0 +1,816 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// internal function declarations + +static HRESULT HandleExitCode( + __in BURN_PACKAGE* pPackage, + __in DWORD dwExitCode, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +static HRESULT ParseCommandLineArgumentsFromXml( + __in IXMLDOMNode* pixnExePackage, + __in BURN_PACKAGE* pPackage + ); +static HRESULT ParseExitCodesFromXml( + __in IXMLDOMNode* pixnExePackage, + __in BURN_PACKAGE* pPackage + ); + + +// function definitions + +extern "C" HRESULT ExeEngineParsePackageFromXml( + __in IXMLDOMNode* pixnExePackage, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + LPWSTR scz = NULL; + + // @DetectCondition + hr = XmlGetAttributeEx(pixnExePackage, L"DetectCondition", &pPackage->Exe.sczDetectCondition); + ExitOnFailure(hr, "Failed to get @DetectCondition."); + + // @InstallArguments + hr = XmlGetAttributeEx(pixnExePackage, L"InstallArguments", &pPackage->Exe.sczInstallArguments); + ExitOnFailure(hr, "Failed to get @InstallArguments."); + + // @UninstallArguments + hr = XmlGetAttributeEx(pixnExePackage, L"UninstallArguments", &pPackage->Exe.sczUninstallArguments); + ExitOnFailure(hr, "Failed to get @UninstallArguments."); + + // @RepairArguments + hr = XmlGetAttributeEx(pixnExePackage, L"RepairArguments", &pPackage->Exe.sczRepairArguments); + ExitOnFailure(hr, "Failed to get @RepairArguments."); + + // @Repairable + hr = XmlGetYesNoAttribute(pixnExePackage, L"Repairable", &pPackage->Exe.fRepairable); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Repairable."); + } + + // @Protocol + hr = XmlGetAttributeEx(pixnExePackage, L"Protocol", &scz); + if (SUCCEEDED(hr)) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"burn", -1)) + { + pPackage->Exe.protocol = BURN_EXE_PROTOCOL_TYPE_BURN; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"netfx4", -1)) + { + pPackage->Exe.protocol = BURN_EXE_PROTOCOL_TYPE_NETFX4; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"none", -1)) + { + pPackage->Exe.protocol = BURN_EXE_PROTOCOL_TYPE_NONE; + } + else + { + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Invalid protocol type: %ls", scz); + } + } + else if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Protocol."); + } + + hr = ParseExitCodesFromXml(pixnExePackage, pPackage); + ExitOnFailure(hr, "Failed to parse exit codes."); + + hr = ParseCommandLineArgumentsFromXml(pixnExePackage, pPackage); + ExitOnFailure(hr, "Failed to parse command lines."); + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(scz); + + return hr; +} + +extern "C" void ExeEnginePackageUninitialize( + __in BURN_PACKAGE* pPackage + ) +{ + ReleaseStr(pPackage->Exe.sczDetectCondition); + ReleaseStr(pPackage->Exe.sczInstallArguments); + ReleaseStr(pPackage->Exe.sczRepairArguments); + ReleaseStr(pPackage->Exe.sczUninstallArguments); + ReleaseStr(pPackage->Exe.sczIgnoreDependencies); + //ReleaseStr(pPackage->Exe.sczProgressSwitch); + ReleaseMem(pPackage->Exe.rgExitCodes); + + // free command-line arguments + if (pPackage->Exe.rgCommandLineArguments) + { + for (DWORD i = 0; i < pPackage->Exe.cCommandLineArguments; ++i) + { + BURN_EXE_COMMAND_LINE_ARGUMENT* pCommandLineArgument = &pPackage->Exe.rgCommandLineArguments[i]; + ReleaseStr(pCommandLineArgument->sczInstallArgument); + ReleaseStr(pCommandLineArgument->sczUninstallArgument); + ReleaseStr(pCommandLineArgument->sczRepairArgument); + ReleaseStr(pCommandLineArgument->sczCondition); + } + MemFree(pPackage->Exe.rgCommandLineArguments); + } + + // clear struct + memset(&pPackage->Exe, 0, sizeof(pPackage->Exe)); +} + +extern "C" HRESULT ExeEngineDetectPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + BOOL fDetected = FALSE; + + // evaluate detect condition + if (pPackage->Exe.sczDetectCondition && *pPackage->Exe.sczDetectCondition) + { + hr = ConditionEvaluate(pVariables, pPackage->Exe.sczDetectCondition, &fDetected); + ExitOnFailure(hr, "Failed to evaluate executable package detect condition."); + } + + // update detect state + pPackage->currentState = fDetected ? BOOTSTRAPPER_PACKAGE_STATE_PRESENT : BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + + if (pPackage->fCanAffectRegistration) + { + pPackage->installRegistrationState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT < pPackage->currentState ? BURN_PACKAGE_REGISTRATION_STATE_PRESENT : BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + +LExit: + return hr; +} + +// +// PlanCalculate - calculates the execute and rollback state for the requested package state. +// +extern "C" HRESULT ExeEnginePlanCalculatePackage( + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE; + BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + + // execute action + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: + execute = pPackage->Exe.fPseudoBundle ? BOOTSTRAPPER_ACTION_STATE_INSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + execute = pPackage->Exe.fRepairable ? BOOTSTRAPPER_ACTION_STATE_REPAIR : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_CACHE: + execute = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: + execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + break; + default: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + execute = BOOTSTRAPPER_ACTION_STATE_INSTALL; + break; + default: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package current state: %d.", pPackage->currentState); + } + + // Calculate the rollback action if there is an execute action. + if (BOOTSTRAPPER_ACTION_STATE_NONE != execute) + { + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: + rollback = BOOTSTRAPPER_ACTION_STATE_INSTALL; + break; + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + rollback = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package expected state."); + } + } + + // return values + pPackage->execute = execute; + pPackage->rollback = rollback; + +LExit: + return hr; +} + +// +// PlanAdd - adds the calculated execute and rollback actions for the package. +// +extern "C" HRESULT ExeEnginePlanAddPackage( + __in_opt DWORD *pdwInsertSequence, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in_opt HANDLE hCacheEvent + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pAction = NULL; + + // add wait for cache + if (hCacheEvent) + { + hr = PlanExecuteCacheSyncAndRollback(pPlan, pPackage, hCacheEvent); + ExitOnFailure(hr, "Failed to plan package cache syncpoint"); + } + + hr = DependencyPlanPackage(pdwInsertSequence, pPackage, pPlan); + ExitOnFailure(hr, "Failed to plan package dependency actions."); + + // add execute action + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute) + { + if (NULL != pdwInsertSequence) + { + hr = PlanInsertExecuteAction(*pdwInsertSequence, pPlan, &pAction); + ExitOnFailure(hr, "Failed to insert execute action."); + } + else + { + hr = PlanAppendExecuteAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append execute action."); + } + + pAction->type = BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE; + pAction->exePackage.pPackage = pPackage; + pAction->exePackage.fFireAndForget = (BOOTSTRAPPER_ACTION_UPDATE_REPLACE == pPlan->action); + pAction->exePackage.action = pPackage->execute; + + if (pPackage->Exe.sczIgnoreDependencies) + { + hr = StrAllocString(&pAction->exePackage.sczIgnoreDependencies, pPackage->Exe.sczIgnoreDependencies, 0); + ExitOnFailure(hr, "Failed to allocate the list of dependencies to ignore."); + } + + if (pPackage->Exe.wzAncestors) + { + hr = StrAllocString(&pAction->exePackage.sczAncestors, pPackage->Exe.wzAncestors, 0); + ExitOnFailure(hr, "Failed to allocate the list of ancestors."); + } + + LoggingSetPackageVariable(pPackage, NULL, FALSE, pLog, pVariables, NULL); // ignore errors. + } + + // add rollback action + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback) + { + hr = PlanAppendRollbackAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append rollback action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE; + pAction->exePackage.pPackage = pPackage; + pAction->exePackage.action = pPackage->rollback; + + if (pPackage->Exe.sczIgnoreDependencies) + { + hr = StrAllocString(&pAction->exePackage.sczIgnoreDependencies, pPackage->Exe.sczIgnoreDependencies, 0); + ExitOnFailure(hr, "Failed to allocate the list of dependencies to ignore."); + } + + if (pPackage->Exe.wzAncestors) + { + hr = StrAllocString(&pAction->exePackage.sczAncestors, pPackage->Exe.wzAncestors, 0); + ExitOnFailure(hr, "Failed to allocate the list of ancestors."); + } + + LoggingSetPackageVariable(pPackage, NULL, TRUE, pLog, pVariables, NULL); // ignore errors. + } + +LExit: + return hr; +} + +extern "C" HRESULT ExeEngineExecutePackage( + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + int nResult = IDNOACTION; + LPCWSTR wzArguments = NULL; + LPWSTR sczArguments = NULL; + LPWSTR sczArgumentsFormatted = NULL; + LPWSTR sczArgumentsObfuscated = NULL; + LPWSTR sczCachedDirectory = NULL; + LPWSTR sczExecutablePath = NULL; + LPWSTR sczCommand = NULL; + LPWSTR sczCommandObfuscated = NULL; + HANDLE hExecutableFile = INVALID_HANDLE_VALUE; + STARTUPINFOW si = { }; + PROCESS_INFORMATION pi = { }; + DWORD dwExitCode = 0; + GENERIC_EXECUTE_MESSAGE message = { }; + BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage; + BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload; + + // get cached executable path + hr = CacheGetCompletedPath(pPackage->fPerMachine, pPackage->sczCacheId, &sczCachedDirectory); + ExitOnFailure(hr, "Failed to get cached path for package: %ls", pPackage->sczId); + + // Best effort to set the execute package cache folder and action variables. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, sczCachedDirectory, TRUE, FALSE); + VariableSetNumeric(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, pExecuteAction->exePackage.action, TRUE); + + hr = PathConcat(sczCachedDirectory, pPackagePayload->sczFilePath, &sczExecutablePath); + ExitOnFailure(hr, "Failed to build executable path."); + + // pick arguments + switch (pExecuteAction->exePackage.action) + { + case BOOTSTRAPPER_ACTION_STATE_INSTALL: + wzArguments = pPackage->Exe.sczInstallArguments; + break; + + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + wzArguments = pPackage->Exe.sczUninstallArguments; + break; + + case BOOTSTRAPPER_ACTION_STATE_REPAIR: + wzArguments = pPackage->Exe.sczRepairArguments; + break; + + default: + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid Exe package action: %d.", pExecuteAction->exePackage.action); + } + + // now add optional arguments + hr = StrAllocString(&sczArguments, wzArguments && *wzArguments ? wzArguments : L"", 0); + ExitOnFailure(hr, "Failed to copy package arguments."); + + for (DWORD i = 0; i < pPackage->Exe.cCommandLineArguments; ++i) + { + BURN_EXE_COMMAND_LINE_ARGUMENT* commandLineArgument = &pPackage->Exe.rgCommandLineArguments[i]; + BOOL fCondition = FALSE; + + hr = ConditionEvaluate(pVariables, commandLineArgument->sczCondition, &fCondition); + ExitOnFailure(hr, "Failed to evaluate executable package command-line condition."); + + if (fCondition) + { + hr = StrAllocConcat(&sczArguments, L" ", 0); + ExitOnFailure(hr, "Failed to separate command-line arguments."); + + switch (pExecuteAction->exePackage.action) + { + case BOOTSTRAPPER_ACTION_STATE_INSTALL: + hr = StrAllocConcat(&sczArguments, commandLineArgument->sczInstallArgument, 0); + ExitOnFailure(hr, "Failed to get command-line argument for install."); + break; + + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + hr = StrAllocConcat(&sczArguments, commandLineArgument->sczUninstallArgument, 0); + ExitOnFailure(hr, "Failed to get command-line argument for uninstall."); + break; + + case BOOTSTRAPPER_ACTION_STATE_REPAIR: + hr = StrAllocConcat(&sczArguments, commandLineArgument->sczRepairArgument, 0); + ExitOnFailure(hr, "Failed to get command-line argument for repair."); + break; + + default: + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid Exe package action: %d.", pExecuteAction->exePackage.action); + } + } + } + + // build command + if (*sczArguments) + { + hr = VariableFormatString(pVariables, sczArguments, &sczArgumentsFormatted, NULL); + ExitOnFailure(hr, "Failed to format argument string."); + + hr = StrAllocFormattedSecure(&sczCommand, L"\"%ls\" %s", sczExecutablePath, sczArgumentsFormatted); + ExitOnFailure(hr, "Failed to create executable command."); + + hr = VariableFormatStringObfuscated(pVariables, sczArguments, &sczArgumentsObfuscated, NULL); + ExitOnFailure(hr, "Failed to format obfuscated argument string."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"\"%ls\" %s", sczExecutablePath, sczArgumentsObfuscated); + } + else + { + hr = StrAllocFormatted(&sczCommand, L"\"%ls\"", sczExecutablePath); + ExitOnFailure(hr, "Failed to create executable command."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"\"%ls\"", sczExecutablePath); + } + ExitOnFailure(hr, "Failed to create obfuscated executable command."); + + if (pPackage->Exe.fSupportsAncestors) + { + // Add the list of dependencies to ignore, if any, to the burn command line. + if (pExecuteAction->exePackage.sczIgnoreDependencies && BURN_EXE_PROTOCOL_TYPE_BURN == pPackage->Exe.protocol) + { + hr = StrAllocFormattedSecure(&sczCommand, L"%ls -%ls=%ls", sczCommand, BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES, pExecuteAction->exePackage.sczIgnoreDependencies); + ExitOnFailure(hr, "Failed to append the list of dependencies to ignore to the command line."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"%ls -%ls=%ls", sczCommandObfuscated, BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES, pExecuteAction->exePackage.sczIgnoreDependencies); + ExitOnFailure(hr, "Failed to append the list of dependencies to ignore to the obfuscated command line."); + } + + // Add the list of ancestors, if any, to the burn command line. + if (pExecuteAction->exePackage.sczAncestors) + { + hr = StrAllocFormattedSecure(&sczCommand, L"%ls -%ls=%ls", sczCommand, BURN_COMMANDLINE_SWITCH_ANCESTORS, pExecuteAction->exePackage.sczAncestors); + ExitOnFailure(hr, "Failed to append the list of ancestors to the command line."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"%ls -%ls=%ls", sczCommandObfuscated, BURN_COMMANDLINE_SWITCH_ANCESTORS, pExecuteAction->exePackage.sczAncestors); + ExitOnFailure(hr, "Failed to append the list of ancestors to the obfuscated command line."); + } + } + + if (BURN_EXE_PROTOCOL_TYPE_BURN == pPackage->Exe.protocol) + { + hr = CoreAppendFileHandleSelfToCommandLine(sczExecutablePath, &hExecutableFile, &sczCommand, &sczCommandObfuscated); + ExitOnFailure(hr, "Failed to append %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF); + } + + // Log before we add the secret pipe name and client token for embedded processes. + LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, LoggingRollbackOrExecute(fRollback), pPackage->sczId, LoggingActionStateToString(pExecuteAction->exePackage.action), sczExecutablePath, sczCommandObfuscated); + + if (!pExecuteAction->exePackage.fFireAndForget && BURN_EXE_PROTOCOL_TYPE_BURN == pPackage->Exe.protocol) + { + hr = EmbeddedRunBundle(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode); + ExitOnFailure(hr, "Failed to run bundle as embedded from path: %ls", sczExecutablePath); + } + else if (!pExecuteAction->exePackage.fFireAndForget && BURN_EXE_PROTOCOL_TYPE_NETFX4 == pPackage->Exe.protocol) + { + hr = NetFxRunChainer(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode); + ExitOnFailure(hr, "Failed to run netfx chainer: %ls", sczExecutablePath); + } + else // create and wait for the executable process while sending fake progress to allow cancel. + { + // Make the cache location of the executable the current directory to help those executables + // that expect stuff to be relative to them. + si.cb = sizeof(si); + if (!::CreateProcessW(sczExecutablePath, sczCommand, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, sczCachedDirectory, &si, &pi)) + { + ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczExecutablePath); + } + + if (pExecuteAction->exePackage.fFireAndForget) + { + ::WaitForInputIdle(pi.hProcess, 5000); + ExitFunction(); + } + + do + { + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwAllowedResults = MB_OKCANCEL; + message.progress.dwPercentage = 50; + nResult = pfnGenericMessageHandler(&message, pvContext); + hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE); + ExitOnRootFailure(hr, "Bootstrapper application aborted during EXE progress."); + + hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode); + if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr) + { + ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczExecutablePath); + } + } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr); + } + + hr = HandleExitCode(pPackage, dwExitCode, pRestart); + ExitOnRootFailure(hr, "Process returned error: 0x%x", dwExitCode); + +LExit: + StrSecureZeroFreeString(sczArguments); + StrSecureZeroFreeString(sczArgumentsFormatted); + ReleaseStr(sczArgumentsObfuscated); + ReleaseStr(sczCachedDirectory); + ReleaseStr(sczExecutablePath); + StrSecureZeroFreeString(sczCommand); + ReleaseStr(sczCommandObfuscated); + + ReleaseHandle(pi.hThread); + ReleaseHandle(pi.hProcess); + ReleaseFileHandle(hExecutableFile); + + // Best effort to clear the execute package cache folder and action variables. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, NULL, TRUE, FALSE); + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, NULL, TRUE, FALSE); + + return hr; +} + +extern "C" void ExeEngineUpdateInstallRegistrationState( + __in BURN_EXECUTE_ACTION* pAction, + __in HRESULT hrExecute + ) +{ + BURN_PACKAGE* pPackage = pAction->exePackage.pPackage; + + if (FAILED(hrExecute) || !pPackage->fCanAffectRegistration) + { + ExitFunction(); + } + + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pAction->exePackage.action) + { + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + else + { + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + +LExit: + return; +} + + +// internal helper functions + +static HRESULT ParseExitCodesFromXml( + __in IXMLDOMNode* pixnExePackage, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + LPWSTR scz = NULL; + + // select exit code nodes + hr = XmlSelectNodes(pixnExePackage, L"ExitCode", &pixnNodes); + ExitOnFailure(hr, "Failed to select exit code nodes."); + + // get exit code node count + hr = pixnNodes->get_length((long*) &cNodes); + ExitOnFailure(hr, "Failed to get exit code node count."); + + if (cNodes) + { + // allocate memory for exit codes + pPackage->Exe.rgExitCodes = (BURN_EXE_EXIT_CODE*) MemAlloc(sizeof(BURN_EXE_EXIT_CODE) * cNodes, TRUE); + ExitOnNull(pPackage->Exe.rgExitCodes, hr, E_OUTOFMEMORY, "Failed to allocate memory for exit code structs."); + + pPackage->Exe.cExitCodes = cNodes; + + // parse package elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_EXE_EXIT_CODE* pExitCode = &pPackage->Exe.rgExitCodes[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Type + hr = XmlGetAttributeNumber(pixnNode, L"Type", (DWORD*)&pExitCode->type); + ExitOnFailure(hr, "Failed to get @Type."); + + // @Code + hr = XmlGetAttributeEx(pixnNode, L"Code", &scz); + ExitOnFailure(hr, "Failed to get @Code."); + + if (L'*' == scz[0]) + { + pExitCode->fWildcard = TRUE; + } + else + { + hr = StrStringToUInt32(scz, 0, (UINT*) &pExitCode->dwCode); + ExitOnFailure(hr, "Failed to parse @Code value: %ls", scz); + } + + // prepare next iteration + ReleaseNullObject(pixnNode); + } + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(scz); + + return hr; +} + +static HRESULT ParseCommandLineArgumentsFromXml( + __in IXMLDOMNode* pixnExePackage, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + LPWSTR scz = NULL; + + // Select command-line argument nodes. + hr = XmlSelectNodes(pixnExePackage, L"CommandLine", &pixnNodes); + ExitOnFailure(hr, "Failed to select command-line argument nodes."); + + // Get command-line argument node count. + hr = pixnNodes->get_length((long*) &cNodes); + ExitOnFailure(hr, "Failed to get command-line argument count."); + + if (cNodes) + { + pPackage->Exe.rgCommandLineArguments = (BURN_EXE_COMMAND_LINE_ARGUMENT*) MemAlloc(sizeof(BURN_EXE_COMMAND_LINE_ARGUMENT) * cNodes, TRUE); + ExitOnNull(pPackage->Exe.rgCommandLineArguments, hr, E_OUTOFMEMORY, "Failed to allocate memory for command-line argument structs."); + + pPackage->Exe.cCommandLineArguments = cNodes; + + // Parse command-line argument elements. + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_EXE_COMMAND_LINE_ARGUMENT* pCommandLineArgument = &pPackage->Exe.rgCommandLineArguments[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next command-line argument node."); + + // @InstallArgument + hr = XmlGetAttributeEx(pixnNode, L"InstallArgument", &pCommandLineArgument->sczInstallArgument); + ExitOnFailure(hr, "Failed to get @InstallArgument."); + + // @UninstallArgument + hr = XmlGetAttributeEx(pixnNode, L"UninstallArgument", &pCommandLineArgument->sczUninstallArgument); + ExitOnFailure(hr, "Failed to get @UninstallArgument."); + + // @RepairArgument + hr = XmlGetAttributeEx(pixnNode, L"RepairArgument", &pCommandLineArgument->sczRepairArgument); + ExitOnFailure(hr, "Failed to get @RepairArgument."); + + // @Condition + hr = XmlGetAttributeEx(pixnNode, L"Condition", &pCommandLineArgument->sczCondition); + ExitOnFailure(hr, "Failed to get @Condition."); + + // Prepare next iteration. + ReleaseNullObject(pixnNode); + } + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(scz); + + return hr; +} + +static HRESULT HandleExitCode( + __in BURN_PACKAGE* pPackage, + __in DWORD dwExitCode, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + BURN_EXE_EXIT_CODE_TYPE typeCode = BURN_EXE_EXIT_CODE_TYPE_NONE; + + for (DWORD i = 0; i < pPackage->Exe.cExitCodes; ++i) + { + BURN_EXE_EXIT_CODE* pExitCode = &pPackage->Exe.rgExitCodes[i]; + + // If this is a wildcard, use the last one we come across. + if (pExitCode->fWildcard) + { + typeCode = pExitCode->type; + } + else if (dwExitCode == pExitCode->dwCode) // If we have an exact match on the error code use that and stop looking. + { + typeCode = pExitCode->type; + break; + } + } + + // If we didn't find a matching code then treat 0 as success, the standard restarts codes as restarts + // and everything else as an error. + if (BURN_EXE_EXIT_CODE_TYPE_NONE == typeCode) + { + if (0 == dwExitCode) + { + typeCode = BURN_EXE_EXIT_CODE_TYPE_SUCCESS; + } + else if (ERROR_SUCCESS_REBOOT_REQUIRED == dwExitCode || + HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED) == static_cast(dwExitCode) || + ERROR_SUCCESS_RESTART_REQUIRED == dwExitCode || + HRESULT_FROM_WIN32(ERROR_SUCCESS_RESTART_REQUIRED) == static_cast(dwExitCode)) + { + typeCode = BURN_EXE_EXIT_CODE_TYPE_SCHEDULE_REBOOT; + } + else if (ERROR_SUCCESS_REBOOT_INITIATED == dwExitCode || + HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_INITIATED) == static_cast(dwExitCode)) + { + typeCode = BURN_EXE_EXIT_CODE_TYPE_FORCE_REBOOT; + } + else + { + typeCode = BURN_EXE_EXIT_CODE_TYPE_ERROR; + } + } + + switch (typeCode) + { + case BURN_EXE_EXIT_CODE_TYPE_SUCCESS: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; + hr = S_OK; + break; + + case BURN_EXE_EXIT_CODE_TYPE_ERROR: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; + hr = HRESULT_FROM_WIN32(dwExitCode); + if (SUCCEEDED(hr)) + { + hr = E_FAIL; + } + break; + + case BURN_EXE_EXIT_CODE_TYPE_SCHEDULE_REBOOT: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_REQUIRED; + hr = S_OK; + break; + + case BURN_EXE_EXIT_CODE_TYPE_FORCE_REBOOT: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_INITIATED; + hr = S_OK; + break; + + default: + hr = E_UNEXPECTED; + break; + } + +//LExit: + return hr; +} diff --git a/src/burn/engine/exeengine.h b/src/burn/engine/exeengine.h new file mode 100644 index 00000000..e032ea01 --- /dev/null +++ b/src/burn/engine/exeengine.h @@ -0,0 +1,50 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// function declarations + +HRESULT ExeEngineParsePackageFromXml( + __in IXMLDOMNode* pixnExePackage, + __in BURN_PACKAGE* pPackage + ); +void ExeEnginePackageUninitialize( + __in BURN_PACKAGE* pPackage + ); +HRESULT ExeEngineDetectPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_VARIABLES* pVariables + ); +HRESULT ExeEnginePlanCalculatePackage( + __in BURN_PACKAGE* pPackage + ); +HRESULT ExeEnginePlanAddPackage( + __in_opt DWORD *pdwInsertSequence, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in_opt HANDLE hCacheEvent + ); +HRESULT ExeEngineExecutePackage( + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_GENERICMESSAGEHANDLER pfnGenericExecuteProgress, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +void ExeEngineUpdateInstallRegistrationState( + __in BURN_EXECUTE_ACTION* pAction, + __in HRESULT hrExecute + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/externalengine.cpp b/src/burn/engine/externalengine.cpp new file mode 100644 index 00000000..409353e4 --- /dev/null +++ b/src/burn/engine/externalengine.cpp @@ -0,0 +1,805 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +static HRESULT CopyStringToExternal( + __in_z LPWSTR wzValue, + __in_z_opt LPWSTR wzBuffer, + __inout SIZE_T* pcchBuffer + ); + +// function definitions + +void ExternalEngineGetPackageCount( + __in BURN_ENGINE_STATE* pEngineState, + __out DWORD* pcPackages + ) +{ + *pcPackages = pEngineState->packages.cPackages; +} + +HRESULT ExternalEngineGetVariableNumeric( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __out LONGLONG* pllValue + ) +{ + HRESULT hr = S_OK; + + if (wzVariable && *wzVariable) + { + hr = VariableGetNumeric(&pEngineState->variables, wzVariable, pllValue); + } + else + { + *pllValue = 0; + hr = E_INVALIDARG; + } + + return hr; +} + +HRESULT ExternalEngineGetVariableString( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __out_ecount_opt(*pcchValue) LPWSTR wzValue, + __inout SIZE_T* pcchValue + ) +{ + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + + if (wzVariable && *wzVariable) + { + hr = VariableGetString(&pEngineState->variables, wzVariable, &sczValue); + if (SUCCEEDED(hr)) + { + hr = CopyStringToExternal(sczValue, wzValue, pcchValue); + } + } + else + { + hr = E_INVALIDARG; + } + + StrSecureZeroFreeString(sczValue); + + return hr; +} + +HRESULT ExternalEngineGetVariableVersion( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __out_ecount_opt(*pcchValue) LPWSTR wzValue, + __inout SIZE_T* pcchValue + ) +{ + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion = NULL; + + if (wzVariable && *wzVariable) + { + hr = VariableGetVersion(&pEngineState->variables, wzVariable, &pVersion); + if (SUCCEEDED(hr)) + { + hr = CopyStringToExternal(pVersion->sczVersion, wzValue, pcchValue); + } + } + else + { + hr = E_INVALIDARG; + } + + ReleaseVerutilVersion(pVersion); + + return hr; +} + +HRESULT ExternalEngineFormatString( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzIn, + __out_ecount_opt(*pcchOut) LPWSTR wzOut, + __inout SIZE_T* pcchOut + ) +{ + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + + if (wzIn && *wzIn) + { + hr = VariableFormatString(&pEngineState->variables, wzIn, &sczValue, NULL); + if (SUCCEEDED(hr)) + { + hr = CopyStringToExternal(sczValue, wzOut, pcchOut); + } + } + else + { + hr = E_INVALIDARG; + } + + StrSecureZeroFreeString(sczValue); + + return hr; +} + +HRESULT ExternalEngineEscapeString( + __in_z LPCWSTR wzIn, + __out_ecount_opt(*pcchOut) LPWSTR wzOut, + __inout SIZE_T* pcchOut + ) +{ + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + + if (wzIn && *wzIn) + { + hr = VariableEscapeString(wzIn, &sczValue); + if (SUCCEEDED(hr)) + { + hr = CopyStringToExternal(sczValue, wzOut, pcchOut); + } + } + else + { + hr = E_INVALIDARG; + } + + StrSecureZeroFreeString(sczValue); + + return hr; +} + +HRESULT ExternalEngineEvaluateCondition( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzCondition, + __out BOOL* pf + ) +{ + HRESULT hr = S_OK; + + if (wzCondition && *wzCondition) + { + hr = ConditionEvaluate(&pEngineState->variables, wzCondition, pf); + } + else + { + *pf = FALSE; + hr = E_INVALIDARG; + } + + return hr; +} + +HRESULT ExternalEngineLog( + __in REPORT_LEVEL rl, + __in_z LPCWSTR wzMessage + ) +{ + HRESULT hr = S_OK; + + hr = LogStringLine(rl, "%ls", wzMessage); + + return hr; +} + +HRESULT ExternalEngineSendEmbeddedError( + __in BURN_ENGINE_STATE* pEngineState, + __in const DWORD dwErrorCode, + __in_z LPCWSTR wzMessage, + __in const DWORD dwUIHint, + __out int* pnResult + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = *pnResult = 0; + + if (BURN_MODE_EMBEDDED != pEngineState->mode) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_STATE); + ExitOnRootFailure(hr, "BA requested to send embedded message when not in embedded mode."); + } + + hr = BuffWriteNumber(&pbData, &cbData, dwErrorCode); + ExitOnFailure(hr, "Failed to write error code to message buffer."); + + hr = BuffWriteString(&pbData, &cbData, wzMessage ? wzMessage : L""); + ExitOnFailure(hr, "Failed to write message string to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, dwUIHint); + ExitOnFailure(hr, "Failed to write UI hint to message buffer."); + + hr = PipeSendMessage(pEngineState->embeddedConnection.hPipe, BURN_EMBEDDED_MESSAGE_TYPE_ERROR, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send embedded message over pipe."); + + *pnResult = static_cast(dwResult); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +HRESULT ExternalEngineSendEmbeddedProgress( + __in BURN_ENGINE_STATE* pEngineState, + __in const DWORD dwProgressPercentage, + __in const DWORD dwOverallProgressPercentage, + __out int* pnResult + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + DWORD dwResult = *pnResult = 0; + + if (BURN_MODE_EMBEDDED != pEngineState->mode) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_STATE); + ExitOnRootFailure(hr, "BA requested to send embedded progress message when not in embedded mode."); + } + + hr = BuffWriteNumber(&pbData, &cbData, dwProgressPercentage); + ExitOnFailure(hr, "Failed to write progress percentage to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, dwOverallProgressPercentage); + ExitOnFailure(hr, "Failed to write overall progress percentage to message buffer."); + + hr = PipeSendMessage(pEngineState->embeddedConnection.hPipe, BURN_EMBEDDED_MESSAGE_TYPE_PROGRESS, pbData, cbData, NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send embedded progress message over pipe."); + + *pnResult = static_cast(dwResult); + +LExit: + ReleaseBuffer(pbData); + + return hr; +} + +HRESULT ExternalEngineSetUpdate( + __in BURN_ENGINE_STATE* pEngineState, + __in_z_opt LPCWSTR wzLocalSource, + __in_z_opt LPCWSTR wzDownloadSource, + __in const DWORD64 qwSize, + __in const BOOTSTRAPPER_UPDATE_HASH_TYPE hashType, + __in_opt const BYTE* rgbHash, + __in const DWORD cbHash + ) +{ + HRESULT hr = S_OK; + LPWSTR sczFilePath = NULL; + LPWSTR sczCommandline = NULL; + UUID guid = { }; + WCHAR wzGuid[39]; + RPC_STATUS rs = RPC_S_OK; + + ::EnterCriticalSection(&pEngineState->userExperience.csEngineActive); + hr = UserExperienceEnsureEngineInactive(&pEngineState->userExperience); + ExitOnFailure(hr, "Engine is active, cannot change engine state."); + + if ((!wzLocalSource || !*wzLocalSource) && (!wzDownloadSource || !*wzDownloadSource)) + { + UpdateUninitialize(&pEngineState->update); + } + else if (BOOTSTRAPPER_UPDATE_HASH_TYPE_NONE == hashType && (0 != cbHash || rgbHash)) + { + hr = E_INVALIDARG; + } + else if (BOOTSTRAPPER_UPDATE_HASH_TYPE_SHA512 == hashType && (SHA512_HASH_LEN != cbHash || !rgbHash)) + { + hr = E_INVALIDARG; + } + else + { + UpdateUninitialize(&pEngineState->update); + + hr = CoreRecreateCommandLine(&sczCommandline, BOOTSTRAPPER_ACTION_INSTALL, pEngineState->command.display, pEngineState->command.restart, BOOTSTRAPPER_RELATION_NONE, FALSE, pEngineState->registration.sczActiveParent, pEngineState->registration.sczAncestors, NULL, pEngineState->command.wzCommandLine); + ExitOnFailure(hr, "Failed to recreate command-line for update bundle."); + + // Bundles would fail to use the downloaded update bundle, as the running bundle would be one of the search paths. + // Here I am generating a random guid, but in the future it would be nice if the feed would provide the ID of the update. + rs = ::UuidCreate(&guid); + hr = HRESULT_FROM_RPC(rs); + ExitOnFailure(hr, "Failed to create bundle update guid."); + + if (!::StringFromGUID2(guid, wzGuid, countof(wzGuid))) + { + hr = E_OUTOFMEMORY; + ExitOnRootFailure(hr, "Failed to convert bundle update guid into string."); + } + + hr = StrAllocFormatted(&sczFilePath, L"%ls\\%ls", wzGuid, pEngineState->registration.sczExecutableName); + ExitOnFailure(hr, "Failed to build bundle update file path."); + + if (!wzLocalSource || !*wzLocalSource) + { + wzLocalSource = sczFilePath; + } + + hr = PseudoBundleInitialize(FILEMAKEVERSION(rmj, rmm, rup, rpr), &pEngineState->update.package, FALSE, pEngineState->registration.sczId, BOOTSTRAPPER_RELATION_UPDATE, BOOTSTRAPPER_PACKAGE_STATE_ABSENT, FALSE, sczFilePath, wzLocalSource, wzDownloadSource, qwSize, TRUE, sczCommandline, NULL, NULL, NULL, rgbHash, cbHash); + ExitOnFailure(hr, "Failed to set update bundle."); + + pEngineState->update.fUpdateAvailable = TRUE; + } + +LExit: + ::LeaveCriticalSection(&pEngineState->userExperience.csEngineActive); + + ReleaseStr(sczCommandline); + ReleaseStr(sczFilePath); + + return hr; +} + +HRESULT ExternalEngineSetLocalSource( + __in BURN_ENGINE_STATE* pEngineState, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in_z LPCWSTR wzPath + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER* pContainer = NULL; + BURN_PAYLOAD* pPayload = NULL; + + ::EnterCriticalSection(&pEngineState->userExperience.csEngineActive); + hr = UserExperienceEnsureEngineInactive(&pEngineState->userExperience); + ExitOnFailure(hr, "Engine is active, cannot change engine state."); + + if (!wzPath || !*wzPath) + { + hr = E_INVALIDARG; + } + else if (wzPayloadId && *wzPayloadId) + { + hr = PayloadFindById(&pEngineState->payloads, wzPayloadId, &pPayload); + ExitOnFailure(hr, "BA requested unknown payload with id: %ls", wzPayloadId); + + hr = StrAllocString(&pPayload->sczSourcePath, wzPath, 0); + ExitOnFailure(hr, "Failed to set source path for payload."); + } + else if (wzPackageOrContainerId && *wzPackageOrContainerId) + { + hr = ContainerFindById(&pEngineState->containers, wzPackageOrContainerId, &pContainer); + ExitOnFailure(hr, "BA requested unknown container with id: %ls", wzPackageOrContainerId); + + hr = StrAllocString(&pContainer->sczSourcePath, wzPath, 0); + ExitOnFailure(hr, "Failed to set source path for container."); + } + else + { + hr = E_INVALIDARG; + } + +LExit: + ::LeaveCriticalSection(&pEngineState->userExperience.csEngineActive); + + return hr; +} + +HRESULT ExternalEngineSetDownloadSource( + __in BURN_ENGINE_STATE* pEngineState, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in_z_opt LPCWSTR wzUrl, + __in_z_opt LPCWSTR wzUser, + __in_z_opt LPCWSTR wzPassword + ) +{ + HRESULT hr = S_OK; + BURN_CONTAINER* pContainer = NULL; + BURN_PAYLOAD* pPayload = NULL; + DOWNLOAD_SOURCE* pDownloadSource = NULL; + + ::EnterCriticalSection(&pEngineState->userExperience.csEngineActive); + hr = UserExperienceEnsureEngineInactive(&pEngineState->userExperience); + ExitOnFailure(hr, "Engine is active, cannot change engine state."); + + if (wzPayloadId && *wzPayloadId) + { + hr = PayloadFindById(&pEngineState->payloads, wzPayloadId, &pPayload); + ExitOnFailure(hr, "BA requested unknown payload with id: %ls", wzPayloadId); + + pDownloadSource = &pPayload->downloadSource; + } + else if (wzPackageOrContainerId && *wzPackageOrContainerId) + { + hr = ContainerFindById(&pEngineState->containers, wzPackageOrContainerId, &pContainer); + ExitOnFailure(hr, "BA requested unknown container with id: %ls", wzPackageOrContainerId); + + pDownloadSource = &pContainer->downloadSource; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "BA did not provide container or payload id."); + } + + if (wzUrl && *wzUrl) + { + hr = StrAllocString(&pDownloadSource->sczUrl, wzUrl, 0); + ExitOnFailure(hr, "Failed to set download URL."); + + if (wzUser && *wzUser) + { + hr = StrAllocString(&pDownloadSource->sczUser, wzUser, 0); + ExitOnFailure(hr, "Failed to set download user."); + + if (wzPassword && *wzPassword) + { + hr = StrAllocString(&pDownloadSource->sczPassword, wzPassword, 0); + ExitOnFailure(hr, "Failed to set download password."); + } + else // no password. + { + ReleaseNullStr(pDownloadSource->sczPassword); + } + } + else // no user means no password either. + { + ReleaseNullStr(pDownloadSource->sczUser); + ReleaseNullStr(pDownloadSource->sczPassword); + } + } + else // no URL provided means clear out the whole download source. + { + ReleaseNullStr(pDownloadSource->sczUrl); + ReleaseNullStr(pDownloadSource->sczUser); + ReleaseNullStr(pDownloadSource->sczPassword); + } + +LExit: + ::LeaveCriticalSection(&pEngineState->userExperience.csEngineActive); + + return hr; +} + +HRESULT ExternalEngineSetVariableNumeric( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __in const LONGLONG llValue + ) +{ + HRESULT hr = S_OK; + + if (wzVariable && *wzVariable) + { + hr = VariableSetNumeric(&pEngineState->variables, wzVariable, llValue, FALSE); + ExitOnFailure(hr, "Failed to set numeric variable."); + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "SetVariableNumeric did not provide variable name."); + } + +LExit: + return hr; +} + +HRESULT ExternalEngineSetVariableString( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __in_z_opt LPCWSTR wzValue, + __in const BOOL fFormatted + ) +{ + HRESULT hr = S_OK; + + if (wzVariable && *wzVariable) + { + hr = VariableSetString(&pEngineState->variables, wzVariable, wzValue, FALSE, fFormatted); + ExitOnFailure(hr, "Failed to set string variable."); + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "SetVariableString did not provide variable name."); + } + +LExit: + return hr; +} + +HRESULT ExternalEngineSetVariableVersion( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __in_z_opt LPCWSTR wzValue + ) +{ + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion = NULL; + + if (wzVariable && *wzVariable) + { + if (wzValue) + { + hr = VerParseVersion(wzValue, 0, FALSE, &pVersion); + ExitOnFailure(hr, "Failed to parse new version value."); + } + + hr = VariableSetVersion(&pEngineState->variables, wzVariable, pVersion, FALSE); + ExitOnFailure(hr, "Failed to set version variable."); + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "SetVariableVersion did not provide variable name."); + } + +LExit: + ReleaseVerutilVersion(pVersion); + + return hr; +} + +void ExternalEngineCloseSplashScreen( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + // If the splash screen is still around, close it. + if (::IsWindow(pEngineState->command.hwndSplashScreen)) + { + ::PostMessageW(pEngineState->command.hwndSplashScreen, WM_CLOSE, 0, 0); + } +} + +HRESULT ExternalEngineCompareVersions( + __in_z LPCWSTR wzVersion1, + __in_z LPCWSTR wzVersion2, + __out int* pnResult + ) +{ + HRESULT hr = S_OK; + + hr = VerCompareStringVersions(wzVersion1, wzVersion2, FALSE, pnResult); + + return hr; +} + +HRESULT ExternalEngineDetect( + __in const DWORD dwThreadId, + __in_opt const HWND hwndParent + ) +{ + HRESULT hr = S_OK; + + if (!::PostThreadMessageW(dwThreadId, WM_BURN_DETECT, 0, reinterpret_cast(hwndParent))) + { + ExitWithLastError(hr, "Failed to post detect message."); + } + +LExit: + return hr; +} + +HRESULT ExternalEnginePlan( + __in const DWORD dwThreadId, + __in const BOOTSTRAPPER_ACTION action + ) +{ + HRESULT hr = S_OK; + + if (BOOTSTRAPPER_ACTION_LAYOUT > action || BOOTSTRAPPER_ACTION_UPDATE_REPLACE_EMBEDDED < action) + { + ExitOnRootFailure(hr = E_INVALIDARG, "BA passed invalid action to Plan: %u.", action); + } + + if (!::PostThreadMessageW(dwThreadId, WM_BURN_PLAN, 0, action)) + { + ExitWithLastError(hr, "Failed to post plan message."); + } + +LExit: + return hr; +} + +HRESULT ExternalEngineElevate( + __in BURN_ENGINE_STATE* pEngineState, + __in const DWORD dwThreadId, + __in_opt const HWND hwndParent + ) +{ + HRESULT hr = S_OK; + + if (INVALID_HANDLE_VALUE != pEngineState->companionConnection.hPipe) + { + hr = HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED); + } + else if (!::PostThreadMessageW(dwThreadId, WM_BURN_ELEVATE, 0, reinterpret_cast(hwndParent))) + { + ExitWithLastError(hr, "Failed to post elevate message."); + } + +LExit: + return hr; +} + +HRESULT ExternalEngineApply( + __in const DWORD dwThreadId, + __in_opt const HWND hwndParent + ) +{ + HRESULT hr = S_OK; + + ExitOnNull(hwndParent, hr, E_INVALIDARG, "BA passed NULL hwndParent to Apply."); + if (!::IsWindow(hwndParent)) + { + ExitOnRootFailure(hr = E_INVALIDARG, "BA passed invalid hwndParent to Apply."); + } + + if (!::PostThreadMessageW(dwThreadId, WM_BURN_APPLY, 0, reinterpret_cast(hwndParent))) + { + ExitWithLastError(hr, "Failed to post apply message."); + } + +LExit: + return hr; +} + +HRESULT ExternalEngineQuit( + __in const DWORD dwThreadId, + __in const DWORD dwExitCode + ) +{ + HRESULT hr = S_OK; + + if (!::PostThreadMessageW(dwThreadId, WM_BURN_QUIT, static_cast(dwExitCode), 0)) + { + ExitWithLastError(hr, "Failed to post shutdown message."); + } + +LExit: + return hr; +} + +HRESULT ExternalEngineLaunchApprovedExe( + __in BURN_ENGINE_STATE* pEngineState, + __in const DWORD dwThreadId, + __in_opt const HWND hwndParent, + __in_z LPCWSTR wzApprovedExeForElevationId, + __in_z_opt LPCWSTR wzArguments, + __in const DWORD dwWaitForInputIdleTimeout + ) +{ + HRESULT hr = S_OK; + BURN_APPROVED_EXE* pApprovedExe = NULL; + BOOL fLeaveCriticalSection = FALSE; + BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe = NULL; + + pLaunchApprovedExe = (BURN_LAUNCH_APPROVED_EXE*)MemAlloc(sizeof(BURN_LAUNCH_APPROVED_EXE), TRUE); + ExitOnNull(pLaunchApprovedExe, hr, E_OUTOFMEMORY, "Failed to alloc BURN_LAUNCH_APPROVED_EXE"); + + ::EnterCriticalSection(&pEngineState->userExperience.csEngineActive); + fLeaveCriticalSection = TRUE; + hr = UserExperienceEnsureEngineInactive(&pEngineState->userExperience); + ExitOnFailure(hr, "Engine is active, cannot change engine state."); + + if (!wzApprovedExeForElevationId || !*wzApprovedExeForElevationId) + { + ExitFunction1(hr = E_INVALIDARG); + } + + hr = ApprovedExesFindById(&pEngineState->approvedExes, wzApprovedExeForElevationId, &pApprovedExe); + ExitOnFailure(hr, "BA requested unknown approved exe with id: %ls", wzApprovedExeForElevationId); + + hr = StrAllocString(&pLaunchApprovedExe->sczId, wzApprovedExeForElevationId, NULL); + ExitOnFailure(hr, "Failed to copy the id."); + + if (wzArguments) + { + hr = StrAllocString(&pLaunchApprovedExe->sczArguments, wzArguments, NULL); + ExitOnFailure(hr, "Failed to copy the arguments."); + } + + pLaunchApprovedExe->dwWaitForInputIdleTimeout = dwWaitForInputIdleTimeout; + + pLaunchApprovedExe->hwndParent = hwndParent; + + if (!::PostThreadMessageW(dwThreadId, WM_BURN_LAUNCH_APPROVED_EXE, 0, reinterpret_cast(pLaunchApprovedExe))) + { + ExitWithLastError(hr, "Failed to post launch approved exe message."); + } + +LExit: + if (fLeaveCriticalSection) + { + ::LeaveCriticalSection(&pEngineState->userExperience.csEngineActive); + } + + if (FAILED(hr)) + { + ApprovedExesUninitializeLaunch(pLaunchApprovedExe); + } + + return hr; +} + +HRESULT ExternalEngineSetUpdateSource( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzUrl + ) +{ + HRESULT hr = S_OK; + BOOL fLeaveCriticalSection = FALSE; + + ::EnterCriticalSection(&pEngineState->userExperience.csEngineActive); + fLeaveCriticalSection = TRUE; + hr = UserExperienceEnsureEngineInactive(&pEngineState->userExperience); + ExitOnFailure(hr, "Engine is active, cannot change engine state."); + + if (wzUrl && *wzUrl) + { + hr = StrAllocString(&pEngineState->update.sczUpdateSource, wzUrl, 0); + ExitOnFailure(hr, "Failed to set feed download URL."); + } + else // no URL provided means clear out the whole download source. + { + ReleaseNullStr(pEngineState->update.sczUpdateSource); + } + +LExit: + if (fLeaveCriticalSection) + { + ::LeaveCriticalSection(&pEngineState->userExperience.csEngineActive); + } + + return hr; +} + +// TODO: callers need to provide the original size (at the time of first public release) of the struct instead of the current size. +HRESULT WINAPI ExternalEngineValidateMessageParameter( + __in_opt const LPVOID pv, + __in SIZE_T cbSizeOffset, + __in DWORD dwMinimumSize + ) +{ + HRESULT hr = S_OK; + + if (!pv) + { + ExitFunction1(hr = E_INVALIDARG); + } + + DWORD cbSize = *(DWORD*)((BYTE*)pv + cbSizeOffset); + if (dwMinimumSize < cbSize) + { + ExitFunction1(hr = E_INVALIDARG); + } + +LExit: + return hr; +} + +static HRESULT CopyStringToExternal( + __in_z LPWSTR wzValue, + __in_z_opt LPWSTR wzBuffer, + __inout SIZE_T* pcchBuffer + ) +{ + HRESULT hr = S_OK; + BOOL fTooSmall = !wzBuffer; + + if (!fTooSmall) + { + hr = ::StringCchCopyExW(wzBuffer, *pcchBuffer, wzValue, NULL, NULL, STRSAFE_FILL_BEHIND_NULL); + if (STRSAFE_E_INSUFFICIENT_BUFFER == hr) + { + fTooSmall = TRUE; + } + } + + if (fTooSmall) + { + hr = ::StringCchLengthW(wzValue, STRSAFE_MAX_LENGTH, reinterpret_cast(pcchBuffer)); + if (SUCCEEDED(hr)) + { + hr = E_MOREDATA; + *pcchBuffer += 1; // null terminator. + } + } + + return hr; +} diff --git a/src/burn/engine/externalengine.h b/src/burn/engine/externalengine.h new file mode 100644 index 00000000..2903615d --- /dev/null +++ b/src/burn/engine/externalengine.h @@ -0,0 +1,181 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#define ValidateMessageParameter(x, pv, type) { x = ExternalEngineValidateMessageParameter(pv, offsetof(type, cbSize), sizeof(type)); if (FAILED(x)) { goto LExit; }} +#define ValidateMessageArgs(x, pv, type, identifier) ValidateMessageParameter(x, pv, type); const type* identifier = reinterpret_cast(pv); UNREFERENCED_PARAMETER(identifier) +#define ValidateMessageResults(x, pv, type, identifier) ValidateMessageParameter(x, pv, type); type* identifier = reinterpret_cast(pv); UNREFERENCED_PARAMETER(identifier) + + +#if defined(__cplusplus) +extern "C" { +#endif + +void ExternalEngineGetPackageCount( + __in BURN_ENGINE_STATE* pEngineState, + __out DWORD* pcPackages + ); + +HRESULT ExternalEngineGetVariableNumeric( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __out LONGLONG* pllValue + ); + +HRESULT ExternalEngineGetVariableString( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __out_ecount_opt(*pcchValue) LPWSTR wzValue, + __inout SIZE_T* pcchValue + ); + +HRESULT ExternalEngineGetVariableVersion( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __out_ecount_opt(*pcchValue) LPWSTR wzValue, + __inout SIZE_T* pcchValue + ); + +HRESULT ExternalEngineFormatString( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzIn, + __out_ecount_opt(*pcchOut) LPWSTR wzOut, + __inout SIZE_T* pcchOut + ); + +HRESULT ExternalEngineEscapeString( + __in_z LPCWSTR wzIn, + __out_ecount_opt(*pcchOut) LPWSTR wzOut, + __inout SIZE_T* pcchOut + ); + +HRESULT ExternalEngineEvaluateCondition( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzCondition, + __out BOOL* pf + ); + +HRESULT ExternalEngineLog( + __in REPORT_LEVEL rl, + __in_z LPCWSTR wzMessage + ); + +HRESULT ExternalEngineSendEmbeddedError( + __in BURN_ENGINE_STATE* pEngineState, + __in const DWORD dwErrorCode, + __in_z LPCWSTR wzMessage, + __in const DWORD dwUIHint, + __out int* pnResult + ); + +HRESULT ExternalEngineSendEmbeddedProgress( + __in BURN_ENGINE_STATE* pEngineState, + __in const DWORD dwProgressPercentage, + __in const DWORD dwOverallProgressPercentage, + __out int* pnResult + ); + +HRESULT ExternalEngineSetUpdate( + __in BURN_ENGINE_STATE* pEngineState, + __in_z_opt LPCWSTR wzLocalSource, + __in_z_opt LPCWSTR wzDownloadSource, + __in const DWORD64 qwSize, + __in const BOOTSTRAPPER_UPDATE_HASH_TYPE hashType, + __in_opt const BYTE* rgbHash, + __in const DWORD cbHash + ); + +HRESULT ExternalEngineSetLocalSource( + __in BURN_ENGINE_STATE* pEngineState, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in_z LPCWSTR wzPath + ); + +HRESULT ExternalEngineSetDownloadSource( + __in BURN_ENGINE_STATE* pEngineState, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in_z_opt LPCWSTR wzUrl, + __in_z_opt LPCWSTR wzUser, + __in_z_opt LPCWSTR wzPassword + ); + +HRESULT ExternalEngineSetVariableNumeric( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __in const LONGLONG llValue + ); + +HRESULT ExternalEngineSetVariableString( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __in_z_opt LPCWSTR wzValue, + __in const BOOL fFormatted + ); + +HRESULT ExternalEngineSetVariableVersion( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzVariable, + __in_z_opt LPCWSTR wzValue + ); + +void ExternalEngineCloseSplashScreen( + __in BURN_ENGINE_STATE* pEngineState + ); + +HRESULT ExternalEngineCompareVersions( + __in_z LPCWSTR wzVersion1, + __in_z LPCWSTR wzVersion2, + __out int* pnResult + ); + +HRESULT ExternalEngineDetect( + __in const DWORD dwThreadId, + __in_opt const HWND hwndParent + ); + +HRESULT ExternalEnginePlan( + __in const DWORD dwThreadId, + __in const BOOTSTRAPPER_ACTION action + ); + +HRESULT ExternalEngineElevate( + __in BURN_ENGINE_STATE* pEngineState, + __in const DWORD dwThreadId, + __in_opt const HWND hwndParent + ); + +HRESULT ExternalEngineApply( + __in const DWORD dwThreadId, + __in_opt const HWND hwndParent + ); + +HRESULT ExternalEngineQuit( + __in const DWORD dwThreadId, + __in const DWORD dwExitCode + ); + +HRESULT ExternalEngineLaunchApprovedExe( + __in BURN_ENGINE_STATE* pEngineState, + __in const DWORD dwThreadId, + __in_opt const HWND hwndParent, + __in_z LPCWSTR wzApprovedExeForElevationId, + __in_z_opt LPCWSTR wzArguments, + __in const DWORD dwWaitForInputIdleTimeout + ); + +HRESULT ExternalEngineSetUpdateSource( + __in BURN_ENGINE_STATE* pEngineState, + __in_z LPCWSTR wzUrl + ); + +HRESULT WINAPI ExternalEngineValidateMessageParameter( + __in_opt const LPVOID pv, + __in SIZE_T cbSizeOffset, + __in DWORD dwMinimumSize + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/inc/burnsources.h b/src/burn/engine/inc/burnsources.h new file mode 100644 index 00000000..bff79ed5 --- /dev/null +++ b/src/burn/engine/inc/burnsources.h @@ -0,0 +1,4 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#define DUTIL_SOURCE_DEFAULT DUTIL_SOURCE_EXTERNAL diff --git a/src/burn/engine/inc/engine.h b/src/burn/engine/inc/engine.h new file mode 100644 index 00000000..808bb91a --- /dev/null +++ b/src/burn/engine/inc/engine.h @@ -0,0 +1,27 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// function declarations + +BOOL EngineInCleanRoom( + __in_z_opt LPCWSTR wzCommandLine + ); + +HRESULT EngineRun( + __in HINSTANCE hInstance, + __in HANDLE hEngineFile, + __in_z_opt LPCWSTR wzCommandLine, + __in int nCmdShow, + __out DWORD* pdwExitCode + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/logging.cpp b/src/burn/engine/logging.cpp new file mode 100644 index 00000000..065ef907 --- /dev/null +++ b/src/burn/engine/logging.cpp @@ -0,0 +1,754 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +static DWORD vdwPackageSequence = 0; +static const DWORD LOG_OPEN_RETRY_COUNT = 3; +static const DWORD LOG_OPEN_RETRY_WAIT = 2000; +static CONST LPWSTR LOG_FAILED_EVENT_LOG_MESSAGE = L"Burn Engine Fatal Error: failed to open log file."; + +// structs + + + +// internal function declarations + +static void CheckLoggingPolicy( + __out DWORD *pdwAttributes + ); +static HRESULT GetNonSessionSpecificTempFolder( + __deref_out_z LPWSTR* psczNonSessionTempFolder + ); + + +// function definitions + +extern "C" HRESULT LoggingOpen( + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in_z LPCWSTR wzBundleName + ) +{ + HRESULT hr = S_OK; + LPWSTR sczLoggingBaseFolder = NULL; + LPWSTR sczPrefixFormatted = NULL; + + // Check if the logging policy is set and configure the logging appropriately. + CheckLoggingPolicy(&pLog->dwAttributes); + + if (pLog->dwAttributes & BURN_LOGGING_ATTRIBUTE_VERBOSE || pLog->dwAttributes & BURN_LOGGING_ATTRIBUTE_EXTRADEBUG) + { + if (pLog->dwAttributes & BURN_LOGGING_ATTRIBUTE_EXTRADEBUG) + { + LogSetLevel(REPORT_DEBUG, FALSE); + } + else if (pLog->dwAttributes & BURN_LOGGING_ATTRIBUTE_VERBOSE) + { + LogSetLevel(REPORT_VERBOSE, FALSE); + } + + if ((!pLog->sczPath || !*pLog->sczPath) && (!pLog->sczPrefix || !*pLog->sczPrefix)) + { + PathCreateTimeBasedTempFile(NULL, L"Setup", NULL, L"log", &pLog->sczPath, NULL); + } + } + + // Open the log approriately. + if (pLog->sczPath && *pLog->sczPath) + { + DWORD cRetry = 0; + + hr = DirGetCurrent(&sczLoggingBaseFolder); + ExitOnFailure(hr, "Failed to get current directory."); + + // Try pretty hard to open the log file when appending. + do + { + if (0 < cRetry) + { + ::Sleep(LOG_OPEN_RETRY_WAIT); + } + + hr = LogOpen(sczLoggingBaseFolder, pLog->sczPath, NULL, NULL, pLog->dwAttributes & BURN_LOGGING_ATTRIBUTE_APPEND, FALSE, &pLog->sczPath); + if (pLog->dwAttributes & BURN_LOGGING_ATTRIBUTE_APPEND && HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION) == hr) + { + ++cRetry; + } + } while (cRetry > 0 && cRetry <= LOG_OPEN_RETRY_COUNT); + + if (FAILED(hr)) + { + // Log is not open, so note that. + LogDisable(); + pLog->state = BURN_LOGGING_STATE_DISABLED; + + if (pLog->dwAttributes & BURN_LOGGING_ATTRIBUTE_APPEND) + { + // If appending, ignore the failure and continue. + hr = S_OK; + } + else // specifically tried to create a log file so show an error if appropriate and bail. + { + HRESULT hrOriginal = hr; + + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_LOG_FAILURE); + SplashScreenDisplayError(display, wzBundleName, hr); + + ExitOnFailure(hrOriginal, "Failed to open log: %ls", pLog->sczPath); + } + } + else + { + pLog->state = BURN_LOGGING_STATE_OPEN; + } + } + else + { + if (pLog->sczPrefix && *pLog->sczPrefix) + { + hr = VariableFormatString(pVariables, pLog->sczPrefix, &sczPrefixFormatted, NULL); + } + + if (sczPrefixFormatted && *sczPrefixFormatted) + { + LPCWSTR wzPrefix = sczPrefixFormatted; + + // Best effort to open default logging. + if (PathIsAbsolute(sczPrefixFormatted)) + { + hr = PathGetDirectory(sczPrefixFormatted, &sczLoggingBaseFolder); + ExitOnFailure(hr, "Failed to get parent directory from '%ls'.", sczPrefixFormatted); + + wzPrefix = PathFile(sczPrefixFormatted); + } + else + { + hr = GetNonSessionSpecificTempFolder(&sczLoggingBaseFolder); + ExitOnFailure(hr, "Failed to get non-session specific TEMP folder."); + } + + hr = LogOpen(sczLoggingBaseFolder, wzPrefix, NULL, pLog->sczExtension, FALSE, FALSE, &pLog->sczPath); + if (FAILED(hr)) + { + LogDisable(); + pLog->state = BURN_LOGGING_STATE_DISABLED; + + hr = S_OK; + } + else + { + pLog->state = BURN_LOGGING_STATE_OPEN; + } + } + else // no logging enabled. + { + LogDisable(); + pLog->state = BURN_LOGGING_STATE_DISABLED; + } + } + + // If the log was opened, write the header info and update the prefix and extension to match + // the log name so future logs are opened with the same pattern. + if (BURN_LOGGING_STATE_OPEN == pLog->state) + { + LPCWSTR wzExtension = PathExtension(pLog->sczPath); + if (wzExtension && *wzExtension) + { + hr = StrAllocString(&pLog->sczPrefix, pLog->sczPath, wzExtension - pLog->sczPath); + ExitOnFailure(hr, "Failed to copy log path to prefix."); + + hr = StrAllocString(&pLog->sczExtension, wzExtension + 1, 0); + ExitOnFailure(hr, "Failed to copy log extension to extension."); + } + else + { + hr = StrAllocString(&pLog->sczPrefix, pLog->sczPath, 0); + ExitOnFailure(hr, "Failed to copy full log path to prefix."); + } + + if (pLog->sczPathVariable && *pLog->sczPathVariable) + { + VariableSetString(pVariables, pLog->sczPathVariable, pLog->sczPath, FALSE, FALSE); // Ignore failure. + } + } + +LExit: + ReleaseStr(sczLoggingBaseFolder); + StrSecureZeroFreeString(sczPrefixFormatted); + + return hr; +} + +extern "C" void LoggingOpenFailed() +{ + HRESULT hr = S_OK; + HANDLE hEventLog = NULL; + LPCWSTR* lpStrings = const_cast(&LOG_FAILED_EVENT_LOG_MESSAGE); + WORD wNumStrings = 1; + + hr = LogOpen(NULL, L"Setup", L"_Failed", L"txt", FALSE, FALSE, NULL); + if (SUCCEEDED(hr)) + { + ExitFunction(); + } + + // If opening the "failure" log failed, then attempt to record that in the Application event log. + hEventLog = ::OpenEventLogW(NULL, L"Application"); + ExitOnNullWithLastError(hEventLog, hr, "Failed to open Application event log"); + + hr = ::ReportEventW(hEventLog, EVENTLOG_ERROR_TYPE, 1, 1, NULL, wNumStrings, 0, lpStrings, NULL); + ExitOnNullWithLastError(hEventLog, hr, "Failed to write event log entry"); + +LExit: + if (hEventLog) + { + ::CloseEventLog(hEventLog); + } +} + +extern "C" void LoggingIncrementPackageSequence() +{ + ++vdwPackageSequence; +} + +extern "C" HRESULT LoggingSetPackageVariable( + __in BURN_PACKAGE* pPackage, + __in_z_opt LPCWSTR wzSuffix, + __in BOOL fRollback, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __out_opt LPWSTR* psczLogPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczLogPath = NULL; + + // Make sure that no package log files are created when logging has been disabled via Log element. + if (BURN_LOGGING_STATE_DISABLED == pLog->state) + { + if (psczLogPath) + { + *psczLogPath = NULL; + } + + ExitFunction(); + } + + if ((!fRollback && pPackage->sczLogPathVariable && *pPackage->sczLogPathVariable) || + (fRollback && pPackage->sczRollbackLogPathVariable && *pPackage->sczRollbackLogPathVariable)) + { + hr = StrAllocFormatted(&sczLogPath, L"%ls%hs%ls_%03u_%ls%ls.%ls", pLog->sczPrefix, wzSuffix && *wzSuffix ? "_" : "", wzSuffix && *wzSuffix ? wzSuffix : L"", vdwPackageSequence, pPackage->sczId, fRollback ? L"_rollback" : L"", pLog->sczExtension); + ExitOnFailure(hr, "Failed to allocate path for package log."); + + hr = VariableSetString(pVariables, fRollback ? pPackage->sczRollbackLogPathVariable : pPackage->sczLogPathVariable, sczLogPath, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set log path into variable."); + + if (psczLogPath) + { + hr = StrAllocString(psczLogPath, sczLogPath, 0); + ExitOnFailure(hr, "Failed to copy package log path."); + } + } + +LExit: + ReleaseStr(sczLogPath); + + return hr; +} + +extern "C" LPCSTR LoggingBurnActionToString( + __in BOOTSTRAPPER_ACTION action + ) +{ + switch (action) + { + case BOOTSTRAPPER_ACTION_UNKNOWN: + return "Unknown"; + case BOOTSTRAPPER_ACTION_HELP: + return "Help"; + case BOOTSTRAPPER_ACTION_LAYOUT: + return "Layout"; + case BOOTSTRAPPER_ACTION_CACHE: + return "Cache"; + case BOOTSTRAPPER_ACTION_UNINSTALL: + return "Uninstall"; + case BOOTSTRAPPER_ACTION_INSTALL: + return "Install"; + case BOOTSTRAPPER_ACTION_MODIFY: + return "Modify"; + case BOOTSTRAPPER_ACTION_REPAIR: + return "Repair"; + case BOOTSTRAPPER_ACTION_UPDATE_REPLACE: + return "UpdateReplace"; + case BOOTSTRAPPER_ACTION_UPDATE_REPLACE_EMBEDDED: + return "UpdateReplaceEmbedded"; + default: + return "Invalid"; + } +} + +LPCSTR LoggingBurnMessageToString( + __in UINT message + ) +{ + switch (message) + { + case WM_BURN_APPLY: + return "Apply"; + case WM_BURN_DETECT: + return "Detect"; + case WM_BURN_ELEVATE: + return "Elevate"; + case WM_BURN_LAUNCH_APPROVED_EXE: + return "LaunchApprovedExe"; + case WM_BURN_PLAN: + return "Plan"; + case WM_BURN_QUIT: + return "Quit"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingActionStateToString( + __in BOOTSTRAPPER_ACTION_STATE actionState + ) +{ + switch (actionState) + { + case BOOTSTRAPPER_ACTION_STATE_NONE: + return "None"; + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + return "Uninstall"; + case BOOTSTRAPPER_ACTION_STATE_INSTALL: + return "Install"; + case BOOTSTRAPPER_ACTION_STATE_MODIFY: + return "Modify"; + case BOOTSTRAPPER_ACTION_STATE_MEND: + return "Mend"; + case BOOTSTRAPPER_ACTION_STATE_REPAIR: + return "Repair"; + case BOOTSTRAPPER_ACTION_STATE_MINOR_UPGRADE: + return "MinorUpgrade"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingDependencyActionToString( + BURN_DEPENDENCY_ACTION action + ) +{ + switch (action) + { + case BURN_DEPENDENCY_ACTION_NONE: + return "None"; + case BURN_DEPENDENCY_ACTION_REGISTER: + return "Register"; + case BURN_DEPENDENCY_ACTION_UNREGISTER: + return "Unregister"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingBoolToString( + __in BOOL f + ) +{ + if (f) + { + return "Yes"; + } + + return "No"; +} + +extern "C" LPCSTR LoggingTrueFalseToString( + __in BOOL f + ) +{ + if (f) + { + return "true"; + } + + return "false"; +} + +extern "C" LPCSTR LoggingPackageStateToString( + __in BOOTSTRAPPER_PACKAGE_STATE packageState + ) +{ + switch (packageState) + { + case BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN: + return "Unknown"; + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: + return "Obsolete"; + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + return "Absent"; + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + return "Present"; + case BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED: + return "Superseded"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingPackageRegistrationStateToString( + __in BOOL fCanAffectRegistration, + __in BURN_PACKAGE_REGISTRATION_STATE registrationState + ) +{ + if (!fCanAffectRegistration) + { + return "(permanent)"; + } + + switch (registrationState) + { + case BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN: + return "Unknown"; + case BURN_PACKAGE_REGISTRATION_STATE_IGNORED: + return "Ignored"; + case BURN_PACKAGE_REGISTRATION_STATE_ABSENT: + return "Absent"; + case BURN_PACKAGE_REGISTRATION_STATE_PRESENT: + return "Present"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingMsiFeatureStateToString( + __in BOOTSTRAPPER_FEATURE_STATE featureState + ) +{ + switch (featureState) + { + case BOOTSTRAPPER_FEATURE_STATE_UNKNOWN: + return "Unknown"; + case BOOTSTRAPPER_FEATURE_STATE_ABSENT: + return "Absent"; + case BOOTSTRAPPER_FEATURE_STATE_ADVERTISED: + return "Advertised"; + case BOOTSTRAPPER_FEATURE_STATE_LOCAL: + return "Local"; + case BOOTSTRAPPER_FEATURE_STATE_SOURCE: + return "Source"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingMsiFeatureActionToString( + __in BOOTSTRAPPER_FEATURE_ACTION featureAction + ) +{ + switch (featureAction) + { + case BOOTSTRAPPER_FEATURE_ACTION_NONE: + return "None"; + case BOOTSTRAPPER_FEATURE_ACTION_ADDLOCAL: + return "AddLocal"; + case BOOTSTRAPPER_FEATURE_ACTION_ADDSOURCE: + return "AddSource"; + case BOOTSTRAPPER_FEATURE_ACTION_ADDDEFAULT: + return "AddDefault"; + case BOOTSTRAPPER_FEATURE_ACTION_REINSTALL: + return "Reinstall"; + case BOOTSTRAPPER_FEATURE_ACTION_ADVERTISE: + return "Advertise"; + case BOOTSTRAPPER_FEATURE_ACTION_REMOVE: + return "Remove"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingMsiInstallContext( + __in MSIINSTALLCONTEXT context + ) +{ + switch (context) + { + case MSIINSTALLCONTEXT_ALL: + return "All"; + case MSIINSTALLCONTEXT_ALLUSERMANAGED: + return "AllUserManaged"; + case MSIINSTALLCONTEXT_MACHINE: + return "Machine"; + case MSIINSTALLCONTEXT_NONE: + return "None"; + case MSIINSTALLCONTEXT_USERMANAGED: + return "UserManaged"; + case MSIINSTALLCONTEXT_USERUNMANAGED: + return "UserUnmanaged"; + default: + return "Invalid"; + } +} + +extern "C" LPCWSTR LoggingBurnMsiPropertyToString( + __in BURN_MSI_PROPERTY burnMsiProperty + ) +{ + switch (burnMsiProperty) + { + case BURN_MSI_PROPERTY_INSTALL: + return BURNMSIINSTALL_PROPERTY_NAME; + case BURN_MSI_PROPERTY_MODIFY: + return BURNMSIMODIFY_PROPERTY_NAME; + case BURN_MSI_PROPERTY_NONE: + return L"(none)"; + case BURN_MSI_PROPERTY_REPAIR: + return BURNMSIREPAIR_PROPERTY_NAME; + case BURN_MSI_PROPERTY_UNINSTALL: + return BURNMSIUNINSTALL_PROPERTY_NAME; + default: + return L"Invalid"; + } +} + +extern "C" LPCSTR LoggingMspTargetActionToString( + __in BOOTSTRAPPER_ACTION_STATE action, + __in BURN_PATCH_SKIP_STATE skipState + ) +{ + switch (skipState) + { + case BURN_PATCH_SKIP_STATE_NONE: + return LoggingActionStateToString(action); + case BURN_PATCH_SKIP_STATE_TARGET_UNINSTALL: + return "Skipped (target uninstall)"; + case BURN_PATCH_SKIP_STATE_SLIPSTREAM: + return "Skipped (slipstream)"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingPerMachineToString( + __in BOOL fPerMachine + ) +{ + if (fPerMachine) + { + return "PerMachine"; + } + + return "PerUser"; +} + +extern "C" LPCSTR LoggingRestartToString( + __in BOOTSTRAPPER_APPLY_RESTART restart + ) +{ + switch (restart) + { + case BOOTSTRAPPER_APPLY_RESTART_NONE: + return "None"; + case BOOTSTRAPPER_APPLY_RESTART_REQUIRED: + return "Required"; + case BOOTSTRAPPER_APPLY_RESTART_INITIATED: + return "Initiated"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingResumeModeToString( + __in BURN_RESUME_MODE resumeMode + ) +{ + switch (resumeMode) + { + case BURN_RESUME_MODE_NONE: + return "None"; + case BURN_RESUME_MODE_ACTIVE: + return "Active"; + case BURN_RESUME_MODE_SUSPEND: + return "Suspend"; + case BURN_RESUME_MODE_ARP: + return "ARP"; + case BURN_RESUME_MODE_REBOOT_PENDING: + return "Reboot Pending"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingRelationTypeToString( + __in BOOTSTRAPPER_RELATION_TYPE type + ) +{ + switch (type) + { + case BOOTSTRAPPER_RELATION_NONE: + return "None"; + case BOOTSTRAPPER_RELATION_DETECT: + return "Detect"; + case BOOTSTRAPPER_RELATION_UPGRADE: + return "Upgrade"; + case BOOTSTRAPPER_RELATION_ADDON: + return "Addon"; + case BOOTSTRAPPER_RELATION_PATCH: + return "Patch"; + case BOOTSTRAPPER_RELATION_DEPENDENT: + return "Dependent"; + case BOOTSTRAPPER_RELATION_UPDATE: + return "Update"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingRelatedOperationToString( + __in BOOTSTRAPPER_RELATED_OPERATION operation + ) +{ + switch (operation) + { + case BOOTSTRAPPER_RELATED_OPERATION_NONE: + return "None"; + case BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE: + return "Downgrade"; + case BOOTSTRAPPER_RELATED_OPERATION_MINOR_UPDATE: + return "MinorUpdate"; + case BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE: + return "MajorUpgrade"; + case BOOTSTRAPPER_RELATED_OPERATION_REMOVE: + return "Remove"; + case BOOTSTRAPPER_RELATED_OPERATION_INSTALL: + return "Install"; + case BOOTSTRAPPER_RELATED_OPERATION_REPAIR: + return "Repair"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingRequestStateToString( + __in BOOTSTRAPPER_REQUEST_STATE requestState + ) +{ + switch (requestState) + { + case BOOTSTRAPPER_REQUEST_STATE_NONE: + return "None"; + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: + return "ForceAbsent"; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: + return "Absent"; + case BOOTSTRAPPER_REQUEST_STATE_CACHE: + return "Cache"; + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: + return "Present"; + case BOOTSTRAPPER_REQUEST_STATE_MEND: + return "Mend"; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + return "Repair"; + default: + return "Invalid"; + } +} + +extern "C" LPCSTR LoggingRollbackOrExecute( + __in BOOL fRollback + ) +{ + return fRollback ? "rollback" : "execute"; +} + +extern "C" LPWSTR LoggingStringOrUnknownIfNull( + __in LPCWSTR wz + ) +{ + return wz ? wz : L"Unknown"; +} + + +// internal function declarations + +static void CheckLoggingPolicy( + __out DWORD *pdwAttributes + ) +{ + HRESULT hr = S_OK; + HKEY hk = NULL; + LPWSTR sczLoggingPolicy = NULL; + + hr = RegOpen(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Policies\\Microsoft\\Windows\\Installer", KEY_READ, &hk); + if (SUCCEEDED(hr)) + { + hr = RegReadString(hk, L"Logging", &sczLoggingPolicy); + if (SUCCEEDED(hr)) + { + LPCWSTR wz = sczLoggingPolicy; + while (*wz) + { + if (L'v' == *wz || L'V' == *wz) + { + *pdwAttributes |= BURN_LOGGING_ATTRIBUTE_VERBOSE; + } + else if (L'x' == *wz || L'X' == *wz) + { + *pdwAttributes |= BURN_LOGGING_ATTRIBUTE_EXTRADEBUG; + } + + ++wz; + } + } + } + + ReleaseStr(sczLoggingPolicy); + ReleaseRegKey(hk); +} + +static HRESULT GetNonSessionSpecificTempFolder( + __deref_out_z LPWSTR* psczNonSessionTempFolder + ) +{ + HRESULT hr = S_OK; + WCHAR wzTempFolder[MAX_PATH] = { }; + SIZE_T cchTempFolder = 0; + DWORD dwSessionId = 0; + LPWSTR sczSessionId = 0; + SIZE_T cchSessionId = 0; + + if (!::GetTempPathW(countof(wzTempFolder), wzTempFolder)) + { + ExitWithLastError(hr, "Failed to get temp folder."); + } + + hr = ::StringCchLengthW(wzTempFolder, countof(wzTempFolder), reinterpret_cast(&cchTempFolder)); + ExitOnFailure(hr, "Failed to get length of temp folder."); + + // If our session id is in the TEMP path then remove that part so we get the non-session + // specific temporary folder. + if (::ProcessIdToSessionId(::GetCurrentProcessId(), &dwSessionId)) + { + hr = StrAllocFormatted(&sczSessionId, L"%u\\", dwSessionId); + ExitOnFailure(hr, "Failed to format session id as a string."); + + hr = ::StringCchLengthW(sczSessionId, STRSAFE_MAX_CCH, reinterpret_cast(&cchSessionId)); + ExitOnFailure(hr, "Failed to get length of session id string."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzTempFolder + cchTempFolder - cchSessionId, static_cast(cchSessionId), sczSessionId, static_cast(cchSessionId))) + { + cchTempFolder -= cchSessionId; + } + } + + hr = StrAllocString(psczNonSessionTempFolder, wzTempFolder, cchTempFolder); + ExitOnFailure(hr, "Failed to copy temp folder."); + +LExit: + ReleaseStr(sczSessionId); + + return hr; +} diff --git a/src/burn/engine/logging.h b/src/burn/engine/logging.h new file mode 100644 index 00000000..601039f9 --- /dev/null +++ b/src/burn/engine/logging.h @@ -0,0 +1,153 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + +enum BURN_LOGGING_STATE +{ + BURN_LOGGING_STATE_CLOSED, + BURN_LOGGING_STATE_OPEN, + BURN_LOGGING_STATE_DISABLED, +}; + +enum BURN_LOGGING_ATTRIBUTE +{ + BURN_LOGGING_ATTRIBUTE_APPEND = 0x1, + BURN_LOGGING_ATTRIBUTE_VERBOSE = 0x2, + BURN_LOGGING_ATTRIBUTE_EXTRADEBUG = 0x4, +}; + + +// structs + +typedef struct _BURN_LOGGING +{ + BURN_LOGGING_STATE state; + LPWSTR sczPathVariable; + + DWORD dwAttributes; + LPWSTR sczPath; + LPWSTR sczPrefix; + LPWSTR sczExtension; +} BURN_LOGGING; + + + +// function declarations + +HRESULT LoggingOpen( + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in_z LPCWSTR wzBundleName + ); + +void LoggingOpenFailed(); + +void LoggingIncrementPackageSequence(); + +HRESULT LoggingSetPackageVariable( + __in BURN_PACKAGE* pPackage, + __in_z_opt LPCWSTR wzSuffix, + __in BOOL fRollback, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __out_opt LPWSTR* psczLogPath + ); + +LPCSTR LoggingBurnActionToString( + __in BOOTSTRAPPER_ACTION action + ); + +LPCSTR LoggingBurnMessageToString( + __in UINT message + ); + +LPCSTR LoggingActionStateToString( + __in BOOTSTRAPPER_ACTION_STATE actionState + ); + +LPCSTR LoggingDependencyActionToString( + BURN_DEPENDENCY_ACTION action + ); + +LPCSTR LoggingBoolToString( + __in BOOL f + ); + +LPCSTR LoggingTrueFalseToString( + __in BOOL f + ); + +LPCSTR LoggingPackageStateToString( + __in BOOTSTRAPPER_PACKAGE_STATE packageState + ); + +LPCSTR LoggingPackageRegistrationStateToString( + __in BOOL fCanAffectRegistration, + __in BURN_PACKAGE_REGISTRATION_STATE registrationState + ); + +LPCSTR LoggingMsiFeatureStateToString( + __in BOOTSTRAPPER_FEATURE_STATE featureState + ); + +LPCSTR LoggingMsiFeatureActionToString( + __in BOOTSTRAPPER_FEATURE_ACTION featureAction + ); + +LPCSTR LoggingMsiInstallContext( + __in MSIINSTALLCONTEXT context + ); + +LPCWSTR LoggingBurnMsiPropertyToString( + __in BURN_MSI_PROPERTY burnMsiProperty + ); + +LPCSTR LoggingMspTargetActionToString( + __in BOOTSTRAPPER_ACTION_STATE action, + __in BURN_PATCH_SKIP_STATE skipState + ); + +LPCSTR LoggingPerMachineToString( + __in BOOL fPerMachine + ); + +LPCSTR LoggingRestartToString( + __in BOOTSTRAPPER_APPLY_RESTART restart + ); + +LPCSTR LoggingResumeModeToString( + __in BURN_RESUME_MODE resumeMode + ); + +LPCSTR LoggingRelationTypeToString( + __in BOOTSTRAPPER_RELATION_TYPE type + ); + +LPCSTR LoggingRelatedOperationToString( + __in BOOTSTRAPPER_RELATED_OPERATION operation + ); + +LPCSTR LoggingRequestStateToString( + __in BOOTSTRAPPER_REQUEST_STATE requestState + ); + +LPCSTR LoggingRollbackOrExecute( + __in BOOL fRollback + ); + +LPWSTR LoggingStringOrUnknownIfNull( + __in LPCWSTR wz + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/manifest.cpp b/src/burn/engine/manifest.cpp new file mode 100644 index 00000000..b1740083 --- /dev/null +++ b/src/burn/engine/manifest.cpp @@ -0,0 +1,164 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +static HRESULT ParseFromXml( + __in IXMLDOMDocument* pixdDocument, + __in BURN_ENGINE_STATE* pEngineState + ); + +// function definitions + +extern "C" HRESULT ManifestLoadXmlFromFile( + __in LPCWSTR wzPath, + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + IXMLDOMDocument* pixdDocument = NULL; + + // load xml document + hr = XmlLoadDocumentFromFile(wzPath, &pixdDocument); + ExitOnFailure(hr, "Failed to load manifest as XML document."); + + hr = ParseFromXml(pixdDocument, pEngineState); + +LExit: + ReleaseObject(pixdDocument); + + return hr; +} + +extern "C" HRESULT ManifestLoadXmlFromBuffer( + __in_bcount(cbBuffer) BYTE* pbBuffer, + __in SIZE_T cbBuffer, + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + IXMLDOMDocument* pixdDocument = NULL; + + // load xml document + hr = XmlLoadDocumentFromBuffer(pbBuffer, cbBuffer, &pixdDocument); + ExitOnFailure(hr, "Failed to load manifest as XML document."); + + hr = ParseFromXml(pixdDocument, pEngineState); + +LExit: + ReleaseObject(pixdDocument); + + return hr; +} + +static HRESULT ParseFromXml( + __in IXMLDOMDocument* pixdDocument, + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + IXMLDOMNode* pixnLog = NULL; + IXMLDOMNode* pixnChain = NULL; + + // get bundle element + hr = pixdDocument->get_documentElement(&pixeBundle); + ExitOnFailure(hr, "Failed to get bundle element."); + + // parse the log element, if present. + hr = XmlSelectSingleNode(pixeBundle, L"Log", &pixnLog); + ExitOnFailure(hr, "Failed to get Log element."); + + if (S_OK == hr) + { + hr = XmlGetAttributeEx(pixnLog, L"PathVariable", &pEngineState->log.sczPathVariable); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get Log/@PathVariable."); + } + + hr = XmlGetAttributeEx(pixnLog, L"Prefix", &pEngineState->log.sczPrefix); + ExitOnFailure(hr, "Failed to get Log/@Prefix attribute."); + + hr = XmlGetAttributeEx(pixnLog, L"Extension", &pEngineState->log.sczExtension); + ExitOnFailure(hr, "Failed to get Log/@Extension attribute."); + } + + // get the chain element + hr = XmlSelectSingleNode(pixeBundle, L"Chain", &pixnChain); + ExitOnFailure(hr, "Failed to get chain element."); + + if (S_OK == hr) + { + // parse disable rollback + hr = XmlGetYesNoAttribute(pixnChain, L"DisableRollback", &pEngineState->fDisableRollback); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get Chain/@DisableRollback"); + } + + // parse disable system restore + hr = XmlGetYesNoAttribute(pixnChain, L"DisableSystemRestore", &pEngineState->fDisableSystemRestore); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get Chain/@DisableSystemRestore"); + } + + // parse parallel cache + hr = XmlGetYesNoAttribute(pixnChain, L"ParallelCache", &pEngineState->fParallelCacheAndExecute); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get Chain/@ParallelCache"); + } + } + + // parse built-in condition + hr = ConditionGlobalParseFromXml(&pEngineState->condition, pixeBundle); + ExitOnFailure(hr, "Failed to parse global condition."); + + // parse variables + hr = VariablesParseFromXml(&pEngineState->variables, pixeBundle); + ExitOnFailure(hr, "Failed to parse variables."); + + // parse user experience + hr = UserExperienceParseFromXml(&pEngineState->userExperience, pixeBundle); + ExitOnFailure(hr, "Failed to parse user experience."); + + // parse extensions + hr = BurnExtensionParseFromXml(&pEngineState->extensions, &pEngineState->userExperience.payloads, pixeBundle); + ExitOnFailure(hr, "Failed to parse extensions."); + + // parse searches + hr = SearchesParseFromXml(&pEngineState->searches, &pEngineState->extensions, pixeBundle); + ExitOnFailure(hr, "Failed to parse searches."); + + // parse registration + hr = RegistrationParseFromXml(&pEngineState->registration, pixeBundle); + ExitOnFailure(hr, "Failed to parse registration."); + + // parse update + hr = UpdateParseFromXml(&pEngineState->update, pixeBundle); + ExitOnFailure(hr, "Failed to parse update."); + + // parse containers + hr = ContainersParseFromXml(&pEngineState->containers, pixeBundle); + ExitOnFailure(hr, "Failed to parse containers."); + + // parse payloads + hr = PayloadsParseFromXml(&pEngineState->payloads, &pEngineState->containers, &pEngineState->layoutPayloads, pixeBundle); + ExitOnFailure(hr, "Failed to parse payloads."); + + // parse packages + hr = PackagesParseFromXml(&pEngineState->packages, &pEngineState->payloads, pixeBundle); + ExitOnFailure(hr, "Failed to parse packages."); + + // parse approved exes for elevation + hr = ApprovedExesParseFromXml(&pEngineState->approvedExes, pixeBundle); + ExitOnFailure(hr, "Failed to parse approved exes."); + +LExit: + ReleaseObject(pixnChain); + ReleaseObject(pixnLog); + ReleaseObject(pixeBundle); + return hr; +} diff --git a/src/burn/engine/manifest.h b/src/burn/engine/manifest.h new file mode 100644 index 00000000..8c527279 --- /dev/null +++ b/src/burn/engine/manifest.h @@ -0,0 +1,28 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +interface IBurnPayload; // forward declare. + +#if defined(__cplusplus) +extern "C" { +#endif + + +// function declarations + +HRESULT ManifestLoadXmlFromFile( + __in LPCWSTR wzPath, + __in BURN_ENGINE_STATE* pEngineState + ); + +HRESULT ManifestLoadXmlFromBuffer( + __in_bcount(cbBuffer) BYTE* pbBuffer, + __in SIZE_T cbBuffer, + __in BURN_ENGINE_STATE* pEngineState + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/msiengine.cpp b/src/burn/engine/msiengine.cpp new file mode 100644 index 00000000..3e96e5f9 --- /dev/null +++ b/src/burn/engine/msiengine.cpp @@ -0,0 +1,2035 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// constants + + +// structs + + + +// internal function declarations + +static HRESULT ParseRelatedMsiFromXml( + __in IXMLDOMNode* pixnRelatedMsi, + __in BURN_RELATED_MSI* pRelatedMsi + ); +static HRESULT EvaluateActionStateConditions( + __in BURN_VARIABLES* pVariables, + __in_z_opt LPCWSTR sczAddLocalCondition, + __in_z_opt LPCWSTR sczAddSourceCondition, + __in_z_opt LPCWSTR sczAdvertiseCondition, + __out BOOTSTRAPPER_FEATURE_STATE* pState + ); +static HRESULT CalculateFeatureAction( + __in BOOTSTRAPPER_FEATURE_STATE currentState, + __in BOOTSTRAPPER_FEATURE_STATE requestedState, + __in BOOL fRepair, + __out BOOTSTRAPPER_FEATURE_ACTION* pFeatureAction, + __inout BOOL* pfDelta + ); +static HRESULT EscapePropertyArgumentString( + __in LPCWSTR wzProperty, + __inout_z LPWSTR* psczEscapedValue, + __in BOOL fZeroOnRealloc + ); +static HRESULT ConcatFeatureActionProperties( + __in BURN_PACKAGE* pPackage, + __in BOOTSTRAPPER_FEATURE_ACTION* rgFeatureActions, + __inout_z LPWSTR* psczArguments + ); +static HRESULT ConcatPatchProperty( + __in BURN_PACKAGE* pPackage, + __in BOOL fRollback, + __inout_z LPWSTR* psczArguments + ); +static void RegisterSourceDirectory( + __in BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzCacheDirectory + ); + + +// function definitions + +extern "C" HRESULT MsiEngineParsePackageFromXml( + __in IXMLDOMNode* pixnMsiPackage, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + LPWSTR scz = NULL; + + // @ProductCode + hr = XmlGetAttributeEx(pixnMsiPackage, L"ProductCode", &pPackage->Msi.sczProductCode); + ExitOnFailure(hr, "Failed to get @ProductCode."); + + // @Language + hr = XmlGetAttributeNumber(pixnMsiPackage, L"Language", &pPackage->Msi.dwLanguage); + ExitOnFailure(hr, "Failed to get @Language."); + + // @Version + hr = XmlGetAttributeEx(pixnMsiPackage, L"Version", &scz); + ExitOnFailure(hr, "Failed to get @Version."); + + hr = VerParseVersion(scz, 0, FALSE, &pPackage->Msi.pVersion); + ExitOnFailure(hr, "Failed to parse @Version: %ls", scz); + + if (pPackage->Msi.pVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_MANIFEST_INVALID_VERSION, scz); + } + + // @UpgradeCode + hr = XmlGetAttributeEx(pixnMsiPackage, L"UpgradeCode", &pPackage->Msi.sczUpgradeCode); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @UpgradeCode."); + } + + // select feature nodes + hr = XmlSelectNodes(pixnMsiPackage, L"MsiFeature", &pixnNodes); + ExitOnFailure(hr, "Failed to select feature nodes."); + + // get feature node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get feature node count."); + + if (cNodes) + { + // allocate memory for features + pPackage->Msi.rgFeatures = (BURN_MSIFEATURE*)MemAlloc(sizeof(BURN_MSIFEATURE) * cNodes, TRUE); + ExitOnNull(pPackage->Msi.rgFeatures, hr, E_OUTOFMEMORY, "Failed to allocate memory for MSI feature structs."); + + pPackage->Msi.cFeatures = cNodes; + + // parse feature elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &pFeature->sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + // @AddLocalCondition + hr = XmlGetAttributeEx(pixnNode, L"AddLocalCondition", &pFeature->sczAddLocalCondition); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @AddLocalCondition."); + } + + // @AddSourceCondition + hr = XmlGetAttributeEx(pixnNode, L"AddSourceCondition", &pFeature->sczAddSourceCondition); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @AddSourceCondition."); + } + + // @AdvertiseCondition + hr = XmlGetAttributeEx(pixnNode, L"AdvertiseCondition", &pFeature->sczAdvertiseCondition); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @AdvertiseCondition."); + } + + // @RollbackAddLocalCondition + hr = XmlGetAttributeEx(pixnNode, L"RollbackAddLocalCondition", &pFeature->sczRollbackAddLocalCondition); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @RollbackAddLocalCondition."); + } + + // @RollbackAddSourceCondition + hr = XmlGetAttributeEx(pixnNode, L"RollbackAddSourceCondition", &pFeature->sczRollbackAddSourceCondition); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @RollbackAddSourceCondition."); + } + + // @RollbackAdvertiseCondition + hr = XmlGetAttributeEx(pixnNode, L"RollbackAdvertiseCondition", &pFeature->sczRollbackAdvertiseCondition); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @RollbackAdvertiseCondition."); + } + + // prepare next iteration + ReleaseNullObject(pixnNode); + } + } + + ReleaseNullObject(pixnNodes); // done with the MsiFeature elements. + + hr = MsiEngineParsePropertiesFromXml(pixnMsiPackage, &pPackage->Msi.rgProperties, &pPackage->Msi.cProperties); + ExitOnFailure(hr, "Failed to parse properties from XML."); + + // select related MSI nodes + hr = XmlSelectNodes(pixnMsiPackage, L"RelatedPackage", &pixnNodes); + ExitOnFailure(hr, "Failed to select related MSI nodes."); + + // get related MSI node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get related MSI node count."); + + if (cNodes) + { + // allocate memory for related MSIs + pPackage->Msi.rgRelatedMsis = (BURN_RELATED_MSI*)MemAlloc(sizeof(BURN_RELATED_MSI) * cNodes, TRUE); + ExitOnNull(pPackage->Msi.rgRelatedMsis, hr, E_OUTOFMEMORY, "Failed to allocate memory for related MSI structs."); + + pPackage->Msi.cRelatedMsis = cNodes; + + // parse related MSI elements + for (DWORD i = 0; i < cNodes; ++i) + { + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // parse related MSI element + hr = ParseRelatedMsiFromXml(pixnNode, &pPackage->Msi.rgRelatedMsis[i]); + ExitOnFailure(hr, "Failed to parse related MSI element."); + + // prepare next iteration + ReleaseNullObject(pixnNode); + } + } + + ReleaseNullObject(pixnNodes); // done with the RelatedPackage elements. + + // Select slipstream MSP nodes. + hr = XmlSelectNodes(pixnMsiPackage, L"SlipstreamMsp", &pixnNodes); + ExitOnFailure(hr, "Failed to select related MSI nodes."); + + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get related MSI node count."); + + if (cNodes) + { + pPackage->Msi.rgSlipstreamMsps = reinterpret_cast(MemAlloc(sizeof(BURN_SLIPSTREAM_MSP) * cNodes, TRUE)); + ExitOnNull(pPackage->Msi.rgSlipstreamMsps, hr, E_OUTOFMEMORY, "Failed to allocate memory for slipstream MSP packages."); + + pPackage->Msi.rgsczSlipstreamMspPackageIds = reinterpret_cast(MemAlloc(sizeof(LPWSTR*) * cNodes, TRUE)); + ExitOnNull(pPackage->Msi.rgsczSlipstreamMspPackageIds, hr, E_OUTOFMEMORY, "Failed to allocate memory for slipstream MSP ids."); + + pPackage->Msi.cSlipstreamMspPackages = cNodes; + + // Parse slipstream MSP Ids. + for (DWORD i = 0; i < cNodes; ++i) + { + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next slipstream MSP node."); + + hr = XmlGetAttributeEx(pixnNode, L"Id", pPackage->Msi.rgsczSlipstreamMspPackageIds + i); + ExitOnFailure(hr, "Failed to parse slipstream MSP ids."); + + ReleaseNullObject(pixnNode); + } + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(scz); + + return hr; +} + +extern "C" HRESULT MsiEngineParsePropertiesFromXml( + __in IXMLDOMNode* pixnPackage, + __out BURN_MSIPROPERTY** prgProperties, + __out DWORD* pcProperties + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + + BURN_MSIPROPERTY* pProperties = NULL; + + // select property nodes + hr = XmlSelectNodes(pixnPackage, L"MsiProperty", &pixnNodes); + ExitOnFailure(hr, "Failed to select property nodes."); + + // get property node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get property node count."); + + if (cNodes) + { + // allocate memory for properties + pProperties = (BURN_MSIPROPERTY*)MemAlloc(sizeof(BURN_MSIPROPERTY) * cNodes, TRUE); + ExitOnNull(pProperties, hr, E_OUTOFMEMORY, "Failed to allocate memory for MSI property structs."); + + // parse property elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_MSIPROPERTY* pProperty = &pProperties[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &pProperty->sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + // @Value + hr = XmlGetAttributeEx(pixnNode, L"Value", &pProperty->sczValue); + ExitOnFailure(hr, "Failed to get @Value."); + + // @RollbackValue + hr = XmlGetAttributeEx(pixnNode, L"RollbackValue", &pProperty->sczRollbackValue); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @RollbackValue."); + } + + // @Condition + hr = XmlGetAttributeEx(pixnNode, L"Condition", &pProperty->sczCondition); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Condition."); + } + + // prepare next iteration + ReleaseNullObject(pixnNode); + } + } + + *pcProperties = cNodes; + *prgProperties = pProperties; + pProperties = NULL; + + hr = S_OK; + +LExit: + ReleaseNullObject(pixnNodes); + ReleaseMem(pProperties); + + return hr; +} + +extern "C" void MsiEnginePackageUninitialize( + __in BURN_PACKAGE* pPackage + ) +{ + ReleaseStr(pPackage->Msi.sczProductCode); + ReleaseStr(pPackage->Msi.sczUpgradeCode); + + // free features + if (pPackage->Msi.rgFeatures) + { + for (DWORD i = 0; i < pPackage->Msi.cFeatures; ++i) + { + BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; + + ReleaseStr(pFeature->sczId); + ReleaseStr(pFeature->sczAddLocalCondition); + ReleaseStr(pFeature->sczAddSourceCondition); + ReleaseStr(pFeature->sczAdvertiseCondition); + ReleaseStr(pFeature->sczRollbackAddLocalCondition); + ReleaseStr(pFeature->sczRollbackAddSourceCondition); + ReleaseStr(pFeature->sczRollbackAdvertiseCondition); + } + MemFree(pPackage->Msi.rgFeatures); + } + + // free properties + if (pPackage->Msi.rgProperties) + { + for (DWORD i = 0; i < pPackage->Msi.cProperties; ++i) + { + BURN_MSIPROPERTY* pProperty = &pPackage->Msi.rgProperties[i]; + + ReleaseStr(pProperty->sczId); + ReleaseStr(pProperty->sczValue); + ReleaseStr(pProperty->sczRollbackValue); + ReleaseStr(pProperty->sczCondition); + } + MemFree(pPackage->Msi.rgProperties); + } + + // free related MSIs + if (pPackage->Msi.rgRelatedMsis) + { + for (DWORD i = 0; i < pPackage->Msi.cRelatedMsis; ++i) + { + BURN_RELATED_MSI* pRelatedMsi = &pPackage->Msi.rgRelatedMsis[i]; + + ReleaseStr(pRelatedMsi->sczUpgradeCode); + ReleaseMem(pRelatedMsi->rgdwLanguages); + } + MemFree(pPackage->Msi.rgRelatedMsis); + } + + // free slipstream MSPs + if (pPackage->Msi.rgsczSlipstreamMspPackageIds) + { + for (DWORD i = 0; i < pPackage->Msi.cSlipstreamMspPackages; ++i) + { + ReleaseStr(pPackage->Msi.rgsczSlipstreamMspPackageIds[i]); + } + + MemFree(pPackage->Msi.rgsczSlipstreamMspPackageIds); + } + + if (pPackage->Msi.rgSlipstreamMsps) + { + MemFree(pPackage->Msi.rgSlipstreamMsps); + } + + if (pPackage->Msi.rgChainedPatches) + { + MemFree(pPackage->Msi.rgChainedPatches); + } + + // clear struct + memset(&pPackage->Msi, 0, sizeof(pPackage->Msi)); +} + +extern "C" HRESULT MsiEngineDetectInitialize( + __in BURN_PACKAGES* pPackages + ) +{ + AssertSz(pPackages->cPatchInfo, "MsiEngineDetectInitialize() should only be called if there are MSP packages."); + + HRESULT hr = S_OK; + + // Add target products for slipstream MSIs that weren't detected. + for (DWORD iPackage = 0; iPackage < pPackages->cPackages; ++iPackage) + { + BURN_PACKAGE* pMsiPackage = pPackages->rgPackages + iPackage; + if (BURN_PACKAGE_TYPE_MSI == pMsiPackage->type) + { + for (DWORD j = 0; j < pMsiPackage->Msi.cSlipstreamMspPackages; ++j) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pMsiPackage->Msi.rgSlipstreamMsps + j; + Assert(pSlipstreamMsp->pMspPackage && BURN_PACKAGE_TYPE_MSP == pSlipstreamMsp->pMspPackage->type); + + if (pSlipstreamMsp->pMspPackage && BURN_PACKAGE_INVALID_PATCH_INDEX == pSlipstreamMsp->dwMsiChainedPatchIndex) + { + hr = MspEngineAddMissingSlipstreamTarget(pMsiPackage, pSlipstreamMsp); + ExitOnFailure(hr, "Failed to add slipstreamed target product code to package: %ls", pSlipstreamMsp->pMspPackage->sczId); + } + } + } + } + +LExit: + return hr; +} + +extern "C" HRESULT MsiEngineDetectPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + Trace(REPORT_STANDARD, "Detecting MSI package 0x%p", pPackage); + + HRESULT hr = S_OK; + int nCompareResult = 0; + LPWSTR sczInstalledVersion = NULL; + LPWSTR sczInstalledLanguage = NULL; + INSTALLSTATE installState = INSTALLSTATE_UNKNOWN; + BOOTSTRAPPER_RELATED_OPERATION operation = BOOTSTRAPPER_RELATED_OPERATION_NONE; + BOOTSTRAPPER_RELATED_OPERATION relatedMsiOperation = BOOTSTRAPPER_RELATED_OPERATION_NONE; + WCHAR wzProductCode[MAX_GUID_CHARS + 1] = { }; + VERUTIL_VERSION* pVersion = NULL; + UINT uLcid = 0; + BOOL fPerMachine = FALSE; + + // detect self by product code + // TODO: what to do about MSIINSTALLCONTEXT_USERMANAGED? + hr = WiuGetProductInfoEx(pPackage->Msi.sczProductCode, NULL, pPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED, INSTALLPROPERTY_VERSIONSTRING, &sczInstalledVersion); + if (SUCCEEDED(hr)) + { + hr = VerParseVersion(sczInstalledVersion, 0, FALSE, &pPackage->Msi.pInstalledVersion); + ExitOnFailure(hr, "Failed to parse installed version: '%ls' for ProductCode: %ls", sczInstalledVersion, pPackage->Msi.sczProductCode); + + if (pPackage->Msi.pInstalledVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_DETECTED_MSI_PACKAGE_INVALID_VERSION, pPackage->Msi.sczProductCode, sczInstalledVersion); + } + + // compare versions + hr = VerCompareParsedVersions(pPackage->Msi.pVersion, pPackage->Msi.pInstalledVersion, &nCompareResult); + ExitOnFailure(hr, "Failed to compare version '%ls' to installed version: '%ls'", pPackage->Msi.pVersion->sczVersion, pPackage->Msi.pInstalledVersion->sczVersion); + + if (nCompareResult < 0) + { + operation = BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE; + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED; + } + else + { + if (nCompareResult > 0) + { + operation = BOOTSTRAPPER_RELATED_OPERATION_MINOR_UPDATE; + } + + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_PRESENT; + } + + // Report related MSI package to BA. + if (BOOTSTRAPPER_RELATED_OPERATION_NONE != operation) + { + LogId(REPORT_STANDARD, MSG_DETECTED_RELATED_PACKAGE, pPackage->Msi.sczProductCode, LoggingPerMachineToString(pPackage->fPerMachine), pPackage->Msi.pInstalledVersion->sczVersion, pPackage->Msi.dwLanguage, LoggingRelatedOperationToString(operation)); + + hr = UserExperienceOnDetectRelatedMsiPackage(pUserExperience, pPackage->sczId, pPackage->Msi.sczUpgradeCode, pPackage->Msi.sczProductCode, pPackage->fPerMachine, pPackage->Msi.pInstalledVersion, operation); + ExitOnRootFailure(hr, "BA aborted detect related MSI package."); + } + } + else if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr || HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY) == hr) // package not present. + { + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to get product information for ProductCode: %ls", pPackage->Msi.sczProductCode); + } + + // detect related packages by upgrade code + for (DWORD i = 0; i < pPackage->Msi.cRelatedMsis; ++i) + { + BURN_RELATED_MSI* pRelatedMsi = &pPackage->Msi.rgRelatedMsis[i]; + + for (DWORD iProduct = 0; ; ++iProduct) + { + // get product + hr = WiuEnumRelatedProducts(pRelatedMsi->sczUpgradeCode, iProduct, wzProductCode); + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + break; + } + ExitOnFailure(hr, "Failed to enum related products."); + + // If we found ourselves, skip because saying that a package is related to itself is nonsensical. + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pPackage->Msi.sczProductCode, -1, wzProductCode, -1)) + { + continue; + } + + // get product version + hr = WiuGetProductInfoEx(wzProductCode, NULL, MSIINSTALLCONTEXT_MACHINE, INSTALLPROPERTY_VERSIONSTRING, &sczInstalledVersion); + if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) != hr && HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY) != hr) + { + ExitOnFailure(hr, "Failed to get version for product in machine context: %ls", wzProductCode); + fPerMachine = TRUE; + } + else + { + hr = WiuGetProductInfoEx(wzProductCode, NULL, MSIINSTALLCONTEXT_USERUNMANAGED, INSTALLPROPERTY_VERSIONSTRING, &sczInstalledVersion); + if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) != hr && HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY) != hr) + { + ExitOnFailure(hr, "Failed to get version for product in user unmanaged context: %ls", wzProductCode); + fPerMachine = FALSE; + } + else + { + hr = S_OK; + continue; + } + } + + hr = VerParseVersion(sczInstalledVersion, 0, FALSE, &pVersion); + ExitOnFailure(hr, "Failed to parse related installed version: '%ls' for ProductCode: %ls", sczInstalledVersion, wzProductCode); + + if (pVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_DETECTED_MSI_PACKAGE_INVALID_VERSION, wzProductCode, sczInstalledVersion); + } + + // compare versions + if (pRelatedMsi->fMinProvided) + { + hr = VerCompareParsedVersions(pVersion, pRelatedMsi->pMinVersion, &nCompareResult); + ExitOnFailure(hr, "Failed to compare related installed version '%ls' to related min version: '%ls'", pVersion->sczVersion, pRelatedMsi->pMinVersion->sczVersion); + + if (pRelatedMsi->fMinInclusive ? (nCompareResult < 0) : (nCompareResult <= 0)) + { + continue; + } + } + + if (pRelatedMsi->fMaxProvided) + { + hr = VerCompareParsedVersions(pVersion, pRelatedMsi->pMaxVersion, &nCompareResult); + ExitOnFailure(hr, "Failed to compare related installed version '%ls' to related max version: '%ls'", pVersion->sczVersion, pRelatedMsi->pMaxVersion->sczVersion); + + if (pRelatedMsi->fMaxInclusive ? (nCompareResult > 0) : (nCompareResult >= 0)) + { + continue; + } + } + + // Filter by language if necessary. + uLcid = 0; // always reset the found language. + if (pRelatedMsi->cLanguages) + { + // If there is a language to get, convert it into an LCID. + hr = WiuGetProductInfoEx(wzProductCode, NULL, fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED, INSTALLPROPERTY_LANGUAGE, &sczInstalledLanguage); + if (SUCCEEDED(hr)) + { + hr = StrStringToUInt32(sczInstalledLanguage, 0, &uLcid); + } + + // Ignore related product where we can't read the language. + if (FAILED(hr)) + { + LogErrorId(hr, MSG_FAILED_READ_RELATED_PACKAGE_LANGUAGE, wzProductCode, sczInstalledLanguage, NULL); + + hr = S_OK; + continue; + } + + BOOL fMatchedLcid = FALSE; + for (DWORD iLanguage = 0; iLanguage < pRelatedMsi->cLanguages; ++iLanguage) + { + if (uLcid == pRelatedMsi->rgdwLanguages[iLanguage]) + { + fMatchedLcid = TRUE; + break; + } + } + + // Skip the product if the language did not meet the inclusive/exclusive criteria. + if ((pRelatedMsi->fLangInclusive && !fMatchedLcid) || (!pRelatedMsi->fLangInclusive && fMatchedLcid)) + { + continue; + } + } + + // If this is a detect-only related package and we're not installed yet, then we'll assume a downgrade + // would take place since that is the overwhelmingly common use of detect-only related packages. If + // not detect-only then it's easy; we're clearly doing a major upgrade. + if (pRelatedMsi->fOnlyDetect) + { + // If we've already detected a major upgrade that trumps any guesses that the detect is a downgrade + // or even something else. + if (BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE == operation) + { + relatedMsiOperation = BOOTSTRAPPER_RELATED_OPERATION_NONE; + } + // It can't be a downgrade if the upgrade codes aren't the same. + else if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT == pPackage->currentState && + pPackage->Msi.sczUpgradeCode && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pPackage->Msi.sczUpgradeCode, -1, pRelatedMsi->sczUpgradeCode, -1)) + { + relatedMsiOperation = BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE; + operation = BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE; + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE; + } + else // we're already on the machine so the detect-only *must* be for detection purposes only. + { + relatedMsiOperation = BOOTSTRAPPER_RELATED_OPERATION_NONE; + } + } + else + { + relatedMsiOperation = BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE; + operation = BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE; + } + + LogId(REPORT_STANDARD, MSG_DETECTED_RELATED_PACKAGE, wzProductCode, LoggingPerMachineToString(fPerMachine), pVersion->sczVersion, uLcid, LoggingRelatedOperationToString(relatedMsiOperation)); + + // Pass to BA. + hr = UserExperienceOnDetectRelatedMsiPackage(pUserExperience, pPackage->sczId, pRelatedMsi->sczUpgradeCode, wzProductCode, fPerMachine, pVersion, relatedMsiOperation); + ExitOnRootFailure(hr, "BA aborted detect related MSI package."); + } + } + + // detect features + if (pPackage->Msi.cFeatures) + { + for (DWORD i = 0; i < pPackage->Msi.cFeatures; ++i) + { + BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; + + // Try to detect features state if the product is present on the machine. + if (BOOTSTRAPPER_PACKAGE_STATE_PRESENT <= pPackage->currentState) + { + hr = WiuQueryFeatureState(pPackage->Msi.sczProductCode, pFeature->sczId, &installState); + ExitOnFailure(hr, "Failed to query feature state."); + + if (INSTALLSTATE_UNKNOWN == installState) // in case of an upgrade a feature could be removed. + { + installState = INSTALLSTATE_ABSENT; + } + } + else // MSI not installed then the features can't be either. + { + installState = INSTALLSTATE_ABSENT; + } + + // set current state + switch (installState) + { + case INSTALLSTATE_ABSENT: + pFeature->currentState = BOOTSTRAPPER_FEATURE_STATE_ABSENT; + break; + case INSTALLSTATE_ADVERTISED: + pFeature->currentState = BOOTSTRAPPER_FEATURE_STATE_ADVERTISED; + break; + case INSTALLSTATE_LOCAL: + pFeature->currentState = BOOTSTRAPPER_FEATURE_STATE_LOCAL; + break; + case INSTALLSTATE_SOURCE: + pFeature->currentState = BOOTSTRAPPER_FEATURE_STATE_SOURCE; + break; + default: + hr = E_UNEXPECTED; + ExitOnRootFailure(hr, "Invalid state value."); + } + + // Pass to BA. + hr = UserExperienceOnDetectMsiFeature(pUserExperience, pPackage->sczId, pFeature->sczId, pFeature->currentState); + ExitOnRootFailure(hr, "BA aborted detect MSI feature."); + } + } + + if (pPackage->fCanAffectRegistration) + { + pPackage->installRegistrationState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT < pPackage->currentState ? BURN_PACKAGE_REGISTRATION_STATE_PRESENT : BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + +LExit: + ReleaseStr(sczInstalledLanguage); + ReleaseStr(sczInstalledVersion); + ReleaseVerutilVersion(pVersion); + + return hr; +} + +extern "C" HRESULT MsiEnginePlanInitializePackage( + __in BURN_PACKAGE* pPackage, + __in BURN_VARIABLES* pVariables, + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + + if (pPackage->Msi.cFeatures) + { + // get feature request states + for (DWORD i = 0; i < pPackage->Msi.cFeatures; ++i) + { + BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; + + // Evaluate feature conditions. + hr = EvaluateActionStateConditions(pVariables, pFeature->sczAddLocalCondition, pFeature->sczAddSourceCondition, pFeature->sczAdvertiseCondition, &pFeature->defaultRequested); + ExitOnFailure(hr, "Failed to evaluate requested state conditions."); + + hr = EvaluateActionStateConditions(pVariables, pFeature->sczRollbackAddLocalCondition, pFeature->sczRollbackAddSourceCondition, pFeature->sczRollbackAdvertiseCondition, &pFeature->expectedState); + ExitOnFailure(hr, "Failed to evaluate expected state conditions."); + + // Remember the default feature requested state so the engine doesn't get blamed for planning the wrong thing if the BA changes it. + pFeature->requested = pFeature->defaultRequested; + + // Send plan MSI feature message to BA. + hr = UserExperienceOnPlanMsiFeature(pUserExperience, pPackage->sczId, pFeature->sczId, &pFeature->requested); + ExitOnRootFailure(hr, "BA aborted plan MSI feature."); + } + } + +LExit: + return hr; +} + +// +// PlanCalculate - calculates the execute and rollback state for the requested package state. +// +extern "C" HRESULT MsiEnginePlanCalculatePackage( + __in BURN_PACKAGE* pPackage, + __in BOOL fInsideMsiTransaction + ) +{ + Trace(REPORT_STANDARD, "Planning MSI package 0x%p", pPackage); + + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion = pPackage->Msi.pVersion; + VERUTIL_VERSION* pInstalledVersion = pPackage->Msi.pInstalledVersion; + int nCompareResult = 0; + BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE; + BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + BOOL fFeatureActionDelta = FALSE; + BOOL fRollbackFeatureActionDelta = FALSE; + + if (pPackage->Msi.cFeatures) + { + // If the package is present and we're repairing it. + BOOL fRepairingPackage = (BOOTSTRAPPER_PACKAGE_STATE_ABSENT < pPackage->currentState && BOOTSTRAPPER_REQUEST_STATE_REPAIR == pPackage->requested); + + // plan features + for (DWORD i = 0; i < pPackage->Msi.cFeatures; ++i) + { + BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; + + // Calculate feature actions. + hr = CalculateFeatureAction(pFeature->currentState, pFeature->requested, fRepairingPackage, &pFeature->execute, &fFeatureActionDelta); + ExitOnFailure(hr, "Failed to calculate execute feature state."); + + hr = CalculateFeatureAction(pFeature->requested, BOOTSTRAPPER_FEATURE_ACTION_NONE == pFeature->execute ? pFeature->expectedState : pFeature->currentState, FALSE, &pFeature->rollback, &fRollbackFeatureActionDelta); + ExitOnFailure(hr, "Failed to calculate rollback feature state."); + } + } + + // execute action + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED: + if (BOOTSTRAPPER_REQUEST_STATE_PRESENT == pPackage->requested || BOOTSTRAPPER_REQUEST_STATE_MEND == pPackage->requested || BOOTSTRAPPER_REQUEST_STATE_REPAIR == pPackage->requested) + { + hr = VerCompareParsedVersions(pVersion, pInstalledVersion, &nCompareResult); + ExitOnFailure(hr, "Failed to compare '%ls' to '%ls' for planning.", pVersion->sczVersion, pInstalledVersion->sczVersion); + + // Take a look at the version and determine if this is a potential + // minor upgrade (same ProductCode newer ProductVersion), otherwise, + // there is a newer version so no work necessary. + if (nCompareResult > 0) + { + execute = BOOTSTRAPPER_ACTION_STATE_MINOR_UPGRADE; + } + else if (BOOTSTRAPPER_REQUEST_STATE_MEND == pPackage->requested) + { + execute = BOOTSTRAPPER_ACTION_STATE_MEND; + } + else if (BOOTSTRAPPER_REQUEST_STATE_REPAIR == pPackage->requested) + { + execute = BOOTSTRAPPER_ACTION_STATE_REPAIR; + } + else + { + execute = fFeatureActionDelta ? BOOTSTRAPPER_ACTION_STATE_MODIFY : BOOTSTRAPPER_ACTION_STATE_NONE; + } + } + else if ((BOOTSTRAPPER_REQUEST_STATE_ABSENT == pPackage->requested || BOOTSTRAPPER_REQUEST_STATE_CACHE == pPackage->requested) && + pPackage->fUninstallable) // removing a package that can be removed. + { + execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + } + else if (BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT == pPackage->requested) + { + execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + } + else + { + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_MEND: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + execute = BOOTSTRAPPER_ACTION_STATE_INSTALL; + break; + + default: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package current state result encountered during plan: %d", pPackage->currentState); + } + + // Calculate the rollback action if there is an execute action. + if (BOOTSTRAPPER_ACTION_STATE_NONE != execute && !fInsideMsiTransaction) + { + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: + rollback = fRollbackFeatureActionDelta ? BOOTSTRAPPER_ACTION_STATE_MODIFY : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: + rollback = BOOTSTRAPPER_ACTION_STATE_INSTALL; + break; + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: __fallthrough; + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: __fallthrough; + // If the package is uninstallable and we requested to put the package on the machine then + // remove the package during rollback. + if (pPackage->fUninstallable && + (BOOTSTRAPPER_REQUEST_STATE_PRESENT == pPackage->requested || + BOOTSTRAPPER_REQUEST_STATE_MEND == pPackage->requested || + BOOTSTRAPPER_REQUEST_STATE_REPAIR == pPackage->requested)) + { + rollback = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + } + else + { + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package detection result encountered."); + } + } + + // return values + pPackage->execute = execute; + pPackage->rollback = rollback; + +LExit: + return hr; +} + +// +// PlanAdd - adds the calculated execute and rollback actions for the package. +// +extern "C" HRESULT MsiEnginePlanAddPackage( + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in_opt HANDLE hCacheEvent + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pAction = NULL; + BOOTSTRAPPER_FEATURE_ACTION* rgFeatureActions = NULL; + BOOTSTRAPPER_FEATURE_ACTION* rgRollbackFeatureActions = NULL; + + if (pPackage->Msi.cFeatures) + { + // Allocate and populate array for feature actions. + rgFeatureActions = (BOOTSTRAPPER_FEATURE_ACTION*)MemAlloc(sizeof(BOOTSTRAPPER_FEATURE_ACTION) * pPackage->Msi.cFeatures, TRUE); + ExitOnNull(rgFeatureActions, hr, E_OUTOFMEMORY, "Failed to allocate memory for feature actions."); + + rgRollbackFeatureActions = (BOOTSTRAPPER_FEATURE_ACTION*)MemAlloc(sizeof(BOOTSTRAPPER_FEATURE_ACTION) * pPackage->Msi.cFeatures, TRUE); + ExitOnNull(rgRollbackFeatureActions, hr, E_OUTOFMEMORY, "Failed to allocate memory for rollback feature actions."); + + for (DWORD i = 0; i < pPackage->Msi.cFeatures; ++i) + { + BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; + + // calculate feature actions + rgFeatureActions[i] = pFeature->execute; + rgRollbackFeatureActions[i] = pFeature->rollback; + } + } + + // add wait for cache + if (hCacheEvent) + { + hr = PlanExecuteCacheSyncAndRollback(pPlan, pPackage, hCacheEvent); + ExitOnFailure(hr, "Failed to plan package cache syncpoint"); + } + + hr = DependencyPlanPackage(NULL, pPackage, pPlan); + ExitOnFailure(hr, "Failed to plan package dependency actions."); + + // add rollback action + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback) + { + hr = PlanAppendRollbackAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append rollback action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE; + pAction->msiPackage.pPackage = pPackage; + pAction->msiPackage.action = pPackage->rollback; + pAction->msiPackage.rgFeatures = rgRollbackFeatureActions; + rgRollbackFeatureActions = NULL; + + hr = MsiEngineCalculateInstallUiLevel(display, pUserExperience, pPackage->sczId, FALSE, pAction->msiPackage.action, + &pAction->msiPackage.actionMsiProperty, &pAction->msiPackage.uiLevel, &pAction->msiPackage.fDisableExternalUiHandler); + ExitOnFailure(hr, "Failed to get msi ui options."); + + LoggingSetPackageVariable(pPackage, NULL, TRUE, pLog, pVariables, &pAction->msiPackage.sczLogPath); // ignore errors. + pAction->msiPackage.dwLoggingAttributes = pLog->dwAttributes; + + // Plan a checkpoint between rollback and execute so that we always attempt + // rollback in the case that the MSI was not able to rollback itself (e.g. + // user pushes cancel after InstallFinalize). + hr = PlanExecuteCheckpoint(pPlan); + ExitOnFailure(hr, "Failed to append execute checkpoint."); + } + + // add execute action + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute) + { + hr = PlanAppendExecuteAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append execute action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE; + pAction->msiPackage.pPackage = pPackage; + pAction->msiPackage.action = pPackage->execute; + pAction->msiPackage.rgFeatures = rgFeatureActions; + rgFeatureActions = NULL; + + hr = MsiEngineCalculateInstallUiLevel(display, pUserExperience, pPackage->sczId, TRUE, pAction->msiPackage.action, + &pAction->msiPackage.actionMsiProperty, &pAction->msiPackage.uiLevel, &pAction->msiPackage.fDisableExternalUiHandler); + ExitOnFailure(hr, "Failed to get msi ui options."); + + LoggingSetPackageVariable(pPackage, NULL, FALSE, pLog, pVariables, &pAction->msiPackage.sczLogPath); // ignore errors. + pAction->msiPackage.dwLoggingAttributes = pLog->dwAttributes; + } + +LExit: + ReleaseMem(rgFeatureActions); + ReleaseMem(rgRollbackFeatureActions); + + return hr; +} + +extern "C" HRESULT MsiEngineBeginTransaction( + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ) +{ + HRESULT hr = S_OK; + MSIHANDLE hTransactionHandle = NULL; + HANDLE hChangeOfOwnerEvent = NULL; + + LogId(REPORT_STANDARD, MSG_MSI_TRANSACTION_BEGIN, pRollbackBoundary->sczId); + + hr = WiuBeginTransaction(pRollbackBoundary->sczId, 0, &hTransactionHandle, &hChangeOfOwnerEvent, WIU_LOG_DEFAULT | INSTALLLOGMODE_VERBOSE, pRollbackBoundary->sczLogPath); + + if (HRESULT_FROM_WIN32(ERROR_ROLLBACK_DISABLED) == hr) + { + LogId(REPORT_ERROR, MSG_MSI_TRANSACTIONS_DISABLED); + } + + ExitOnFailure(hr, "Failed to begin an MSI transaction"); + +LExit: + ReleaseMsi(hTransactionHandle); + ReleaseHandle(hChangeOfOwnerEvent); + + return hr; +} + +extern "C" HRESULT MsiEngineCommitTransaction( + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ) +{ + HRESULT hr = S_OK; + + LogId(REPORT_STANDARD, MSG_MSI_TRANSACTION_COMMIT, pRollbackBoundary->sczId); + + hr = WiuEndTransaction(MSITRANSACTIONSTATE_COMMIT, WIU_LOG_DEFAULT | INSTALLLOGMODE_VERBOSE, pRollbackBoundary->sczLogPath); + ExitOnFailure(hr, "Failed to commit the MSI transaction"); + +LExit: + + return hr; +} + +extern "C" HRESULT MsiEngineRollbackTransaction( + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ) +{ + HRESULT hr = S_OK; + + LogId(REPORT_WARNING, MSG_MSI_TRANSACTION_ROLLBACK, pRollbackBoundary->sczId); + + hr = WiuEndTransaction(MSITRANSACTIONSTATE_ROLLBACK, WIU_LOG_DEFAULT | INSTALLLOGMODE_VERBOSE, pRollbackBoundary->sczLogPath); + ExitOnFailure(hr, "Failed to rollback the MSI transaction"); + +LExit: + + return hr; +} + +extern "C" HRESULT MsiEngineExecutePackage( + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + WIU_MSI_EXECUTE_CONTEXT context = { }; + WIU_RESTART restart = WIU_RESTART_NONE; + + LPWSTR sczInstalledVersion = NULL; + LPWSTR sczCachedDirectory = NULL; + LPWSTR sczMsiPath = NULL; + LPWSTR sczProperties = NULL; + LPWSTR sczObfuscatedProperties = NULL; + BURN_PACKAGE* pPackage = pExecuteAction->msiPackage.pPackage; + BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload; + + // During rollback, if the package is already in the rollback state we expect don't + // touch it again. + if (fRollback) + { + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->msiPackage.action) + { + hr = WiuGetProductInfoEx(pPackage->Msi.sczProductCode, NULL, pPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED, INSTALLPROPERTY_VERSIONSTRING, &sczInstalledVersion); + if (FAILED(hr)) // package not present. + { + LogId(REPORT_STANDARD, MSG_ROLLBACK_PACKAGE_SKIPPED, pPackage->sczId, LoggingActionStateToString(pExecuteAction->msiPackage.action), LoggingPackageStateToString(BOOTSTRAPPER_PACKAGE_STATE_ABSENT)); + + hr = S_OK; + ExitFunction(); + } + } + else if (BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->msiPackage.action) + { + hr = WiuGetProductInfoEx(pPackage->Msi.sczProductCode, NULL, pPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED, INSTALLPROPERTY_VERSIONSTRING, &sczInstalledVersion); + if (SUCCEEDED(hr)) // package present. + { + LogId(REPORT_STANDARD, MSG_ROLLBACK_PACKAGE_SKIPPED, pPackage->sczId, LoggingActionStateToString(pExecuteAction->msiPackage.action), LoggingPackageStateToString(BOOTSTRAPPER_PACKAGE_STATE_PRESENT)); + + hr = S_OK; + ExitFunction(); + } + + hr = S_OK; + } + } + + // Default to "verbose" logging and set extra debug mode only if explicitly required. + DWORD dwLogMode = WIU_LOG_DEFAULT | INSTALLLOGMODE_VERBOSE; + + if (pExecuteAction->msiPackage.dwLoggingAttributes & BURN_LOGGING_ATTRIBUTE_EXTRADEBUG) + { + dwLogMode |= INSTALLLOGMODE_EXTRADEBUG; + } + + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL != pExecuteAction->msiPackage.action) + { + // get cached MSI path + hr = CacheGetCompletedPath(pPackage->fPerMachine, pPackage->sczCacheId, &sczCachedDirectory); + ExitOnFailure(hr, "Failed to get cached path for package: %ls", pPackage->sczId); + + // Best effort to set the execute package cache folder variable. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, sczCachedDirectory, TRUE, FALSE); + + hr = PathConcat(sczCachedDirectory, pPackagePayload->sczFilePath, &sczMsiPath); + ExitOnFailure(hr, "Failed to build MSI path."); + } + + // Best effort to set the execute package action variable. + VariableSetNumeric(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, pExecuteAction->msiPackage.action, TRUE); + + // Wire up the external UI handler and logging. + if (pExecuteAction->msiPackage.fDisableExternalUiHandler) + { + hr = WiuInitializeInternalUI(pExecuteAction->msiPackage.uiLevel, hwndParent, &context); + ExitOnFailure(hr, "Failed to initialize internal UI for MSI package."); + } + else + { + hr = WiuInitializeExternalUI(pfnMessageHandler, pExecuteAction->msiPackage.uiLevel, hwndParent, pvContext, fRollback, &context); + ExitOnFailure(hr, "Failed to initialize external UI handler."); + } + + if (pExecuteAction->msiPackage.sczLogPath && *pExecuteAction->msiPackage.sczLogPath) + { + hr = WiuEnableLog(dwLogMode, pExecuteAction->msiPackage.sczLogPath, 0); + ExitOnFailure(hr, "Failed to enable logging for package: %ls to: %ls", pPackage->sczId, pExecuteAction->msiPackage.sczLogPath); + } + + // set up properties + hr = MsiEngineConcatProperties(pPackage->Msi.rgProperties, pPackage->Msi.cProperties, pVariables, fRollback, &sczProperties, FALSE); + ExitOnFailure(hr, "Failed to add properties to argument string."); + + hr = MsiEngineConcatProperties(pPackage->Msi.rgProperties, pPackage->Msi.cProperties, pVariables, fRollback, &sczObfuscatedProperties, TRUE); + ExitOnFailure(hr, "Failed to add obfuscated properties to argument string."); + + // add feature action properties + hr = ConcatFeatureActionProperties(pPackage, pExecuteAction->msiPackage.rgFeatures, &sczProperties); + ExitOnFailure(hr, "Failed to add feature action properties to argument string."); + + hr = ConcatFeatureActionProperties(pPackage, pExecuteAction->msiPackage.rgFeatures, &sczObfuscatedProperties); + ExitOnFailure(hr, "Failed to add feature action properties to obfuscated argument string."); + + // add slipstream patch properties + hr = ConcatPatchProperty(pPackage, fRollback, &sczProperties); + ExitOnFailure(hr, "Failed to add patch properties to argument string."); + + hr = ConcatPatchProperty(pPackage, fRollback, &sczObfuscatedProperties); + ExitOnFailure(hr, "Failed to add patch properties to obfuscated argument string."); + + hr = MsiEngineConcatActionProperty(pExecuteAction->msiPackage.actionMsiProperty, &sczProperties); + ExitOnFailure(hr, "Failed to add action property to argument string."); + + hr = MsiEngineConcatActionProperty(pExecuteAction->msiPackage.actionMsiProperty, &sczObfuscatedProperties); + ExitOnFailure(hr, "Failed to add action property to obfuscated argument string."); + + LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, LoggingRollbackOrExecute(fRollback), pPackage->sczId, LoggingActionStateToString(pExecuteAction->msiPackage.action), sczMsiPath, sczObfuscatedProperties ? sczObfuscatedProperties : L""); + + // + // Do the actual action. + // + switch (pExecuteAction->msiPackage.action) + { + case BOOTSTRAPPER_ACTION_STATE_INSTALL: + hr = StrAllocConcatSecure(&sczProperties, L" REBOOT=ReallySuppress", 0); + ExitOnFailure(hr, "Failed to add reboot suppression property on install."); + + hr = WiuInstallProduct(sczMsiPath, sczProperties, &restart); + ExitOnFailure(hr, "Failed to install MSI package."); + + RegisterSourceDirectory(pPackage, sczMsiPath); + break; + + case BOOTSTRAPPER_ACTION_STATE_MINOR_UPGRADE: + // If feature selection is not enabled, then reinstall the existing features to ensure they get + // updated. + if (0 == pPackage->Msi.cFeatures) + { + hr = StrAllocConcatSecure(&sczProperties, L" REINSTALL=ALL", 0); + ExitOnFailure(hr, "Failed to add reinstall all property on minor upgrade."); + } + + hr = StrAllocConcatSecure(&sczProperties, L" REINSTALLMODE=\"vomus\" REBOOT=ReallySuppress", 0); + ExitOnFailure(hr, "Failed to add reinstall mode and reboot suppression properties on minor upgrade."); + + hr = WiuInstallProduct(sczMsiPath, sczProperties, &restart); + ExitOnFailure(hr, "Failed to perform minor upgrade of MSI package."); + + RegisterSourceDirectory(pPackage, sczMsiPath); + break; + + case BOOTSTRAPPER_ACTION_STATE_MODIFY: __fallthrough; + case BOOTSTRAPPER_ACTION_STATE_MEND: __fallthrough; + case BOOTSTRAPPER_ACTION_STATE_REPAIR: + { + LPCWSTR wzReinstallAll = (BOOTSTRAPPER_ACTION_STATE_MODIFY == pExecuteAction->msiPackage.action || + pPackage->Msi.cFeatures) ? L"" : L" REINSTALL=ALL"; + LPCWSTR wzReinstallMode = (BOOTSTRAPPER_ACTION_STATE_MODIFY == pExecuteAction->msiPackage.action || BOOTSTRAPPER_ACTION_STATE_MEND == pExecuteAction->msiPackage.action) ? L"o" : L"e"; + + hr = StrAllocFormattedSecure(&sczProperties, L"%ls%ls REINSTALLMODE=\"cmus%ls\" REBOOT=ReallySuppress", sczProperties ? sczProperties : L"", wzReinstallAll, wzReinstallMode); + ExitOnFailure(hr, "Failed to add reinstall mode and reboot suppression properties on repair."); + } + + // Ignore all dependencies, since the Burn engine already performed the check. + hr = StrAllocFormattedSecure(&sczProperties, L"%ls %ls=ALL", sczProperties, DEPENDENCY_IGNOREDEPENDENCIES); + ExitOnFailure(hr, "Failed to add the list of dependencies to ignore to the properties."); + + hr = WiuInstallProduct(sczMsiPath, sczProperties, &restart); + ExitOnFailure(hr, "Failed to run maintenance mode for MSI package."); + break; + + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + hr = StrAllocConcatSecure(&sczProperties, L" REBOOT=ReallySuppress", 0); + ExitOnFailure(hr, "Failed to add reboot suppression property on uninstall."); + + // Ignore all dependencies, since the Burn engine already performed the check. + hr = StrAllocFormattedSecure(&sczProperties, L"%ls %ls=ALL", sczProperties, DEPENDENCY_IGNOREDEPENDENCIES); + ExitOnFailure(hr, "Failed to add the list of dependencies to ignore to the properties."); + + hr = WiuConfigureProductEx(pPackage->Msi.sczProductCode, INSTALLLEVEL_DEFAULT, INSTALLSTATE_ABSENT, sczProperties, &restart); + if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr) + { + LogId(REPORT_STANDARD, MSG_ATTEMPTED_UNINSTALL_ABSENT_PACKAGE, pPackage->sczId); + hr = S_OK; + } + ExitOnFailure(hr, "Failed to uninstall MSI package."); + break; + } + +LExit: + WiuUninitializeExternalUI(&context); + + StrSecureZeroFreeString(sczProperties); + ReleaseStr(sczObfuscatedProperties); + ReleaseStr(sczMsiPath); + ReleaseStr(sczCachedDirectory); + ReleaseStr(sczInstalledVersion); + + switch (restart) + { + case WIU_RESTART_NONE: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; + break; + + case WIU_RESTART_REQUIRED: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_REQUIRED; + break; + + case WIU_RESTART_INITIATED: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_INITIATED; + break; + } + + // Best effort to clear the execute package cache folder and action variables. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, NULL, TRUE, FALSE); + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, NULL, TRUE, FALSE); + + return hr; +} + +extern "C" HRESULT MsiEngineConcatActionProperty( + __in BURN_MSI_PROPERTY actionMsiProperty, + __deref_out_z LPWSTR* psczProperties + ) +{ + HRESULT hr = S_OK; + LPCWSTR wzPropertyName = NULL; + + switch (actionMsiProperty) + { + case BURN_MSI_PROPERTY_INSTALL: + wzPropertyName = BURNMSIINSTALL_PROPERTY_NAME; + break; + case BURN_MSI_PROPERTY_MODIFY: + wzPropertyName = BURNMSIMODIFY_PROPERTY_NAME; + break; + case BURN_MSI_PROPERTY_REPAIR: + wzPropertyName = BURNMSIREPAIR_PROPERTY_NAME; + break; + case BURN_MSI_PROPERTY_UNINSTALL: + wzPropertyName = BURNMSIUNINSTALL_PROPERTY_NAME; + break; + } + + if (wzPropertyName) + { + hr = StrAllocConcatFormattedSecure(psczProperties, L" %ls=1", wzPropertyName); + ExitOnFailure(hr, "Failed to add burn action property."); + } + +LExit: + return hr; +} + +extern "C" HRESULT MsiEngineConcatProperties( + __in_ecount(cProperties) BURN_MSIPROPERTY* rgProperties, + __in DWORD cProperties, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __deref_out_z LPWSTR* psczProperties, + __in BOOL fObfuscateHiddenVariables + ) +{ + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + LPWSTR sczEscapedValue = NULL; + LPWSTR sczProperty = NULL; + + for (DWORD i = 0; i < cProperties; ++i) + { + BURN_MSIPROPERTY* pProperty = &rgProperties[i]; + + if (pProperty->sczCondition && *pProperty->sczCondition) + { + BOOL fCondition = FALSE; + + hr = ConditionEvaluate(pVariables, pProperty->sczCondition, &fCondition); + if (FAILED(hr) || !fCondition) + { + LogId(REPORT_VERBOSE, MSG_MSI_PROPERTY_CONDITION_FAILED, pProperty->sczId, pProperty->sczCondition, LoggingTrueFalseToString(fCondition)); + continue; + } + } + + // format property value + if (fObfuscateHiddenVariables) + { + hr = VariableFormatStringObfuscated(pVariables, (fRollback && pProperty->sczRollbackValue) ? pProperty->sczRollbackValue : pProperty->sczValue, &sczValue, NULL); + } + else + { + hr = VariableFormatString(pVariables, (fRollback && pProperty->sczRollbackValue) ? pProperty->sczRollbackValue : pProperty->sczValue, &sczValue, NULL); + ExitOnFailure(hr, "Failed to format property value."); + } + ExitOnFailure(hr, "Failed to format property value."); + + // escape property value + hr = EscapePropertyArgumentString(sczValue, &sczEscapedValue, !fObfuscateHiddenVariables); + ExitOnFailure(hr, "Failed to escape string."); + + // build part + hr = VariableStrAllocFormatted(!fObfuscateHiddenVariables, &sczProperty, L" %s%=\"%s\"", pProperty->sczId, sczEscapedValue); + ExitOnFailure(hr, "Failed to format property string part."); + + // append to property string + hr = VariableStrAllocConcat(!fObfuscateHiddenVariables, psczProperties, sczProperty, 0); + ExitOnFailure(hr, "Failed to append property string part."); + } + +LExit: + StrSecureZeroFreeString(sczValue); + StrSecureZeroFreeString(sczEscapedValue); + StrSecureZeroFreeString(sczProperty); + return hr; +} + +extern "C" HRESULT MsiEngineCalculateInstallUiLevel( + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzPackageId, + __in BOOL fExecute, + __in BOOTSTRAPPER_ACTION_STATE actionState, + __out BURN_MSI_PROPERTY* pActionMsiProperty, + __out INSTALLUILEVEL* pUiLevel, + __out BOOL* pfDisableExternalUiHandler + ) +{ + *pUiLevel = INSTALLUILEVEL_NONE; + *pfDisableExternalUiHandler = FALSE; + + if (BOOTSTRAPPER_DISPLAY_FULL == display || + BOOTSTRAPPER_DISPLAY_PASSIVE == display) + { + *pUiLevel = static_cast(*pUiLevel | INSTALLUILEVEL_SOURCERESONLY); + } + + switch (actionState) + { + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + *pActionMsiProperty = BURN_MSI_PROPERTY_UNINSTALL; + break; + case BOOTSTRAPPER_ACTION_STATE_REPAIR: + *pActionMsiProperty = BURN_MSI_PROPERTY_REPAIR; + break; + case BOOTSTRAPPER_ACTION_STATE_MODIFY: + *pActionMsiProperty = BURN_MSI_PROPERTY_MODIFY; + break; + default: + *pActionMsiProperty = BURN_MSI_PROPERTY_INSTALL; + break; + } + + return UserExperienceOnPlanMsiPackage(pUserExperience, wzPackageId, fExecute, actionState, pActionMsiProperty, pUiLevel, pfDisableExternalUiHandler); +} + +extern "C" void MsiEngineUpdateInstallRegistrationState( + __in BURN_EXECUTE_ACTION* pAction, + __in BOOL fRollback, + __in HRESULT hrExecute, + __in BOOL fInsideMsiTransaction + ) +{ + BURN_PACKAGE_REGISTRATION_STATE newState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + BURN_PACKAGE* pPackage = pAction->msiPackage.pPackage; + + if (FAILED(hrExecute) || !pPackage->fCanAffectRegistration) + { + ExitFunction(); + } + + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pAction->msiPackage.action) + { + newState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + else + { + newState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + + if (fInsideMsiTransaction) + { + pPackage->transactionRegistrationState = newState; + } + else + { + pPackage->installRegistrationState = newState; + } + + if (BURN_PACKAGE_REGISTRATION_STATE_ABSENT == newState) + { + for (DWORD i = 0; i < pPackage->Msi.cChainedPatches; ++i) + { + BURN_CHAINED_PATCH* pChainedPatch = pPackage->Msi.rgChainedPatches + i; + BURN_MSPTARGETPRODUCT* pTargetProduct = pChainedPatch->pMspPackage->Msp.rgTargetProducts + pChainedPatch->dwMspTargetProductIndex; + + if (fInsideMsiTransaction) + { + pTargetProduct->transactionRegistrationState = newState; + } + else + { + pTargetProduct->registrationState = newState; + } + } + } + else + { + for (DWORD i = 0; i < pPackage->Msi.cSlipstreamMspPackages; ++i) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pPackage->Msi.rgSlipstreamMsps + i; + BOOTSTRAPPER_ACTION_STATE patchExecuteAction = fRollback ? pSlipstreamMsp->rollback : pSlipstreamMsp->execute; + + if (BOOTSTRAPPER_ACTION_STATE_INSTALL > patchExecuteAction) + { + continue; + } + + BURN_CHAINED_PATCH* pChainedPatch = pPackage->Msi.rgChainedPatches + pSlipstreamMsp->dwMsiChainedPatchIndex; + BURN_MSPTARGETPRODUCT* pTargetProduct = pChainedPatch->pMspPackage->Msp.rgTargetProducts + pChainedPatch->dwMspTargetProductIndex; + + if (fInsideMsiTransaction) + { + pTargetProduct->transactionRegistrationState = newState; + } + else + { + pTargetProduct->registrationState = newState; + } + } + } + +LExit: + return; +} + + +// internal helper functions + +static HRESULT ParseRelatedMsiFromXml( + __in IXMLDOMNode* pixnRelatedMsi, + __in BURN_RELATED_MSI* pRelatedMsi + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + LPWSTR scz = NULL; + + // @Id + hr = XmlGetAttributeEx(pixnRelatedMsi, L"Id", &pRelatedMsi->sczUpgradeCode); + ExitOnFailure(hr, "Failed to get @Id."); + + // @MinVersion + hr = XmlGetAttributeEx(pixnRelatedMsi, L"MinVersion", &scz); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @MinVersion."); + + hr = VerParseVersion(scz, 0, FALSE, &pRelatedMsi->pMinVersion); + ExitOnFailure(hr, "Failed to parse @MinVersion: %ls", scz); + + if (pRelatedMsi->pMinVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_MANIFEST_INVALID_VERSION, scz); + } + + // flag that we have a min version + pRelatedMsi->fMinProvided = TRUE; + + // @MinInclusive + hr = XmlGetYesNoAttribute(pixnRelatedMsi, L"MinInclusive", &pRelatedMsi->fMinInclusive); + ExitOnFailure(hr, "Failed to get @MinInclusive."); + } + + // @MaxVersion + hr = XmlGetAttributeEx(pixnRelatedMsi, L"MaxVersion", &scz); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @MaxVersion."); + + hr = VerParseVersion(scz, 0, FALSE, &pRelatedMsi->pMaxVersion); + ExitOnFailure(hr, "Failed to parse @MaxVersion: %ls", scz); + + if (pRelatedMsi->pMaxVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_MANIFEST_INVALID_VERSION, scz); + } + + // flag that we have a max version + pRelatedMsi->fMaxProvided = TRUE; + + // @MaxInclusive + hr = XmlGetYesNoAttribute(pixnRelatedMsi, L"MaxInclusive", &pRelatedMsi->fMaxInclusive); + ExitOnFailure(hr, "Failed to get @MaxInclusive."); + } + + // @OnlyDetect + hr = XmlGetYesNoAttribute(pixnRelatedMsi, L"OnlyDetect", &pRelatedMsi->fOnlyDetect); + ExitOnFailure(hr, "Failed to get @OnlyDetect."); + + // select language nodes + hr = XmlSelectNodes(pixnRelatedMsi, L"Language", &pixnNodes); + ExitOnFailure(hr, "Failed to select language nodes."); + + // get language node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get language node count."); + + if (cNodes) + { + // @LangInclusive + hr = XmlGetYesNoAttribute(pixnRelatedMsi, L"LangInclusive", &pRelatedMsi->fLangInclusive); + ExitOnFailure(hr, "Failed to get @LangInclusive."); + + // allocate memory for language IDs + pRelatedMsi->rgdwLanguages = (DWORD*)MemAlloc(sizeof(DWORD) * cNodes, TRUE); + ExitOnNull(pRelatedMsi->rgdwLanguages, hr, E_OUTOFMEMORY, "Failed to allocate memory for language IDs."); + + pRelatedMsi->cLanguages = cNodes; + + // parse language elements + for (DWORD i = 0; i < cNodes; ++i) + { + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeNumber(pixnNode, L"Id", &pRelatedMsi->rgdwLanguages[i]); + ExitOnFailure(hr, "Failed to get Language/@Id."); + + // prepare next iteration + ReleaseNullObject(pixnNode); + } + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(scz); + + return hr; +} + +static HRESULT EvaluateActionStateConditions( + __in BURN_VARIABLES* pVariables, + __in_z_opt LPCWSTR sczAddLocalCondition, + __in_z_opt LPCWSTR sczAddSourceCondition, + __in_z_opt LPCWSTR sczAdvertiseCondition, + __out BOOTSTRAPPER_FEATURE_STATE* pState + ) +{ + HRESULT hr = S_OK; + BOOL fCondition = FALSE; + + // if no condition was set, return no feature state + if (!sczAddLocalCondition && !sczAddSourceCondition && !sczAdvertiseCondition) + { + *pState = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; + ExitFunction(); + } + + if (sczAddLocalCondition) + { + hr = ConditionEvaluate(pVariables, sczAddLocalCondition, &fCondition); + ExitOnFailure(hr, "Failed to evaluate add local condition."); + + if (fCondition) + { + *pState = BOOTSTRAPPER_FEATURE_STATE_LOCAL; + ExitFunction(); + } + } + + if (sczAddSourceCondition) + { + hr = ConditionEvaluate(pVariables, sczAddSourceCondition, &fCondition); + ExitOnFailure(hr, "Failed to evaluate add source condition."); + + if (fCondition) + { + *pState = BOOTSTRAPPER_FEATURE_STATE_SOURCE; + ExitFunction(); + } + } + + if (sczAdvertiseCondition) + { + hr = ConditionEvaluate(pVariables, sczAdvertiseCondition, &fCondition); + ExitOnFailure(hr, "Failed to evaluate advertise condition."); + + if (fCondition) + { + *pState = BOOTSTRAPPER_FEATURE_STATE_ADVERTISED; + ExitFunction(); + } + } + + // if no condition was true, set to absent + *pState = BOOTSTRAPPER_FEATURE_STATE_ABSENT; + +LExit: + return hr; +} + +static HRESULT CalculateFeatureAction( + __in BOOTSTRAPPER_FEATURE_STATE currentState, + __in BOOTSTRAPPER_FEATURE_STATE requestedState, + __in BOOL fRepair, + __out BOOTSTRAPPER_FEATURE_ACTION* pFeatureAction, + __inout BOOL* pfDelta + ) +{ + HRESULT hr = S_OK; + + *pFeatureAction = BOOTSTRAPPER_FEATURE_ACTION_NONE; + switch (requestedState) + { + case BOOTSTRAPPER_FEATURE_STATE_UNKNOWN: + *pFeatureAction = BOOTSTRAPPER_FEATURE_ACTION_NONE; + break; + + case BOOTSTRAPPER_FEATURE_STATE_ABSENT: + if (BOOTSTRAPPER_FEATURE_STATE_ABSENT != currentState) + { + *pFeatureAction = BOOTSTRAPPER_FEATURE_ACTION_REMOVE; + } + break; + + case BOOTSTRAPPER_FEATURE_STATE_ADVERTISED: + if (BOOTSTRAPPER_FEATURE_STATE_ADVERTISED != currentState) + { + *pFeatureAction = BOOTSTRAPPER_FEATURE_ACTION_ADVERTISE; + } + else if (fRepair) + { + *pFeatureAction = BOOTSTRAPPER_FEATURE_ACTION_REINSTALL; + } + break; + + case BOOTSTRAPPER_FEATURE_STATE_LOCAL: + if (BOOTSTRAPPER_FEATURE_STATE_LOCAL != currentState) + { + *pFeatureAction = BOOTSTRAPPER_FEATURE_ACTION_ADDLOCAL; + } + else if (fRepair) + { + *pFeatureAction = BOOTSTRAPPER_FEATURE_ACTION_REINSTALL; + } + break; + + case BOOTSTRAPPER_FEATURE_STATE_SOURCE: + if (BOOTSTRAPPER_FEATURE_STATE_SOURCE != currentState) + { + *pFeatureAction = BOOTSTRAPPER_FEATURE_ACTION_ADDSOURCE; + } + else if (fRepair) + { + *pFeatureAction = BOOTSTRAPPER_FEATURE_ACTION_REINSTALL; + } + break; + + default: + hr = E_UNEXPECTED; + ExitOnRootFailure(hr, "Invalid state value."); + } + + if (BOOTSTRAPPER_FEATURE_ACTION_NONE != *pFeatureAction) + { + *pfDelta = TRUE; + } + +LExit: + return hr; +} + +static HRESULT EscapePropertyArgumentString( + __in LPCWSTR wzProperty, + __inout_z LPWSTR* psczEscapedValue, + __in BOOL fZeroOnRealloc + ) +{ + HRESULT hr = S_OK; + DWORD cch = 0; + DWORD cchEscape = 0; + LPCWSTR wzSource = NULL; + LPWSTR wzTarget = NULL; + + // count characters to escape + wzSource = wzProperty; + while (*wzSource) + { + ++cch; + if (L'\"' == *wzSource) + { + ++cchEscape; + } + ++wzSource; + } + + // allocate target buffer + hr = VariableStrAlloc(fZeroOnRealloc, psczEscapedValue, cch + cchEscape + 1); // character count, plus escape character count, plus null terminator + ExitOnFailure(hr, "Failed to allocate string buffer."); + + // write to target buffer + wzSource = wzProperty; + wzTarget = *psczEscapedValue; + while (*wzSource) + { + *wzTarget = *wzSource; + if (L'\"' == *wzTarget) + { + ++wzTarget; + *wzTarget = L'\"'; + } + + ++wzSource; + ++wzTarget; + } + + *wzTarget = L'\0'; // add null terminator + +LExit: + return hr; +} + +static HRESULT ConcatFeatureActionProperties( + __in BURN_PACKAGE* pPackage, + __in BOOTSTRAPPER_FEATURE_ACTION* rgFeatureActions, + __inout_z LPWSTR* psczArguments + ) +{ + HRESULT hr = S_OK; + LPWSTR scz = NULL; + LPWSTR sczAddLocal = NULL; + LPWSTR sczAddSource = NULL; + LPWSTR sczAddDefault = NULL; + LPWSTR sczReinstall = NULL; + LPWSTR sczAdvertise = NULL; + LPWSTR sczRemove = NULL; + + // features + for (DWORD i = 0; i < pPackage->Msi.cFeatures; ++i) + { + BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; + + switch (rgFeatureActions[i]) + { + case BOOTSTRAPPER_FEATURE_ACTION_ADDLOCAL: + if (sczAddLocal) + { + hr = StrAllocConcat(&sczAddLocal, L",", 0); + ExitOnFailure(hr, "Failed to concat separator."); + } + hr = StrAllocConcat(&sczAddLocal, pFeature->sczId, 0); + ExitOnFailure(hr, "Failed to concat feature."); + break; + + case BOOTSTRAPPER_FEATURE_ACTION_ADDSOURCE: + if (sczAddSource) + { + hr = StrAllocConcat(&sczAddSource, L",", 0); + ExitOnFailure(hr, "Failed to concat separator."); + } + hr = StrAllocConcat(&sczAddSource, pFeature->sczId, 0); + ExitOnFailure(hr, "Failed to concat feature."); + break; + + case BOOTSTRAPPER_FEATURE_ACTION_ADDDEFAULT: + if (sczAddDefault) + { + hr = StrAllocConcat(&sczAddDefault, L",", 0); + ExitOnFailure(hr, "Failed to concat separator."); + } + hr = StrAllocConcat(&sczAddDefault, pFeature->sczId, 0); + ExitOnFailure(hr, "Failed to concat feature."); + break; + + case BOOTSTRAPPER_FEATURE_ACTION_REINSTALL: + if (sczReinstall) + { + hr = StrAllocConcat(&sczReinstall, L",", 0); + ExitOnFailure(hr, "Failed to concat separator."); + } + hr = StrAllocConcat(&sczReinstall, pFeature->sczId, 0); + ExitOnFailure(hr, "Failed to concat feature."); + break; + + case BOOTSTRAPPER_FEATURE_ACTION_ADVERTISE: + if (sczAdvertise) + { + hr = StrAllocConcat(&sczAdvertise, L",", 0); + ExitOnFailure(hr, "Failed to concat separator."); + } + hr = StrAllocConcat(&sczAdvertise, pFeature->sczId, 0); + ExitOnFailure(hr, "Failed to concat feature."); + break; + + case BOOTSTRAPPER_FEATURE_ACTION_REMOVE: + if (sczRemove) + { + hr = StrAllocConcat(&sczRemove, L",", 0); + ExitOnFailure(hr, "Failed to concat separator."); + } + hr = StrAllocConcat(&sczRemove, pFeature->sczId, 0); + ExitOnFailure(hr, "Failed to concat feature."); + break; + } + } + + if (sczAddLocal) + { + hr = StrAllocFormatted(&scz, L" ADDLOCAL=\"%s\"", sczAddLocal, 0); + ExitOnFailure(hr, "Failed to format ADDLOCAL string."); + + hr = StrAllocConcatSecure(psczArguments, scz, 0); + ExitOnFailure(hr, "Failed to concat argument string."); + } + + if (sczAddSource) + { + hr = StrAllocFormatted(&scz, L" ADDSOURCE=\"%s\"", sczAddSource, 0); + ExitOnFailure(hr, "Failed to format ADDSOURCE string."); + + hr = StrAllocConcatSecure(psczArguments, scz, 0); + ExitOnFailure(hr, "Failed to concat argument string."); + } + + if (sczAddDefault) + { + hr = StrAllocFormatted(&scz, L" ADDDEFAULT=\"%s\"", sczAddDefault, 0); + ExitOnFailure(hr, "Failed to format ADDDEFAULT string."); + + hr = StrAllocConcatSecure(psczArguments, scz, 0); + ExitOnFailure(hr, "Failed to concat argument string."); + } + + if (sczReinstall) + { + hr = StrAllocFormatted(&scz, L" REINSTALL=\"%s\"", sczReinstall, 0); + ExitOnFailure(hr, "Failed to format REINSTALL string."); + + hr = StrAllocConcatSecure(psczArguments, scz, 0); + ExitOnFailure(hr, "Failed to concat argument string."); + } + + if (sczAdvertise) + { + hr = StrAllocFormatted(&scz, L" ADVERTISE=\"%s\"", sczAdvertise, 0); + ExitOnFailure(hr, "Failed to format ADVERTISE string."); + + hr = StrAllocConcatSecure(psczArguments, scz, 0); + ExitOnFailure(hr, "Failed to concat argument string."); + } + + if (sczRemove) + { + hr = StrAllocFormatted(&scz, L" REMOVE=\"%s\"", sczRemove, 0); + ExitOnFailure(hr, "Failed to format REMOVE string."); + + hr = StrAllocConcatSecure(psczArguments, scz, 0); + ExitOnFailure(hr, "Failed to concat argument string."); + } + +LExit: + ReleaseStr(scz); + ReleaseStr(sczAddLocal); + ReleaseStr(sczAddSource); + ReleaseStr(sczAddDefault); + ReleaseStr(sczReinstall); + ReleaseStr(sczAdvertise); + ReleaseStr(sczRemove); + + return hr; +} + +static HRESULT ConcatPatchProperty( + __in BURN_PACKAGE* pPackage, + __in BOOL fRollback, + __inout_z LPWSTR* psczArguments + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCachedDirectory = NULL; + LPWSTR sczMspPath = NULL; + LPWSTR sczPatches = NULL; + + // If there are slipstream patch actions, build up their patch action. + if (pPackage->Msi.cSlipstreamMspPackages) + { + for (DWORD i = 0; i < pPackage->Msi.cSlipstreamMspPackages; ++i) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pPackage->Msi.rgSlipstreamMsps + i; + BURN_PACKAGE* pMspPackage = pSlipstreamMsp->pMspPackage; + BURN_PAYLOAD* pMspPackagePayload = pMspPackage->payloads.rgItems[0].pPayload; + BOOTSTRAPPER_ACTION_STATE patchExecuteAction = fRollback ? pSlipstreamMsp->rollback : pSlipstreamMsp->execute; + + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL < patchExecuteAction) + { + hr = CacheGetCompletedPath(pMspPackage->fPerMachine, pMspPackage->sczCacheId, &sczCachedDirectory); + ExitOnFailure(hr, "Failed to get cached path for MSP package: %ls", pMspPackage->sczId); + + hr = PathConcat(sczCachedDirectory, pMspPackagePayload->sczFilePath, &sczMspPath); + ExitOnFailure(hr, "Failed to build MSP path."); + + if (!sczPatches) + { + hr = StrAllocConcat(&sczPatches, L" PATCH=\"", 0); + ExitOnFailure(hr, "Failed to prefix with PATCH property."); + } + else + { + hr = StrAllocConcat(&sczPatches, L";", 0); + ExitOnFailure(hr, "Failed to semi-colon delimit patches."); + } + + hr = StrAllocConcat(&sczPatches, sczMspPath, 0); + ExitOnFailure(hr, "Failed to append patch path."); + } + } + + if (sczPatches) + { + hr = StrAllocConcat(&sczPatches, L"\"", 0); + ExitOnFailure(hr, "Failed to close the quoted PATCH property."); + + hr = StrAllocConcatSecure(psczArguments, sczPatches, 0); + ExitOnFailure(hr, "Failed to append PATCH property."); + } + } + +LExit: + ReleaseStr(sczMspPath); + ReleaseStr(sczCachedDirectory); + ReleaseStr(sczPatches); + return hr; +} + +static void RegisterSourceDirectory( + __in BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzMsiPath + ) +{ + HRESULT hr = S_OK; + LPWSTR sczMsiDirectory = NULL; + MSIINSTALLCONTEXT dwContext = pPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED; + + hr = PathGetDirectory(wzMsiPath, &sczMsiDirectory); + ExitOnFailure(hr, "Failed to get directory for path: %ls", wzMsiPath); + + hr = WiuSourceListAddSourceEx(pPackage->Msi.sczProductCode, NULL, dwContext, MSICODE_PRODUCT, sczMsiDirectory, 1); + if (FAILED(hr)) + { + LogId(REPORT_VERBOSE, MSG_SOURCELIST_REGISTER, sczMsiDirectory, pPackage->Msi.sczProductCode, hr); + ExitFunction(); + } + +LExit: + ReleaseStr(sczMsiDirectory); + + return; +} diff --git a/src/burn/engine/msiengine.h b/src/burn/engine/msiengine.h new file mode 100644 index 00000000..8b5bcdd0 --- /dev/null +++ b/src/burn/engine/msiengine.h @@ -0,0 +1,104 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +// constants +#define BURNMSIINSTALL_PROPERTY_NAME L"BURNMSIINSTALL" +#define BURNMSIMODIFY_PROPERTY_NAME L"BURNMSIMODIFY" +#define BURNMSIREPAIR_PROPERTY_NAME L"BURNMSIREPAIR" +#define BURNMSIUNINSTALL_PROPERTY_NAME L"BURNMSIUNINSTALL" + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// function declarations + +HRESULT MsiEngineParsePackageFromXml( + __in IXMLDOMNode* pixnBundle, + __in BURN_PACKAGE* pPackage + ); +HRESULT MsiEngineParsePropertiesFromXml( + __in IXMLDOMNode* pixnPackage, + __out BURN_MSIPROPERTY** prgProperties, + __out DWORD* pcProperties + ); +void MsiEnginePackageUninitialize( + __in BURN_PACKAGE* pPackage + ); +HRESULT MsiEngineDetectInitialize( + __in BURN_PACKAGES* pPackages + ); +HRESULT MsiEngineDetectPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_USER_EXPERIENCE* pUserExperience + ); +HRESULT MsiEnginePlanInitializePackage( + __in BURN_PACKAGE* pPackage, + __in BURN_VARIABLES* pVariables, + __in BURN_USER_EXPERIENCE* pUserExperience + ); +HRESULT MsiEnginePlanCalculatePackage( + __in BURN_PACKAGE* pPackage, + __in BOOL fInsideMsiTransaction + ); +HRESULT MsiEnginePlanAddPackage( + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in_opt HANDLE hCacheEvent + ); +HRESULT MsiEngineBeginTransaction( + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ); +HRESULT MsiEngineCommitTransaction( + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ); +HRESULT MsiEngineRollbackTransaction( + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ); +HRESULT MsiEngineExecutePackage( + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +HRESULT MsiEngineConcatActionProperty( + __in BURN_MSI_PROPERTY actionMsiProperty, + __deref_out_z LPWSTR* psczProperties + ); +HRESULT MsiEngineConcatProperties( + __in_ecount(cProperties) BURN_MSIPROPERTY* rgProperties, + __in DWORD cProperties, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __deref_out_z LPWSTR* psczProperties, + __in BOOL fObfuscateHiddenVariables + ); +HRESULT MsiEngineCalculateInstallUiLevel( + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzPackageId, + __in BOOL fExecute, + __in BOOTSTRAPPER_ACTION_STATE actionState, + __out BURN_MSI_PROPERTY* pActionMsiProperty, + __out INSTALLUILEVEL* pUiLevel, + __out BOOL* pfDisableExternalUiHandler + ); +void MsiEngineUpdateInstallRegistrationState( + __in BURN_EXECUTE_ACTION* pAction, + __in BOOL fRollback, + __in HRESULT hrExecute, + __in BOOL fInsideMsiTransaction + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/mspengine.cpp b/src/burn/engine/mspengine.cpp new file mode 100644 index 00000000..6d58d324 --- /dev/null +++ b/src/burn/engine/mspengine.cpp @@ -0,0 +1,1197 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// constants + + +// structs + +struct POSSIBLE_TARGETPRODUCT +{ + WCHAR wzProductCode[39]; + LPWSTR pszLocalPackage; + MSIINSTALLCONTEXT context; +}; + +// internal function declarations + +static HRESULT GetPossibleTargetProductCodes( + __in BURN_PACKAGES* pPackages, + __deref_inout_ecount_opt(*pcPossibleTargetProductCodes) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProductCodes, + __inout DWORD* pcPossibleTargetProductCodes + ); +static HRESULT AddPossibleTargetProduct( + __in STRINGDICT_HANDLE sdUniquePossibleTargetProductCodes, + __in_z LPCWSTR wzPossibleTargetProductCode, + __in MSIINSTALLCONTEXT context, + __deref_inout_ecount_opt(*pcPossibleTargetProducts) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProducts, + __inout DWORD* pcPossibleTargetProducts + ); +static HRESULT AddDetectedTargetProduct( + __in BURN_PACKAGE* pPackage, + __in DWORD dwOrder, + __in_z LPCWSTR wzProductCode, + __in MSIINSTALLCONTEXT context, + __out DWORD* pdwTargetProductIndex + ); +static HRESULT AddMsiChainedPatch( + __in BURN_PACKAGE* pPackage, + __in BURN_PACKAGE* pMspPackage, + __in DWORD dwMspTargetProductIndex, + __out DWORD* pdwChainedPatchIndex + ); +static HRESULT DeterminePatchChainedTarget( + __in BURN_PACKAGES* pPackages, + __in BURN_PACKAGE* pMspPackage, + __in LPCWSTR wzTargetProductCode, + __in DWORD dwMspTargetProductIndex + ); +static HRESULT PlanTargetProduct( + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fRollback, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_ACTION_STATE actionState, + __in BURN_PACKAGE* pPackage, + __in BURN_MSPTARGETPRODUCT* pTargetProduct, + __in_opt HANDLE hCacheEvent + ); + + +// function definitions + +extern "C" HRESULT MspEngineParsePackageFromXml( + __in IXMLDOMNode* pixnMspPackage, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + + // @PatchCode + hr = XmlGetAttributeEx(pixnMspPackage, L"PatchCode", &pPackage->Msp.sczPatchCode); + ExitOnFailure(hr, "Failed to get @PatchCode."); + + // @PatchXml + hr = XmlGetAttributeEx(pixnMspPackage, L"PatchXml", &pPackage->Msp.sczApplicabilityXml); + ExitOnFailure(hr, "Failed to get @PatchXml."); + + // Read properties. + hr = MsiEngineParsePropertiesFromXml(pixnMspPackage, &pPackage->Msp.rgProperties, &pPackage->Msp.cProperties); + ExitOnFailure(hr, "Failed to parse properties from XML."); + +LExit: + + return hr; +} + +extern "C" void MspEnginePackageUninitialize( + __in BURN_PACKAGE* pPackage + ) +{ + ReleaseStr(pPackage->Msp.sczPatchCode); + ReleaseStr(pPackage->Msp.sczApplicabilityXml); + + // free properties + if (pPackage->Msp.rgProperties) + { + for (DWORD i = 0; i < pPackage->Msp.cProperties; ++i) + { + BURN_MSIPROPERTY* pProperty = &pPackage->Msp.rgProperties[i]; + + ReleaseStr(pProperty->sczId); + ReleaseStr(pProperty->sczValue); + ReleaseStr(pProperty->sczRollbackValue); + } + MemFree(pPackage->Msp.rgProperties); + } + + // free target products + ReleaseMem(pPackage->Msp.rgTargetProducts); + + // clear struct + memset(&pPackage->Msp, 0, sizeof(pPackage->Msp)); +} + +extern "C" HRESULT MspEngineDetectInitialize( + __in BURN_PACKAGES* pPackages + ) +{ + AssertSz(pPackages->cPatchInfo, "MspEngineDetectInitialize() should only be called if there are MSP packages."); + + HRESULT hr = S_OK; + POSSIBLE_TARGETPRODUCT* rgPossibleTargetProducts = NULL; + DWORD cPossibleTargetProducts = 0; + +#ifdef DEBUG + // All patch info should be initialized to zero. + for (DWORD i = 0; i < pPackages->cPatchInfo; ++i) + { + BURN_PACKAGE* pPackage = pPackages->rgPatchInfoToPackage[i]; + Assert(!pPackage->Msp.cTargetProductCodes); + Assert(!pPackage->Msp.rgTargetProducts); + } +#endif + + // Figure out which product codes to target on the machine. In the worst case all products on the machine + // will be returned. + hr = GetPossibleTargetProductCodes(pPackages, &rgPossibleTargetProducts, &cPossibleTargetProducts); + ExitOnFailure(hr, "Failed to get possible target product codes."); + + // Loop through possible target products, testing the collective patch applicability against each product in + // the appropriate context. Store the result with the appropriate patch package. + for (DWORD iSearch = 0; iSearch < cPossibleTargetProducts; ++iSearch) + { + const POSSIBLE_TARGETPRODUCT* pPossibleTargetProduct = rgPossibleTargetProducts + iSearch; + + LogId(REPORT_STANDARD, MSG_DETECT_CALCULATE_PATCH_APPLICABILITY, pPossibleTargetProduct->wzProductCode, LoggingMsiInstallContext(pPossibleTargetProduct->context)); + + if (pPossibleTargetProduct->pszLocalPackage) + { + // Ignores current machine state to determine just patch applicability. + // Superseded and obsolesced patches will be planned separately. + hr = WiuDetermineApplicablePatches(pPossibleTargetProduct->pszLocalPackage, pPackages->rgPatchInfo, pPackages->cPatchInfo); + } + else + { + hr = WiuDeterminePatchSequence(pPossibleTargetProduct->wzProductCode, NULL, pPossibleTargetProduct->context, pPackages->rgPatchInfo, pPackages->cPatchInfo); + } + + if (SUCCEEDED(hr)) + { + for (DWORD iPatchInfo = 0; iPatchInfo < pPackages->cPatchInfo; ++iPatchInfo) + { + hr = HRESULT_FROM_WIN32(pPackages->rgPatchInfo[iPatchInfo].uStatus); + BURN_PACKAGE* pMspPackage = pPackages->rgPatchInfoToPackage[iPatchInfo]; + Assert(BURN_PACKAGE_TYPE_MSP == pMspPackage->type); + + if (S_OK == hr) + { + // Note that we do add superseded and obsolete MSP packages. Package Detect and Plan will sort them out later. + hr = MspEngineAddDetectedTargetProduct(pPackages, pMspPackage, pPackages->rgPatchInfo[iPatchInfo].dwOrder, pPossibleTargetProduct->wzProductCode, pPossibleTargetProduct->context); + ExitOnFailure(hr, "Failed to add target product code to package: %ls", pMspPackage->sczId); + } + else + { + LogStringLine(REPORT_DEBUG, " 0x%x: Patch applicability failed for package: %ls", hr, pMspPackage->sczId); + } + } + } + else + { + LogId(REPORT_STANDARD, MSG_DETECT_FAILED_CALCULATE_PATCH_APPLICABILITY, pPossibleTargetProduct->wzProductCode, LoggingMsiInstallContext(pPossibleTargetProduct->context), hr); + } + + hr = S_OK; // always reset so we test all possible target products. + } + +LExit: + if (rgPossibleTargetProducts) + { + for (DWORD i = 0; i < cPossibleTargetProducts; ++i) + { + ReleaseStr(rgPossibleTargetProducts[i].pszLocalPackage); + } + MemFree(rgPossibleTargetProducts); + } + + return hr; +} + +extern "C" HRESULT MspEngineAddDetectedTargetProduct( + __in BURN_PACKAGES* pPackages, + __in BURN_PACKAGE* pPackage, + __in DWORD dwOrder, + __in_z LPCWSTR wzProductCode, + __in MSIINSTALLCONTEXT context + ) +{ + HRESULT hr = S_OK; + DWORD dwTargetProductIndex = 0; + + hr = AddDetectedTargetProduct(pPackage, dwOrder, wzProductCode, context, &dwTargetProductIndex); + ExitOnFailure(hr, "Failed to add detected target product."); + + hr = DeterminePatchChainedTarget(pPackages, pPackage, wzProductCode, dwTargetProductIndex); + ExitOnFailure(hr, "Failed to determine patch chained target."); + +LExit: + return hr; +} + +extern "C" HRESULT MspEngineAddMissingSlipstreamTarget( + __in BURN_PACKAGE* pMsiPackage, + __in BURN_SLIPSTREAM_MSP* pSlipstreamMsp + ) +{ + HRESULT hr = S_OK; + DWORD dwTargetProductIndex = 0; + BURN_MSPTARGETPRODUCT* pTargetProduct = NULL; + DWORD dwChainedPatchIndex = 0; + + hr = AddDetectedTargetProduct(pSlipstreamMsp->pMspPackage, 0, pMsiPackage->Msi.sczProductCode, pMsiPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED, &dwTargetProductIndex); + ExitOnFailure(hr, "Failed to add missing slipstream target."); + + pTargetProduct = pSlipstreamMsp->pMspPackage->Msp.rgTargetProducts + dwTargetProductIndex; + pTargetProduct->fSlipstream = TRUE; + pTargetProduct->fSlipstreamRequired = TRUE; + pTargetProduct->pChainedTargetPackage = pMsiPackage; + + hr = AddMsiChainedPatch(pMsiPackage, pSlipstreamMsp->pMspPackage, dwTargetProductIndex, &dwChainedPatchIndex); + ExitOnFailure(hr, "Failed to add chained patch."); + + pSlipstreamMsp->dwMsiChainedPatchIndex = dwChainedPatchIndex; + +LExit: + return hr; +} + +extern "C" HRESULT MspEngineDetectPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + LPWSTR sczState = NULL; + + if (pPackage->fCanAffectRegistration) + { + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + + if (0 == pPackage->Msp.cTargetProductCodes) + { + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + } + else + { + // Start the package state at the highest state then loop through all the + // target product codes and end up setting the current state to the lowest + // package state applied to the target product codes. + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED; + + for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; + + hr = WiuGetPatchInfoEx(pPackage->Msp.sczPatchCode, pTargetProduct->wzTargetProductCode, NULL, pTargetProduct->context, INSTALLPROPERTY_PATCHSTATE, &sczState); + if (SUCCEEDED(hr)) + { + switch (*sczState) + { + case '1': + pTargetProduct->fInstalled = TRUE; + pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_PRESENT; + break; + + case '2': + pTargetProduct->fInstalled = TRUE; + pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED; + break; + + case '4': + pTargetProduct->fInstalled = TRUE; + pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE; + break; + + default: + pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + break; + } + } + else if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PATCH) == hr || HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr) + { + pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + hr = S_OK; + } + ExitOnFailure(hr, "Failed to get patch information for patch code: %ls, target product code: %ls", pPackage->Msp.sczPatchCode, pTargetProduct->wzTargetProductCode); + + if (pPackage->currentState > pTargetProduct->patchPackageState) + { + pPackage->currentState = pTargetProduct->patchPackageState; + } + + if (pPackage->fCanAffectRegistration) + { + pTargetProduct->registrationState = pTargetProduct->fInstalled ? BURN_PACKAGE_REGISTRATION_STATE_PRESENT : BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + + if (pTargetProduct->fInstalled) + { + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + } + + hr = UserExperienceOnDetectPatchTarget(pUserExperience, pPackage->sczId, pTargetProduct->wzTargetProductCode, pTargetProduct->patchPackageState); + ExitOnRootFailure(hr, "BA aborted detect patch target."); + } + } + +LExit: + ReleaseStr(sczState); + + return hr; +} + +extern "C" HRESULT MspEnginePlanInitializePackage( + __in BURN_PACKAGE* pPackage, + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + + for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; + + if (!pTargetProduct->fInstalled && pTargetProduct->fSlipstreamRequired && BOOTSTRAPPER_REQUEST_STATE_PRESENT > pTargetProduct->pChainedTargetPackage->requested) + { + // There's no way to apply the patch if the target isn't installed. + pTargetProduct->defaultRequested = pTargetProduct->requested = BOOTSTRAPPER_REQUEST_STATE_NONE; + continue; + } + + pTargetProduct->defaultRequested = pTargetProduct->requested = pPackage->requested; + + hr = UserExperienceOnPlanPatchTarget(pUserExperience, pPackage->sczId, pTargetProduct->wzTargetProductCode, &pTargetProduct->requested); + ExitOnRootFailure(hr, "BA aborted plan patch target."); + } + +LExit: + return hr; +} + +// +// PlanCalculate - calculates the execute and rollback state for the requested package state. +// +extern "C" HRESULT MspEnginePlanCalculatePackage( + __in BURN_PACKAGE* pPackage, + __in BOOL fInsideMsiTransaction + ) +{ + HRESULT hr = S_OK; + BOOL fWillUninstallAll = TRUE; + + for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; + + BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE; + BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + + // Calculate the execute action. + switch (pTargetProduct->patchPackageState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + switch (pTargetProduct->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + execute = BOOTSTRAPPER_ACTION_STATE_REPAIR; + fWillUninstallAll = FALSE; + break; + + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_CACHE: + execute = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: + execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + break; + + default: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + fWillUninstallAll = FALSE; + break; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + switch (pTargetProduct->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + execute = BOOTSTRAPPER_ACTION_STATE_INSTALL; + fWillUninstallAll = FALSE; + break; + + default: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + default: + if (pTargetProduct->fInstalled) + { + fWillUninstallAll = FALSE; + } + break; + } + + // Calculate the rollback action if there is an execute action. + if (BOOTSTRAPPER_ACTION_STATE_NONE != execute && !fInsideMsiTransaction) + { + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + switch (pTargetProduct->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: + rollback = BOOTSTRAPPER_ACTION_STATE_INSTALL; + break; + + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: __fallthrough; + switch (pTargetProduct->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + rollback = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + } + + pTargetProduct->execute = execute; + pTargetProduct->rollback = rollback; + + // The highest aggregate action state found will be returned. + if (pPackage->execute < execute) + { + pPackage->execute = execute; + } + + if (pPackage->rollback < rollback) + { + pPackage->rollback = rollback; + } + } + + // The dependency manager will do the wrong thing if the package level action is UNINSTALL + // when the patch will still be applied to at least one product. + if (!fWillUninstallAll && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) + { + pPackage->execute = BOOTSTRAPPER_ACTION_STATE_NONE; + } + + return hr; +} + +// +// PlanAdd - adds the calculated execute and rollback actions for the package. +// +extern "C" HRESULT MspEnginePlanAddPackage( + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in_opt HANDLE hCacheEvent + ) +{ + HRESULT hr = S_OK; + + // TODO: need to handle the case where this patch adds itself to an earlier patch's list of target products. That would + // essentially bump this patch earlier in the plan and we need to make sure this patch is downloaded. + // add wait for cache + if (hCacheEvent) + { + hr = PlanExecuteCacheSyncAndRollback(pPlan, pPackage, hCacheEvent); + ExitOnFailure(hr, "Failed to plan package cache syncpoint"); + } + + hr = DependencyPlanPackage(NULL, pPackage, pPlan); + ExitOnFailure(hr, "Failed to plan package dependency actions."); + + // Plan the actions for each target product code. + for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; + + // If the dependency manager changed the action state for the patch, change the target product actions. + if (pPackage->fDependencyManagerWasHere) + { + pTargetProduct->execute = pPackage->execute; + pTargetProduct->rollback = pPackage->rollback; + } + + if (BOOTSTRAPPER_ACTION_STATE_NONE != pTargetProduct->execute) + { + hr = PlanTargetProduct(display, pUserExperience, FALSE, pPlan, pLog, pVariables, pTargetProduct->execute, pPackage, pTargetProduct, hCacheEvent); + ExitOnFailure(hr, "Failed to plan target product."); + } + + if (BOOTSTRAPPER_ACTION_STATE_NONE != pTargetProduct->rollback) + { + hr = PlanTargetProduct(display, pUserExperience, TRUE, pPlan, pLog, pVariables, pTargetProduct->rollback, pPackage, pTargetProduct, hCacheEvent); + ExitOnFailure(hr, "Failed to plan rollback target product."); + } + } + +LExit: + + return hr; +} + +extern "C" HRESULT MspEngineExecutePackage( + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + WIU_MSI_EXECUTE_CONTEXT context = { }; + WIU_RESTART restart = WIU_RESTART_NONE; + + LPWSTR sczCachedDirectory = NULL; + LPWSTR sczMspPath = NULL; + LPWSTR sczPatches = NULL; + LPWSTR sczProperties = NULL; + LPWSTR sczObfuscatedProperties = NULL; + + // default to "verbose" logging + DWORD dwLogMode = WIU_LOG_DEFAULT | INSTALLLOGMODE_VERBOSE; + + // get cached MSP paths + for (DWORD i = 0; i < pExecuteAction->mspTarget.cOrderedPatches; ++i) + { + LPCWSTR wzAppend = NULL; + BURN_PACKAGE* pMspPackage = pExecuteAction->mspTarget.rgOrderedPatches[i].pPackage; + BURN_PAYLOAD* pMspPackagePayload = pMspPackage->payloads.rgItems[0].pPayload; + AssertSz(BURN_PACKAGE_TYPE_MSP == pMspPackage->type, "Invalid package type added to ordered patches."); + + if (BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->mspTarget.action) + { + hr = CacheGetCompletedPath(pMspPackage->fPerMachine, pMspPackage->sczCacheId, &sczCachedDirectory); + ExitOnFailure(hr, "Failed to get cached path for MSP package: %ls", pMspPackage->sczId); + + // TODO: Figure out if this makes sense -- the variable is set to the last patch's path only + // Best effort to set the execute package cache folder variable. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, sczCachedDirectory, TRUE, FALSE); + + hr = PathConcat(sczCachedDirectory, pMspPackagePayload->sczFilePath, &sczMspPath); + ExitOnFailure(hr, "Failed to build MSP path."); + + wzAppend = sczMspPath; + } + else // uninstall + { + wzAppend = pMspPackage->Msp.sczPatchCode; + } + + if (NULL != sczPatches) + { + hr = StrAllocConcat(&sczPatches, L";", 0); + ExitOnFailure(hr, "Failed to semi-colon delimit patches."); + } + + hr = StrAllocConcat(&sczPatches, wzAppend, 0); + ExitOnFailure(hr, "Failed to append patch."); + } + + // Best effort to set the execute package action variable. + VariableSetNumeric(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, pExecuteAction->mspTarget.action, TRUE); + + // Wire up the external UI handler and logging. + if (pExecuteAction->mspTarget.fDisableExternalUiHandler) + { + hr = WiuInitializeInternalUI(pExecuteAction->mspTarget.uiLevel, hwndParent, &context); + ExitOnFailure(hr, "Failed to initialize internal UI for MSP package."); + } + else + { + hr = WiuInitializeExternalUI(pfnMessageHandler, pExecuteAction->mspTarget.uiLevel, hwndParent, pvContext, fRollback, &context); + ExitOnFailure(hr, "Failed to initialize external UI handler."); + } + + //if (BURN_LOGGING_LEVEL_DEBUG == logLevel) + //{ + // dwLogMode | INSTALLLOGMODE_EXTRADEBUG; + //} + + if (pExecuteAction->mspTarget.sczLogPath && *pExecuteAction->mspTarget.sczLogPath) + { + hr = WiuEnableLog(dwLogMode, pExecuteAction->mspTarget.sczLogPath, 0); + ExitOnFailure(hr, "Failed to enable logging for package: %ls to: %ls", pExecuteAction->mspTarget.pPackage->sczId, pExecuteAction->mspTarget.sczLogPath); + } + + // set up properties + hr = MsiEngineConcatProperties(pExecuteAction->mspTarget.pPackage->Msp.rgProperties, pExecuteAction->mspTarget.pPackage->Msp.cProperties, pVariables, fRollback, &sczProperties, FALSE); + ExitOnFailure(hr, "Failed to add properties to argument string."); + + hr = MsiEngineConcatProperties(pExecuteAction->mspTarget.pPackage->Msp.rgProperties, pExecuteAction->mspTarget.pPackage->Msp.cProperties, pVariables, fRollback, &sczObfuscatedProperties, TRUE); + ExitOnFailure(hr, "Failed to add properties to obfuscated argument string."); + + hr = MsiEngineConcatActionProperty(pExecuteAction->mspTarget.actionMsiProperty, &sczProperties); + ExitOnFailure(hr, "Failed to add action property to argument string."); + + hr = MsiEngineConcatActionProperty(pExecuteAction->mspTarget.actionMsiProperty, &sczObfuscatedProperties); + ExitOnFailure(hr, "Failed to add action property to obfuscated argument string."); + + LogId(REPORT_STANDARD, MSG_APPLYING_PATCH_PACKAGE, pExecuteAction->mspTarget.pPackage->sczId, LoggingActionStateToString(pExecuteAction->mspTarget.action), sczPatches, sczObfuscatedProperties, pExecuteAction->mspTarget.sczTargetProductCode); + + // + // Do the actual action. + // + switch (pExecuteAction->mspTarget.action) + { + case BOOTSTRAPPER_ACTION_STATE_INSTALL: __fallthrough; + case BOOTSTRAPPER_ACTION_STATE_REPAIR: + hr = StrAllocConcatSecure(&sczProperties, L" PATCH=\"", 0); + ExitOnFailure(hr, "Failed to add PATCH property on install."); + + hr = StrAllocConcatSecure(&sczProperties, sczPatches, 0); + ExitOnFailure(hr, "Failed to add patches to PATCH property on install."); + + hr = StrAllocConcatSecure(&sczProperties, L"\" REBOOT=ReallySuppress", 0); + ExitOnFailure(hr, "Failed to add reboot suppression property on install."); + + hr = WiuConfigureProductEx(pExecuteAction->mspTarget.sczTargetProductCode, INSTALLLEVEL_DEFAULT, INSTALLSTATE_DEFAULT, sczProperties, &restart); + ExitOnFailure(hr, "Failed to install MSP package."); + break; + + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + hr = StrAllocConcatSecure(&sczProperties, L" REBOOT=ReallySuppress", 0); + ExitOnFailure(hr, "Failed to add reboot suppression property on uninstall."); + + // Ignore all dependencies, since the Burn engine already performed the check. + hr = StrAllocFormattedSecure(&sczProperties, L"%ls %ls=ALL", sczProperties, DEPENDENCY_IGNOREDEPENDENCIES); + ExitOnFailure(hr, "Failed to add the list of dependencies to ignore to the properties."); + + hr = WiuRemovePatches(sczPatches, pExecuteAction->mspTarget.sczTargetProductCode, sczProperties, &restart); + ExitOnFailure(hr, "Failed to uninstall MSP package."); + break; + } + +LExit: + WiuUninitializeExternalUI(&context); + + ReleaseStr(sczCachedDirectory); + ReleaseStr(sczMspPath); + StrSecureZeroFreeString(sczProperties); + ReleaseStr(sczObfuscatedProperties); + ReleaseStr(sczPatches); + + switch (restart) + { + case WIU_RESTART_NONE: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; + break; + + case WIU_RESTART_REQUIRED: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_REQUIRED; + break; + + case WIU_RESTART_INITIATED: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_INITIATED; + break; + } + + // Best effort to clear the execute package cache folder and action variables. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, NULL, TRUE, FALSE); + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, NULL, TRUE, FALSE); + + return hr; +} + +extern "C" void MspEngineUpdateInstallRegistrationState( + __in BURN_EXECUTE_ACTION* pAction, + __in HRESULT hrExecute, + __in BOOL fInsideMsiTransaction + ) +{ + BURN_PACKAGE_REGISTRATION_STATE newState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + + if (FAILED(hrExecute)) + { + ExitFunction(); + } + + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pAction->mspTarget.action) + { + newState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + else + { + newState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + + for (DWORD i = 0; i < pAction->mspTarget.cOrderedPatches; ++i) + { + BURN_ORDERED_PATCHES* pOrderedPatches = pAction->mspTarget.rgOrderedPatches + i; + BURN_PACKAGE* pPackage = pOrderedPatches->pPackage; + BURN_MSPTARGETPRODUCT* pTargetProduct = NULL; + + Assert(BURN_PACKAGE_TYPE_MSP == pPackage->type); + + if (!pPackage->fCanAffectRegistration) + { + continue; + } + + for (DWORD j = 0; j < pPackage->Msp.cTargetProductCodes; ++j) + { + pTargetProduct = pPackage->Msp.rgTargetProducts + j; + if (pAction->mspTarget.fPerMachineTarget == (MSIINSTALLCONTEXT_MACHINE == pTargetProduct->context) && + CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pAction->mspTarget.sczTargetProductCode, -1, pTargetProduct->wzTargetProductCode, -1)) + { + break; + } + + pTargetProduct = NULL; + } + + if (!pTargetProduct) + { + AssertSz(pTargetProduct, "Ordered patch didn't have corresponding target product"); + continue; + } + + if (fInsideMsiTransaction) + { + pTargetProduct->transactionRegistrationState = newState; + } + else + { + pTargetProduct->registrationState = newState; + } + } + +LExit: + return; +} + +extern "C" void MspEngineFinalizeInstallRegistrationState( + __in BURN_PACKAGE* pPackage + ) +{ + if (!pPackage->fCanAffectRegistration) + { + ExitFunction(); + } + + if (!pPackage->Msp.cTargetProductCodes) + { + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + else + { + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + + for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + i; + + if (pPackage->installRegistrationState < pTargetProduct->registrationState) + { + pPackage->installRegistrationState = pTargetProduct->registrationState; + } + } + } + +LExit: + return; +} + + +// internal helper functions + +static HRESULT GetPossibleTargetProductCodes( + __in BURN_PACKAGES* pPackages, + __deref_inout_ecount_opt(*pcPossibleTargetProducts) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProducts, + __inout DWORD* pcPossibleTargetProducts + ) +{ + HRESULT hr = S_OK; + STRINGDICT_HANDLE sdUniquePossibleTargetProductCodes = NULL; + BOOL fCheckAll = FALSE; + WCHAR wzPossibleTargetProductCode[MAX_GUID_CHARS + 1]; + + // Use a dictionary to ensure we capture unique product codes. Otherwise, we could end up + // doing patch applicability for the same product code multiple times and that would confuse + // everything down stream. + hr = DictCreateStringList(&sdUniquePossibleTargetProductCodes, 5, DICT_FLAG_NONE); + ExitOnFailure(hr, "Failed to create unique target product codes."); + + // If the patches target a specific set of product/upgrade codes, search only those. This + // should be much faster than searching all packages on the machine. + if (pPackages->rgPatchTargetCodes) + { + for (DWORD i = 0; i < pPackages->cPatchTargetCodes; ++i) + { + BURN_PATCH_TARGETCODE* pTargetCode = pPackages->rgPatchTargetCodes + i; + + // If targeting a product, add the unique product code to the list. + if (BURN_PATCH_TARGETCODE_TYPE_PRODUCT == pTargetCode->type) + { + hr = AddPossibleTargetProduct(sdUniquePossibleTargetProductCodes, pTargetCode->sczTargetCode, MSIINSTALLCONTEXT_NONE, prgPossibleTargetProducts, pcPossibleTargetProducts); + ExitOnFailure(hr, "Failed to add product code to possible target product codes."); + } + else if (BURN_PATCH_TARGETCODE_TYPE_UPGRADE == pTargetCode->type) + { + // Enumerate all unique related products to the target upgrade code. + for (DWORD iProduct = 0; SUCCEEDED(hr); ++iProduct) + { + hr = WiuEnumRelatedProducts(pTargetCode->sczTargetCode, iProduct, wzPossibleTargetProductCode); + if (SUCCEEDED(hr)) + { + hr = AddPossibleTargetProduct(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode, MSIINSTALLCONTEXT_NONE, prgPossibleTargetProducts, pcPossibleTargetProducts); + ExitOnFailure(hr, "Failed to add upgrade product code to possible target product codes."); + } + else if (E_BADCONFIGURATION == hr) + { + // Skip product's with bad configuration and continue. + LogId(REPORT_STANDARD, MSG_DETECT_BAD_PRODUCT_CONFIGURATION, wzPossibleTargetProductCode); + + hr = S_OK; + } + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to enumerate all products to patch related to upgrade code: %ls", pTargetCode->sczTargetCode); + } + else + { + // The element does not target a specific product. + fCheckAll = TRUE; + + break; + } + } + } + else + { + fCheckAll = TRUE; + } + + // One or more of the patches do not target a specific product so search everything on the machine. + if (fCheckAll) + { + for (DWORD iProduct = 0; SUCCEEDED(hr); ++iProduct) + { + MSIINSTALLCONTEXT context = MSIINSTALLCONTEXT_NONE; + + hr = WiuEnumProductsEx(NULL, NULL, MSIINSTALLCONTEXT_ALL, iProduct, wzPossibleTargetProductCode, &context, NULL, NULL); + if (SUCCEEDED(hr)) + { + hr = AddPossibleTargetProduct(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode, context, prgPossibleTargetProducts, pcPossibleTargetProducts); + ExitOnFailure(hr, "Failed to add product code to search product codes."); + } + else if (E_BADCONFIGURATION == hr) + { + // Skip products with bad configuration and continue. + LogId(REPORT_STANDARD, MSG_DETECT_BAD_PRODUCT_CONFIGURATION, wzPossibleTargetProductCode); + + hr = S_OK; + } + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to enumerate all products on the machine for patches applicability."); + } + +LExit: + ReleaseDict(sdUniquePossibleTargetProductCodes); + + return hr; +} + +static HRESULT AddPossibleTargetProduct( + __in STRINGDICT_HANDLE sdUniquePossibleTargetProductCodes, + __in_z LPCWSTR wzPossibleTargetProductCode, + __in MSIINSTALLCONTEXT context, + __deref_inout_ecount_opt(*pcPossibleTargetProducts) POSSIBLE_TARGETPRODUCT** prgPossibleTargetProducts, + __inout DWORD* pcPossibleTargetProducts + ) +{ + HRESULT hr = S_OK; + LPWSTR pszLocalPackage = NULL; + + // Only add this possible target code if we haven't queried for it already. + if (E_NOTFOUND == DictKeyExists(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode)) + { + // If the install context is not known, ask the Windows Installer for it. If we can't get the context + // then bail. + if (MSIINSTALLCONTEXT_NONE == context) + { + hr = WiuEnumProductsEx(wzPossibleTargetProductCode, NULL, MSIINSTALLCONTEXT_ALL, 0, NULL, &context, NULL, NULL); + if (FAILED(hr)) + { + ExitFunction1(hr = S_OK); + } + } + + hr = DictAddKey(sdUniquePossibleTargetProductCodes, wzPossibleTargetProductCode); + ExitOnFailure(hr, "Failed to add possible target code to unique product codes."); + + hr = MemEnsureArraySize(reinterpret_cast(prgPossibleTargetProducts), *pcPossibleTargetProducts + 1, sizeof(POSSIBLE_TARGETPRODUCT), 3); + ExitOnFailure(hr, "Failed to grow array of possible target products."); + + POSSIBLE_TARGETPRODUCT *const pPossibleTargetProduct = *prgPossibleTargetProducts + *pcPossibleTargetProducts; + + hr = ::StringCchCopyW(pPossibleTargetProduct->wzProductCode, countof(pPossibleTargetProduct->wzProductCode), wzPossibleTargetProductCode); + ExitOnFailure(hr, "Failed to copy possible target product code."); + + // Attempt to get the local package path so we can more quickly determine patch applicability later. + hr = WiuGetProductInfoEx(wzPossibleTargetProductCode, NULL, context, INSTALLPROPERTY_LOCALPACKAGE, &pszLocalPackage); + if (SUCCEEDED(hr)) + { + pPossibleTargetProduct->pszLocalPackage = pszLocalPackage; + pszLocalPackage = NULL; + } + else + { + // Will instead call MsiDeterminePatchSequence later. + hr = S_OK; + } + + pPossibleTargetProduct->context = context; + + ++(*pcPossibleTargetProducts); + } + +LExit: + ReleaseStr(pszLocalPackage); + + return hr; +} + +static HRESULT AddDetectedTargetProduct( + __in BURN_PACKAGE* pPackage, + __in DWORD dwOrder, + __in_z LPCWSTR wzProductCode, + __in MSIINSTALLCONTEXT context, + __out DWORD* pdwTargetProductIndex + ) +{ + HRESULT hr = S_OK; + BURN_MSPTARGETPRODUCT* pTargetProduct = NULL; + + *pdwTargetProductIndex = BURN_PACKAGE_INVALID_PATCH_INDEX; + + hr = MemEnsureArraySize(reinterpret_cast(&pPackage->Msp.rgTargetProducts), pPackage->Msp.cTargetProductCodes + 1, sizeof(BURN_MSPTARGETPRODUCT), 5); + ExitOnFailure(hr, "Failed to ensure enough target product codes were allocated."); + + pTargetProduct = pPackage->Msp.rgTargetProducts + pPackage->Msp.cTargetProductCodes; + + hr = ::StringCchCopyW(pTargetProduct->wzTargetProductCode, countof(pTargetProduct->wzTargetProductCode), wzProductCode); + ExitOnFailure(hr, "Failed to copy target product code."); + + pTargetProduct->context = context; + pTargetProduct->dwOrder = dwOrder; + + *pdwTargetProductIndex = pPackage->Msp.cTargetProductCodes; + ++pPackage->Msp.cTargetProductCodes; + +LExit: + return hr; +} + +static HRESULT AddMsiChainedPatch( + __in BURN_PACKAGE* pPackage, + __in BURN_PACKAGE* pMspPackage, + __in DWORD dwMspTargetProductIndex, + __out DWORD* pdwChainedPatchIndex + ) +{ + HRESULT hr = S_OK; + + hr = MemEnsureArraySize(reinterpret_cast(&pPackage->Msi.rgChainedPatches), pPackage->Msi.cChainedPatches + 1, sizeof(BURN_CHAINED_PATCH), 5); + ExitOnFailure(hr, "Failed to ensure enough chained patches were allocated."); + + BURN_CHAINED_PATCH* pChainedPatch = pPackage->Msi.rgChainedPatches + pPackage->Msi.cChainedPatches; + pChainedPatch->pMspPackage = pMspPackage; + pChainedPatch->dwMspTargetProductIndex = dwMspTargetProductIndex; + + *pdwChainedPatchIndex = pPackage->Msi.cChainedPatches; + ++pPackage->Msi.cChainedPatches; +LExit: + return hr; +} + +static HRESULT DeterminePatchChainedTarget( + __in BURN_PACKAGES* pPackages, + __in BURN_PACKAGE* pMspPackage, + __in LPCWSTR wzTargetProductCode, + __in DWORD dwMspTargetProductIndex + ) +{ + HRESULT hr = S_OK; + DWORD dwChainedPatchIndex = 0; + BURN_MSPTARGETPRODUCT* pTargetProduct = pMspPackage->Msp.rgTargetProducts + dwMspTargetProductIndex; + + for (DWORD iPackage = 0; iPackage < pPackages->cPackages; ++iPackage) + { + BURN_PACKAGE* pPackage = pPackages->rgPackages + iPackage; + + if (BURN_PACKAGE_TYPE_MSI == pPackage->type && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzTargetProductCode, -1, pPackage->Msi.sczProductCode, -1)) + { + pTargetProduct->pChainedTargetPackage = pPackage; + + hr = AddMsiChainedPatch(pPackage, pMspPackage, dwMspTargetProductIndex, &dwChainedPatchIndex); + ExitOnFailure(hr, "Failed to add chained patch."); + + for (DWORD j = 0; j < pPackage->Msi.cSlipstreamMspPackages; ++j) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pPackage->Msi.rgSlipstreamMsps + j; + if (pSlipstreamMsp->pMspPackage == pMspPackage) + { + AssertSz(BURN_PACKAGE_INVALID_PATCH_INDEX == pSlipstreamMsp->dwMsiChainedPatchIndex, "An MSP should only show up as a slipstreamed patch in an MSI once."); + pTargetProduct->fSlipstream = TRUE; + pSlipstreamMsp->dwMsiChainedPatchIndex = dwChainedPatchIndex; + break; + } + } + + break; + } + } + +LExit: + return hr; +} + +static HRESULT PlanTargetProduct( + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fRollback, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_ACTION_STATE actionState, + __in BURN_PACKAGE* pPackage, + __in BURN_MSPTARGETPRODUCT* pTargetProduct, + __in_opt HANDLE hCacheEvent + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* rgActions = fRollback ? pPlan->rgRollbackActions : pPlan->rgExecuteActions; + DWORD cActions = fRollback ? pPlan->cRollbackActions : pPlan->cExecuteActions; + BURN_EXECUTE_ACTION* pAction = NULL; + DWORD dwInsertSequence = 0; + + // Try to find another MSP action with the exact same action (install or uninstall) targeting + // the same product in the same machine context (per-user or per-machine). + for (DWORD i = 0; i < cActions; ++i) + { + pAction = rgActions + i; + + if (BURN_EXECUTE_ACTION_TYPE_MSP_TARGET == pAction->type && + pAction->mspTarget.action == actionState && + pAction->mspTarget.fPerMachineTarget == (MSIINSTALLCONTEXT_MACHINE == pTargetProduct->context) && + CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pAction->mspTarget.sczTargetProductCode, -1, pTargetProduct->wzTargetProductCode, -1)) + { + dwInsertSequence = i; + break; + } + + pAction = NULL; + } + + // If we didn't find an MSP target action already updating the product, create a new action. + if (!pAction) + { + if (fRollback) + { + hr = PlanAppendRollbackAction(pPlan, &pAction); + } + else + { + hr = PlanAppendExecuteAction(pPlan, &pAction); + } + ExitOnFailure(hr, "Failed to plan action for target product."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_MSP_TARGET; + pAction->mspTarget.action = actionState; + pAction->mspTarget.pPackage = pPackage; + pAction->mspTarget.fPerMachineTarget = (MSIINSTALLCONTEXT_MACHINE == pTargetProduct->context); + pAction->mspTarget.pChainedTargetPackage = pTargetProduct->pChainedTargetPackage; + pAction->mspTarget.fSlipstream = pTargetProduct->fSlipstream; + hr = StrAllocString(&pAction->mspTarget.sczTargetProductCode, pTargetProduct->wzTargetProductCode, 0); + ExitOnFailure(hr, "Failed to copy target product code."); + + hr = MsiEngineCalculateInstallUiLevel(display, pUserExperience, pPackage->sczId, !fRollback, pAction->mspTarget.action, + &pAction->mspTarget.actionMsiProperty, &pAction->mspTarget.uiLevel, &pAction->mspTarget.fDisableExternalUiHandler); + ExitOnFailure(hr, "Failed to get msp ui options."); + + // If this is a per-machine target product, then the plan needs to be per-machine as well. + if (pAction->mspTarget.fPerMachineTarget) + { + pPlan->fPerMachine = TRUE; + } + + LoggingSetPackageVariable(pPackage, pAction->mspTarget.sczTargetProductCode, fRollback, pLog, pVariables, &pAction->mspTarget.sczLogPath); // ignore errors. + } + else + { + if (!fRollback && hCacheEvent) + { + // Since a previouse MSP target action is being updated with the new MSP, + // insert a wait syncpoint to before this action since we need to cache the current MSI before using it. + BURN_EXECUTE_ACTION* pWaitSyncPointAction = NULL; + hr = PlanInsertExecuteAction(dwInsertSequence, pPlan, &pWaitSyncPointAction); + ExitOnFailure(hr, "Failed to insert execute action."); + + pWaitSyncPointAction->type = BURN_EXECUTE_ACTION_TYPE_WAIT_SYNCPOINT; + pWaitSyncPointAction->syncpoint.hEvent = hCacheEvent; + + // Since we inserted an action before the MSP target action that we will be updating, need to update the pointer. + pAction = pPlan->rgExecuteActions + (dwInsertSequence + 1); + } + } + + // Add our target product to the array and sort based on their order determined during detection. + hr = MemEnsureArraySize(reinterpret_cast(&pAction->mspTarget.rgOrderedPatches), pAction->mspTarget.cOrderedPatches + 1, sizeof(BURN_ORDERED_PATCHES), 2); + ExitOnFailure(hr, "Failed grow array of ordered patches."); + + pAction->mspTarget.rgOrderedPatches[pAction->mspTarget.cOrderedPatches].pTargetProduct = pTargetProduct; + pAction->mspTarget.rgOrderedPatches[pAction->mspTarget.cOrderedPatches].pPackage = pPackage; + ++pAction->mspTarget.cOrderedPatches; + + // Insertion sort to keep the patches ordered. + for (DWORD i = pAction->mspTarget.cOrderedPatches - 1; i > 0; --i) + { + if (pAction->mspTarget.rgOrderedPatches[i].pTargetProduct->dwOrder < pAction->mspTarget.rgOrderedPatches[i - 1].pTargetProduct->dwOrder) + { + BURN_ORDERED_PATCHES temp = pAction->mspTarget.rgOrderedPatches[i - 1]; + pAction->mspTarget.rgOrderedPatches[i - 1] = pAction->mspTarget.rgOrderedPatches[i]; + pAction->mspTarget.rgOrderedPatches[i] = temp; + } + else // no swap necessary, we're done. + { + break; + } + } + +LExit: + return hr; +} diff --git a/src/burn/engine/mspengine.h b/src/burn/engine/mspengine.h new file mode 100644 index 00000000..79998030 --- /dev/null +++ b/src/burn/engine/mspengine.h @@ -0,0 +1,84 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + + +// structures + + +// typedefs + + +// function declarations + +HRESULT MspEngineParsePackageFromXml( + __in IXMLDOMNode* pixnBundle, + __in BURN_PACKAGE* pPackage + ); +void MspEnginePackageUninitialize( + __in BURN_PACKAGE* pPackage + ); +HRESULT MspEngineDetectInitialize( + __in BURN_PACKAGES* pPackages + ); +HRESULT MspEngineAddDetectedTargetProduct( + __in BURN_PACKAGES* pPackages, + __in BURN_PACKAGE* pPackage, + __in DWORD dwOrder, + __in_z LPCWSTR wzProductCode, + __in MSIINSTALLCONTEXT context + ); +HRESULT MspEngineAddMissingSlipstreamTarget( + __in BURN_PACKAGE* pMsiPackage, + __in BURN_SLIPSTREAM_MSP* pSlipstreamMsp + ); +HRESULT MspEngineDetectPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_USER_EXPERIENCE* pUserExperience + ); +HRESULT MspEnginePlanInitializePackage( + __in BURN_PACKAGE* pPackage, + __in BURN_USER_EXPERIENCE* pUserExperience + ); +HRESULT MspEnginePlanCalculatePackage( + __in BURN_PACKAGE* pPackage, + __in BOOL fInsideMsiTransaction + ); +HRESULT MspEnginePlanAddPackage( + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in_opt HANDLE hCacheEvent + ); +HRESULT MspEngineExecutePackage( + __in_opt HWND hwndParent, + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +void MspEngineUpdateInstallRegistrationState( + __in BURN_EXECUTE_ACTION* pAction, + __in HRESULT hrExecute, + __in BOOL fInsideMsiTransaction + ); +void MspEngineFinalizeInstallRegistrationState( + __in BURN_PACKAGE* pPackage + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/msuengine.cpp b/src/burn/engine/msuengine.cpp new file mode 100644 index 00000000..6003123b --- /dev/null +++ b/src/burn/engine/msuengine.cpp @@ -0,0 +1,529 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// constants + +#define WU_S_REBOOT_REQUIRED 0x00240005L +#define WU_S_ALREADY_INSTALLED 0x00240006L + + +// function definitions +static HRESULT EnsureWUServiceEnabled( + __in BOOL fStopWusaService, + __out SC_HANDLE* pschWu, + __out BOOL* pfPreviouslyDisabled + ); +static HRESULT SetServiceStartType( + __in SC_HANDLE sch, + __in DWORD stratType + ); +static HRESULT StopWUService( + __in SC_HANDLE schWu + ); + + +extern "C" HRESULT MsuEngineParsePackageFromXml( + __in IXMLDOMNode* pixnMsuPackage, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + + // @KB + hr = XmlGetAttributeEx(pixnMsuPackage, L"KB", &pPackage->Msu.sczKB); + ExitOnFailure(hr, "Failed to get @KB."); + + // @DetectCondition + hr = XmlGetAttributeEx(pixnMsuPackage, L"DetectCondition", &pPackage->Msu.sczDetectCondition); + ExitOnFailure(hr, "Failed to get @DetectCondition."); + +LExit: + return hr; +} + +extern "C" void MsuEnginePackageUninitialize( + __in BURN_PACKAGE* pPackage + ) +{ + ReleaseNullStr(pPackage->Msu.sczKB); + ReleaseNullStr(pPackage->Msu.sczDetectCondition); +} + +extern "C" HRESULT MsuEngineDetectPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + BOOL fDetected = FALSE; + + // evaluate detect condition + if (pPackage->Msu.sczDetectCondition && *pPackage->Msu.sczDetectCondition) + { + hr = ConditionEvaluate(pVariables, pPackage->Msu.sczDetectCondition, &fDetected); + ExitOnFailure(hr, "Failed to evaluate MSU package detect condition."); + } + + // update detect state + pPackage->currentState = fDetected ? BOOTSTRAPPER_PACKAGE_STATE_PRESENT : BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + + if (pPackage->fCanAffectRegistration) + { + pPackage->installRegistrationState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT < pPackage->currentState ? BURN_PACKAGE_REGISTRATION_STATE_PRESENT : BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + +LExit: + return hr; +} + +// +// PlanCalculate - calculates the execute and rollback state for the requested package state. +// +extern "C" HRESULT MsuEnginePlanCalculatePackage( + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE; + BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + BOOL fAllowUninstall = FALSE; + + // We can only uninstall MSU packages if they have a KB and we are on Win7 or newer. + fAllowUninstall = pPackage->Msu.sczKB && *pPackage->Msu.sczKB && ::IsWindows7OrGreater(); + + // execute action + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_CACHE: + execute = fAllowUninstall && pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: + execute = fAllowUninstall ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + + default: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + execute = BOOTSTRAPPER_ACTION_STATE_INSTALL; + break; + + default: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package state."); + } + + // Calculate the rollback action if there is an execute action. + if (BOOTSTRAPPER_ACTION_STATE_NONE != execute) + { + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: + rollback = BOOTSTRAPPER_ACTION_STATE_INSTALL; + break; + + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + rollback = fAllowUninstall ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package expected state."); + } + } + + // return values + pPackage->execute = execute; + pPackage->rollback = rollback; + +LExit: + return hr; +} + +// +// PlanAdd - adds the calculated execute and rollback actions for the package. +// +extern "C" HRESULT MsuEnginePlanAddPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in HANDLE hCacheEvent + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pAction = NULL; + + // add wait for cache + if (hCacheEvent) + { + hr = PlanExecuteCacheSyncAndRollback(pPlan, pPackage, hCacheEvent); + ExitOnFailure(hr, "Failed to plan package cache syncpoint"); + } + + hr = DependencyPlanPackage(NULL, pPackage, pPlan); + ExitOnFailure(hr, "Failed to plan package dependency actions."); + + // add execute action + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute) + { + hr = PlanAppendExecuteAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append execute action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE; + pAction->msuPackage.pPackage = pPackage; + pAction->msuPackage.action = pPackage->execute; + + LoggingSetPackageVariable(pPackage, NULL, FALSE, pLog, pVariables, &pAction->msuPackage.sczLogPath); // ignore errors. + } + + // add rollback action + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback) + { + hr = PlanAppendRollbackAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append rollback action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE; + pAction->msuPackage.pPackage = pPackage; + pAction->msuPackage.action = pPackage->rollback; + + LoggingSetPackageVariable(pPackage, NULL, TRUE, pLog, pVariables, &pAction->msuPackage.sczLogPath); // ignore errors. + } + +LExit: + return hr; +} + +extern "C" HRESULT MsuEngineExecutePackage( + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in BOOL fStopWusaService, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + int nResult = IDNOACTION; + LPWSTR sczCachedDirectory = NULL; + LPWSTR sczMsuPath = NULL; + LPWSTR sczWindowsPath = NULL; + LPWSTR sczSystemPath = NULL; + LPWSTR sczWusaPath = NULL; + LPWSTR sczCommand = NULL; + SC_HANDLE schWu = NULL; + BOOL fWuWasDisabled = FALSE; + STARTUPINFOW si = { }; + PROCESS_INFORMATION pi = { }; + GENERIC_EXECUTE_MESSAGE message = { }; + DWORD dwExitCode = 0; + BOOL fUseSysNativePath = FALSE; + BURN_PACKAGE* pPackage = pExecuteAction->msuPackage.pPackage; + BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload; + +#if !defined(_WIN64) + hr = ProcWow64(::GetCurrentProcess(), &fUseSysNativePath); + ExitOnFailure(hr, "Failed to determine WOW64 status."); +#endif + + *pRestart = BOOTSTRAPPER_APPLY_RESTART_NONE; + + // get wusa.exe path + if (fUseSysNativePath) + { + hr = PathGetKnownFolder(CSIDL_WINDOWS, &sczWindowsPath); + ExitOnFailure(hr, "Failed to find Windows directory."); + + hr = PathConcat(sczWindowsPath, L"SysNative\\", &sczSystemPath); + ExitOnFailure(hr, "Failed to append SysNative directory."); + } + else + { + hr = PathGetKnownFolder(CSIDL_SYSTEM, &sczSystemPath); + ExitOnFailure(hr, "Failed to find System32 directory."); + } + + hr = PathConcat(sczSystemPath, L"wusa.exe", &sczWusaPath); + ExitOnFailure(hr, "Failed to allocate WUSA.exe path."); + + // build command + switch (pExecuteAction->msuPackage.action) + { + case BOOTSTRAPPER_ACTION_STATE_INSTALL: + // get cached MSU path + hr = CacheGetCompletedPath(TRUE, pPackage->sczCacheId, &sczCachedDirectory); + ExitOnFailure(hr, "Failed to get cached path for package: %ls", pPackage->sczId); + + // Best effort to set the execute package cache folder variable. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, sczCachedDirectory, TRUE, FALSE); + + hr = PathConcat(sczCachedDirectory, pPackagePayload->sczFilePath, &sczMsuPath); + ExitOnFailure(hr, "Failed to build MSU path."); + + // format command + hr = StrAllocFormatted(&sczCommand, L"\"%ls\" \"%ls\" /quiet /norestart", sczWusaPath, sczMsuPath); + ExitOnFailure(hr, "Failed to format MSU install command."); + break; + + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + // format command + hr = StrAllocFormatted(&sczCommand, L"\"%ls\" /uninstall /kb:%ls /quiet /norestart", sczWusaPath, pPackage->Msu.sczKB); + ExitOnFailure(hr, "Failed to format MSU uninstall command."); + break; + + default: + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Failed to get action arguments for MSU package."); + } + + if (pExecuteAction->msuPackage.sczLogPath && *pExecuteAction->msuPackage.sczLogPath) + { + hr = StrAllocConcat(&sczCommand, L" /log:", 0); + ExitOnFailure(hr, "Failed to append log switch to MSU command-line."); + + hr = StrAllocConcat(&sczCommand, pExecuteAction->msuPackage.sczLogPath, 0); + ExitOnFailure(hr, "Failed to append log path to MSU command-line."); + } + + LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, LoggingRollbackOrExecute(fRollback), pPackage->sczId, LoggingActionStateToString(pExecuteAction->msuPackage.action), sczMsuPath ? sczMsuPath : pPackage->Msu.sczKB, sczCommand); + + hr = EnsureWUServiceEnabled(fStopWusaService, &schWu, &fWuWasDisabled); + ExitOnFailure(hr, "Failed to ensure WU service was enabled to install MSU package."); + + // create process + si.cb = sizeof(si); + if (!::CreateProcessW(sczWusaPath, sczCommand, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) + { + ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczWusaPath); + } + + do + { + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwAllowedResults = MB_OKCANCEL; + message.progress.dwPercentage = 50; + nResult = pfnGenericMessageHandler(&message, pvContext); + hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE); + ExitOnRootFailure(hr, "Bootstrapper application aborted during MSU progress."); + + // wait for process to terminate + hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode); + if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr) + { + ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczWusaPath); + } + } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr); + + // get process exit code + if (!::GetExitCodeProcess(pi.hProcess, &dwExitCode)) + { + ExitWithLastError(hr, "Failed to get process exit code."); + } + + // We'll normalize the restart required error code from wusa.exe just in case. Most likely + // that on reboot we'll actually get WU_S_REBOOT_REQUIRED. + if (HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED) == static_cast(dwExitCode)) + { + dwExitCode = ERROR_SUCCESS_REBOOT_REQUIRED; + } + + // handle exit code + switch (dwExitCode) + { + case S_OK: __fallthrough; + case S_FALSE: __fallthrough; + case WU_S_ALREADY_INSTALLED: + hr = S_OK; + break; + + case ERROR_SUCCESS_REBOOT_REQUIRED: __fallthrough; + case WU_S_REBOOT_REQUIRED: + *pRestart = BOOTSTRAPPER_APPLY_RESTART_REQUIRED; + hr = S_OK; + break; + + default: + hr = static_cast(dwExitCode); + break; + } + +LExit: + ReleaseStr(sczCachedDirectory); + ReleaseStr(sczMsuPath); + ReleaseStr(sczSystemPath); + ReleaseStr(sczWindowsPath); + ReleaseStr(sczWusaPath); + ReleaseStr(sczCommand); + + ReleaseHandle(pi.hProcess); + ReleaseHandle(pi.hThread); + + if (fWuWasDisabled) + { + SetServiceStartType(schWu, SERVICE_DISABLED); + } + + // Best effort to clear the execute package cache folder variable. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, NULL, TRUE, FALSE); + + return hr; +} + +extern "C" void MsuEngineUpdateInstallRegistrationState( + __in BURN_EXECUTE_ACTION* pAction, + __in HRESULT hrExecute + ) +{ + BURN_PACKAGE* pPackage = pAction->msuPackage.pPackage; + + if (FAILED(hrExecute) || !pPackage->fCanAffectRegistration) + { + ExitFunction(); + } + + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pAction->msuPackage.action) + { + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + else + { + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + +LExit: + return; +} + +static HRESULT EnsureWUServiceEnabled( + __in BOOL fStopWusaService, + __out SC_HANDLE* pschWu, + __out BOOL* pfPreviouslyDisabled + ) +{ + HRESULT hr = S_OK; + SC_HANDLE schSCM = NULL; + SC_HANDLE schWu = NULL; + SERVICE_STATUS serviceStatus = { }; + QUERY_SERVICE_CONFIGW* pConfig = NULL; + + schSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); + ExitOnNullWithLastError(schSCM, hr, "Failed to open service control manager."); + + schWu = ::OpenServiceW(schSCM, L"wuauserv", SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_STOP ); + ExitOnNullWithLastError(schWu, hr, "Failed to open WU service."); + + if (!::QueryServiceStatus(schWu, &serviceStatus) ) + { + ExitWithLastError(hr, "Failed to query status of WU service."); + } + + // Stop service if requested to. + if (SERVICE_STOPPED != serviceStatus.dwCurrentState && fStopWusaService) + { + hr = StopWUService(schWu); + } + + // If the service is not running then it might be disabled so let's check. + if (SERVICE_RUNNING != serviceStatus.dwCurrentState) + { + hr = SvcQueryConfig(schWu, &pConfig); + ExitOnFailure(hr, "Failed to read configuration for WU service."); + + // If WU is disabled, change it to a demand start service (but touch nothing else). + if (SERVICE_DISABLED == pConfig->dwStartType) + { + hr = SetServiceStartType(schWu, SERVICE_DEMAND_START); + ExitOnFailure(hr, "Failed to mark WU service to start on demand."); + + *pfPreviouslyDisabled = TRUE; + } + } + + *pschWu = schWu; + schWu = NULL; + +LExit: + ReleaseMem(pConfig); + ReleaseServiceHandle(schWu); + ReleaseServiceHandle(schSCM); + + return hr; +} + +static HRESULT SetServiceStartType( + __in SC_HANDLE sch, + __in DWORD startType + ) +{ + HRESULT hr = S_OK; + + if (!::ChangeServiceConfigW(sch, SERVICE_NO_CHANGE, startType, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) + { + ExitWithLastError(hr, "Failed to set service start type."); + } + +LExit: + return hr; +} + +static HRESULT StopWUService( + __in SC_HANDLE schWu + ) +{ + HRESULT hr = S_OK; + SERVICE_STATUS serviceStatus = { }; + + if(!::ControlService(schWu, SERVICE_CONTROL_STOP, &serviceStatus)) + { + ExitWithLastError(hr, "Failed to stop wusa service."); + } + +LExit: + return hr; +} diff --git a/src/burn/engine/msuengine.h b/src/burn/engine/msuengine.h new file mode 100644 index 00000000..fda7a5ab --- /dev/null +++ b/src/burn/engine/msuengine.h @@ -0,0 +1,50 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// function declarations + +HRESULT MsuEngineParsePackageFromXml( + __in IXMLDOMNode* pixnMsiPackage, + __in BURN_PACKAGE* pPackage + ); +void MsuEnginePackageUninitialize( + __in BURN_PACKAGE* pPackage + ); +HRESULT MsuEngineDetectPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_VARIABLES* pVariables + ); +HRESULT MsuEnginePlanCalculatePackage( + __in BURN_PACKAGE* pPackage + ); +HRESULT MsuEnginePlanAddPackage( + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in HANDLE hCacheEvent + ); +HRESULT MsuEngineExecutePackage( + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in BOOL fStopWusaService, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ); +void MsuEngineUpdateInstallRegistrationState( + __in BURN_EXECUTE_ACTION* pAction, + __in HRESULT hrExecute + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/netfxchainer.cpp b/src/burn/engine/netfxchainer.cpp new file mode 100644 index 00000000..4e7a7720 --- /dev/null +++ b/src/burn/engine/netfxchainer.cpp @@ -0,0 +1,418 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +static VOID DestroyNetFxChainer( + __in NetFxChainer* pChainer + ) +{ + if (pChainer) + { + ReleaseHandle(pChainer->hSection); + ReleaseHandle(pChainer->hEventChaineeSend); + ReleaseHandle(pChainer->hEventChainerSend); + ReleaseHandle(pChainer->hMutex); + + if (pChainer->pData) + { + ::UnmapViewOfFile(pChainer->pData); + } + + MemFree(pChainer); + } +} + +static HRESULT CreateNetFxChainer( + __in LPCWSTR wzSectionName, + __in LPCWSTR wzEventName, + __out NetFxChainer** ppChainer + ) +{ + HRESULT hr = S_OK; + LPWSTR sczName = NULL; + NetFxChainer* pChainer = NULL; + + pChainer = (NetFxChainer*)MemAlloc(sizeof(NetFxChainer), TRUE); + ExitOnNull(pChainer, hr, E_OUTOFMEMORY, "Failed to allocate memory for NetFxChainer struct."); + + pChainer->hEventChaineeSend = ::CreateEvent(NULL, FALSE, FALSE, wzEventName); + ExitOnNullWithLastError(pChainer->hEventChaineeSend, hr, "Failed to create event: %ls", wzEventName); + + hr = StrAllocFormatted(&sczName, L"%ls_send", wzEventName); + ExitOnFailure(hr, "failed to allocate memory for event name"); + + pChainer->hEventChainerSend = ::CreateEvent(NULL, FALSE, FALSE, sczName); + ExitOnNullWithLastError(pChainer->hEventChainerSend, hr, "Failed to create event: %ls", sczName); + + hr = StrAllocFormatted(&sczName, L"%ls_mutex", wzEventName); + ExitOnFailure(hr, "failed to allocate memory for mutex name"); + + // Create the mutex, we initially own + pChainer->hMutex = ::CreateMutex(NULL, TRUE, sczName); + ExitOnNullWithLastError(pChainer->hMutex, hr, "Failed to create mutex: %ls", sczName); + + pChainer->hSection = ::CreateFileMapping(INVALID_HANDLE_VALUE, + NULL, // security attributes + PAGE_READWRITE, + 0, // high-order DWORD of maximum size + NETFXDATA_SIZE, // low-order DWORD of maximum size + wzSectionName); + ExitOnNullWithLastError(pChainer->hSection, hr, "Failed to memory map cabinet file: %ls", wzSectionName); + + pChainer->pData = reinterpret_cast(::MapViewOfFile(pChainer->hSection, + FILE_MAP_WRITE, + 0, 0, // offsets + 0 // map entire file + )); + ExitOnNullWithLastError(pChainer->pData, hr, "Failed to MapViewOfFile for %ls.", wzSectionName); + + // Initialize the shared memory + hr = ::StringCchCopyW(pChainer->pData->szEventName, countof(pChainer->pData->szEventName), wzEventName); + ExitOnFailure(hr, "failed to copy event name to shared memory structure."); + pChainer->pData->downloadFinished = false; + pChainer->pData->downloadSoFar = 0; + pChainer->pData->hrDownloadFinished = E_PENDING; + pChainer->pData->downloadAbort = false; + pChainer->pData->installFinished = false; + pChainer->pData->installSoFar = 0; + pChainer->pData->hrInstallFinished = E_PENDING; + pChainer->pData->installAbort = false; + pChainer->pData->hrInternalError = S_OK; + pChainer->pData->version = NETFXDATA_VERSION; + pChainer->pData->messageCode = 0; + pChainer->pData->messageResponse = 0; + pChainer->pData->messageDataLength = 0; + + // Done with initialization, allow others to access. + ::ReleaseMutex(pChainer->hMutex); + + *ppChainer = pChainer; + pChainer = NULL; + +LExit: + ReleaseStr(sczName); + + if (pChainer) + { + // Something failed, release the mutex and destroy the object + if (pChainer->hMutex) + { + ::ReleaseMutex(pChainer->hMutex); + } + + DestroyNetFxChainer(pChainer); + } + + return hr; +} + + +static VOID NetFxAbort( + __in NetFxChainer* pChainer + ) +{ + ::WaitForSingleObject(pChainer->hMutex, INFINITE); + + pChainer->pData->downloadAbort = true; + pChainer->pData->installAbort = true; + + ::ReleaseMutex(pChainer->hMutex); + + ::SetEvent(pChainer->hEventChainerSend); +} + +static BYTE NetFxGetProgress( + __in NetFxChainer* pChainer + ) +{ + BYTE bProgress = 0; + ::WaitForSingleObject(pChainer->hMutex, INFINITE); + + bProgress = (pChainer->pData->installSoFar + pChainer->pData->downloadSoFar) / 2; + + ::ReleaseMutex(pChainer->hMutex); + + return bProgress; +} + +static HRESULT NetFxGetMessage( + __in NetFxChainer* pChainer, + __out DWORD* pdwMessage, + __out LPVOID* ppBuffer, + __out DWORD* pdwBufferSize + ) +{ + HRESULT hr = S_OK; + ::WaitForSingleObject(pChainer->hMutex, INFINITE); + + *pdwMessage = pChainer->pData->messageCode; + *ppBuffer = NULL; + *pdwBufferSize = 0; + + if (NETFX_NO_MESSAGE != *pdwMessage) + { + *ppBuffer = MemAlloc(pChainer->pData->messageDataLength, TRUE); + ExitOnNull(*ppBuffer, hr, E_OUTOFMEMORY, "Failed to allocate memory for message data"); + + memcpy(*ppBuffer, pChainer->pData->messageData, pChainer->pData->messageDataLength); + *pdwBufferSize = pChainer->pData->messageDataLength; + } + +LExit: + ::ReleaseMutex(pChainer->hMutex); + + return hr; +} + +static void NetFxRespond( + __in NetFxChainer* pChainer, + __in DWORD dwResponse + ) +{ + ::WaitForSingleObject(pChainer->hMutex, INFINITE); + + pChainer->pData->messageCode = NETFX_NO_MESSAGE; + pChainer->pData->messageResponse = dwResponse; + if (IDCANCEL == dwResponse) + { + pChainer->pData->downloadAbort = true; + pChainer->pData->installAbort = true; + } + + ::ReleaseMutex(pChainer->hMutex); + + ::SetEvent(pChainer->hEventChainerSend); +} + +static HRESULT NetFxGetResult( + __in NetFxChainer* pChainer, + __out HRESULT* phrInternalError + ) +{ + HRESULT hr = S_OK; + ::WaitForSingleObject(pChainer->hMutex, INFINITE); + + hr = pChainer->pData->hrInstallFinished; + + if (FAILED(pChainer->pData->hrDownloadFinished) && // Download failed + (S_OK == hr || E_ABORT == hr)) // Install succeeded or was aborted + { + hr = pChainer->pData->hrDownloadFinished; + } + + if (phrInternalError) + { + *phrInternalError = pChainer->pData->hrInternalError; + } + + ::ReleaseMutex(pChainer->hMutex); + + return hr; +} + +static HRESULT OnNetFxFilesInUse( + __in NetFxChainer* pNetfxChainer, + __in NetFxCloseApplications* pCloseApps, + __in PFN_GENERICMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + DWORD cFiles = 0; + LPWSTR* rgwzFiles = NULL; + GENERIC_EXECUTE_MESSAGE message = { }; + DWORD dwResponse = 0; + + cFiles = pCloseApps->dwApplicationsSize; + rgwzFiles = (LPWSTR*)MemAlloc(sizeof(LPWSTR*) * cFiles, TRUE); + ExitOnNull(rgwzFiles, hr, E_OUTOFMEMORY, "Failed to allocate buffer."); + + for (DWORD i = 0; i < pCloseApps->dwApplicationsSize; ++i) + { + rgwzFiles[i] = pCloseApps->applications[i].szName; + } + + // send message + message.type = GENERIC_EXECUTE_MESSAGE_FILES_IN_USE; + message.dwAllowedResults = MB_ABORTRETRYIGNORE; + message.filesInUse.cFiles = cFiles; + message.filesInUse.rgwzFiles = (LPCWSTR*)rgwzFiles; + dwResponse = (DWORD)pfnMessageHandler(&message, pvContext); + + NetFxRespond(pNetfxChainer, dwResponse); + +LExit: + ReleaseMem(rgwzFiles); + + return hr; +} + +static HRESULT OnNetFxProgress( + __in NetFxChainer* pNetfxChainer, + __in BYTE bProgress, + __in PFN_GENERICMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext + ) +{ + GENERIC_EXECUTE_MESSAGE message = { }; + DWORD dwResponse = 0; + + // send message + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwAllowedResults = MB_OKCANCEL; + message.progress.dwPercentage = 100 * (DWORD)bProgress / BYTE_MAX; + dwResponse = (DWORD)pfnMessageHandler(&message, pvContext); + + if (IDCANCEL == dwResponse) + { + NetFxAbort(pNetfxChainer); + } + + return S_OK; +} + +static HRESULT OnNetFxError( + __in NetFxChainer* /*pNetfxChainer*/, + __in HRESULT hrError, + __in PFN_GENERICMESSAGEHANDLER pfnMessageHandler, + __in LPVOID pvContext + ) +{ + GENERIC_EXECUTE_MESSAGE message = { }; + DWORD dwResponse = 0; + + // send message + message.type = GENERIC_EXECUTE_MESSAGE_ERROR; + message.dwAllowedResults = MB_OK; + message.error.dwErrorCode = hrError; + message.error.wzMessage = NULL; + dwResponse = (DWORD)pfnMessageHandler(&message, pvContext); + + return S_OK; +} + +static HRESULT ProcessNetFxMessage( + __in NetFxChainer* pNetfxChainer, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + DWORD dwMessage = NETFX_NO_MESSAGE; + DWORD dwBufferSize = 0; + LPVOID pBuffer = NULL; + + // send progress + hr = OnNetFxProgress(pNetfxChainer, NetFxGetProgress(pNetfxChainer), pfnGenericMessageHandler, pvContext); + ExitOnFailure(hr, "Failed to send progress from netfx chainer."); + + // Check for message + hr = NetFxGetMessage(pNetfxChainer, &dwMessage, &pBuffer, &dwBufferSize); + ExitOnFailure(hr, "Failed to get message from netfx chainer."); + + switch(dwMessage) + { + case NETFX_CLOSE_APPS: + hr = OnNetFxFilesInUse(pNetfxChainer, (NetFxCloseApplications*)pBuffer, pfnGenericMessageHandler, pvContext); + ExitOnFailure(hr, "Failed to send files in use message from netfx chainer."); + break; + + default: + // No message we understand. + break; + } + +LExit: + ReleaseMem(pBuffer); + + return hr; +} + +extern "C" HRESULT NetFxRunChainer( + __in LPCWSTR wzExecutablePath, + __in LPCWSTR wzArguments, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out DWORD* pdwExitCode + ) +{ + HRESULT hr = S_OK; + DWORD er = 0; + WCHAR wzGuid[GUID_STRING_LENGTH]; + LPWSTR sczEventName = NULL; + LPWSTR sczSectionName = NULL; + LPWSTR sczCommand = NULL; + NetFxChainer* pNetfxChainer = NULL; + STARTUPINFOW si = { }; + PROCESS_INFORMATION pi = { }; + HRESULT hrInternalError = 0; + + // Create the unique name suffix. + hr = GuidFixedCreate(wzGuid); + ExitOnRootFailure(hr, "Failed to create netfx chainer guid."); + + hr = StrAllocFormatted(&sczSectionName, L"NetFxSection.%ls", wzGuid); + ExitOnFailure(hr, "Failed to allocate section name."); + + hr = StrAllocFormatted(&sczEventName, L"NetFxEvent.%ls", wzGuid); + ExitOnFailure(hr, "Failed to allocate event name."); + + hr = CreateNetFxChainer(sczSectionName, sczEventName, &pNetfxChainer); + ExitOnFailure(hr, "Failed to create netfx chainer."); + + hr = StrAllocFormattedSecure(&sczCommand, L"%ls /pipe %ls", wzArguments, sczSectionName); + ExitOnFailure(hr, "Failed to allocate netfx chainer arguments."); + + si.cb = sizeof(si); + if (!::CreateProcessW(wzExecutablePath, sczCommand, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) + { + ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", wzExecutablePath); + } + + HANDLE handles[2] = { pi.hProcess, pNetfxChainer->hEventChaineeSend }; + + for (;;) + { + er = ::WaitForMultipleObjects(2, handles, FALSE, 100); + if (WAIT_OBJECT_0 == er) + { + // Process has exited + *pdwExitCode = NetFxGetResult(pNetfxChainer, &hrInternalError); + if (E_PENDING == *pdwExitCode) + { + if (!::GetExitCodeProcess(pi.hProcess, pdwExitCode)) + { + ExitWithLastError(hr, "Failed to get netfx return code."); + } + } + else if (FAILED(hrInternalError)) + { + // push internal error message + OnNetFxError(pNetfxChainer, hrInternalError, pfnGenericMessageHandler, pvContext); + ExitOnFailure(hr, "Failed to send internal error message from netfx chainer."); + } + + break; + } + else if (WAIT_OBJECT_0 + 1 == er) + { + // Chainee has notified us of a change. + hr = ProcessNetFxMessage(pNetfxChainer, pfnGenericMessageHandler, pvContext); + ExitOnFailure(hr, "Failed to process netfx chainer message."); + } + else if (WAIT_FAILED == er) + { + ExitWithLastError(hr, "Failed to wait for netfx chainer process to complete"); + } + } + +LExit: + ReleaseStr(sczSectionName); + ReleaseStr(sczEventName); + StrSecureZeroFreeString(sczCommand); + DestroyNetFxChainer(pNetfxChainer); + ReleaseHandle(pi.hThread); + ReleaseHandle(pi.hProcess); + + return hr; +} diff --git a/src/burn/engine/netfxchainer.h b/src/burn/engine/netfxchainer.h new file mode 100644 index 00000000..7d3aff1c --- /dev/null +++ b/src/burn/engine/netfxchainer.h @@ -0,0 +1,98 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + +struct NetFxDataStructure +{ + bool downloadFinished; // download done yet? + bool installFinished; // install done yet? + bool downloadAbort; // set downloader to abort + bool installAbort; // set installer to abort + HRESULT hrDownloadFinished; // resultant HRESULT for download + HRESULT hrInstallFinished; // resultant HRESULT for install + HRESULT hrInternalError; + WCHAR szCurrentItemStep[MAX_PATH]; + BYTE downloadSoFar; // download progress 0 - 255 (0 to 100% done) + BYTE installSoFar; // install progress 0 - 255 (0 to 100% done) + WCHAR szEventName[MAX_PATH]; // event that chainer 'creates' and chainee 'opens'to sync communications + + BYTE version; // version of the data structure, set by chainer. + + DWORD messageCode; // current message being sent by the chainee, 0 if no message is active + DWORD messageResponse; // chainer's response to current message, 0 if not yet handled + DWORD messageDataLength; // length of the m_messageData field in bytes + BYTE messageData[1]; // variable length buffer, content depends on m_messageCode +}; + +struct NetFxChainer +{ + HANDLE hSection; + + HANDLE hEventChaineeSend; + HANDLE hEventChainerSend; + HANDLE hMutex; + + NetFxDataStructure* pData; + DWORD dwDataSize; +}; + +#define NETFXDATA_SIZE 65536 + +#define NETFXDATA_VERSION 1 + +#define NETFX_MESSAGE(version, defaultResponse, messageCode) \ + ((((DWORD)version & 0xFF) << 24) | (((DWORD)defaultResponse & 0xFF) << 16) | ((DWORD)messageCode & 0xFFFF)) +#define NETFX_MESSAGE_CODE(messageId) \ + (messageId & 0xFFFF) +#define NETFX_MESSAGE_DEFAULT_RESPONSE(messageId) \ + ((messageId >> 16) & 0xFF) +#define NETFX_MESSAGE_VERSION(messageId) \ + ((messageId >>24) & 0xFF) + +#define NETFX_NO_MESSAGE 0 + + +//------------------------------------------------------------------------------ +// NETFX_CLOSE_APPS +// +// Sent by the chainee when it detects that applications are holding files in +// use. Respond to this message in order to tell the chainee to close the +// applications to prevent a reboot. +// +// pData : NetFxCloseApplications : The list of applications +// Acceptable responses: +// IDYES : Indicates that the chainee should attempt to shutdown the apps. +// If all apps do not successfully close the message may be sent again. +// IDNO : Indicates that the chainee should not attempt to close apps. +// IDRETRY : Indicates that the chainee should refresh the list of apps. +// Another NETFX_CLOSE_APPS message will be sent asynchronously with +// the new list of apps. +//------------------------------------------------------------------------------ +#define NETFX_CLOSE_APPS NETFX_MESSAGE(NETFXDATA_VERSION, IDNO, 1) + +struct NetFxApplication +{ + WCHAR szName[MAX_PATH]; + DWORD dwPid; +}; + +struct NetFxCloseApplications +{ + DWORD dwApplicationsSize; + NetFxApplication applications[1]; +}; + +HRESULT NetFxRunChainer( + __in LPCWSTR wzExecutablePath, + __in LPCWSTR wzArguments, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out DWORD* pdwExitCode + ); +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/package.cpp b/src/burn/engine/package.cpp new file mode 100644 index 00000000..3f8c8b0f --- /dev/null +++ b/src/burn/engine/package.cpp @@ -0,0 +1,692 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// internal function declarations + +static HRESULT ParsePayloadRefsFromXml( + __in BURN_PACKAGE* pPackage, + __in BURN_PAYLOADS* pPayloads, + __in IXMLDOMNode* pixnPackage + ); +static HRESULT ParsePatchTargetCode( + __in BURN_PACKAGES* pPackages, + __in IXMLDOMNode* pixnBundle + ); +static HRESULT FindRollbackBoundaryById( + __in BURN_PACKAGES* pPackages, + __in_z LPCWSTR wzId, + __out BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary + ); + + +// function definitions + +extern "C" HRESULT PackagesParseFromXml( + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOADS* pPayloads, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + BSTR bstrNodeName = NULL; + DWORD cMspPackages = 0; + LPWSTR scz = NULL; + + // select rollback boundary nodes + hr = XmlSelectNodes(pixnBundle, L"RollbackBoundary", &pixnNodes); + ExitOnFailure(hr, "Failed to select rollback boundary nodes."); + + // get rollback boundary node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get rollback bundary node count."); + + if (cNodes) + { + // allocate memory for rollback boundaries + pPackages->rgRollbackBoundaries = (BURN_ROLLBACK_BOUNDARY*)MemAlloc(sizeof(BURN_ROLLBACK_BOUNDARY) * cNodes, TRUE); + ExitOnNull(pPackages->rgRollbackBoundaries, hr, E_OUTOFMEMORY, "Failed to allocate memory for rollback boundary structs."); + + pPackages->cRollbackBoundaries = cNodes; + + // parse rollback boundary elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = &pPackages->rgRollbackBoundaries[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, &bstrNodeName); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &pRollbackBoundary->sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + // @Vital + hr = XmlGetYesNoAttribute(pixnNode, L"Vital", &pRollbackBoundary->fVital); + ExitOnFailure(hr, "Failed to get @Vital."); + + // @Transaction + hr = XmlGetYesNoAttribute(pixnNode, L"Transaction", &pRollbackBoundary->fTransaction); + ExitOnFailure(hr, "Failed to get @Transaction."); + + // prepare next iteration + ReleaseNullObject(pixnNode); + ReleaseNullBSTR(bstrNodeName); + } + } + + ReleaseNullObject(pixnNodes); // done with the RollbackBoundary elements. + + // select package nodes + hr = XmlSelectNodes(pixnBundle, L"Chain/ExePackage|Chain/MsiPackage|Chain/MspPackage|Chain/MsuPackage", &pixnNodes); + ExitOnFailure(hr, "Failed to select package nodes."); + + // get package node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get package node count."); + + if (!cNodes) + { + ExitFunction1(hr = S_OK); + } + + // allocate memory for packages + pPackages->rgPackages = (BURN_PACKAGE*)MemAlloc(sizeof(BURN_PACKAGE) * cNodes, TRUE); + ExitOnNull(pPackages->rgPackages, hr, E_OUTOFMEMORY, "Failed to allocate memory for package structs."); + + pPackages->cPackages = cNodes; + + // parse package elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_PACKAGE* pPackage = &pPackages->rgPackages[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, &bstrNodeName); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &pPackage->sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + // @Cache + hr = XmlGetAttributeEx(pixnNode, L"Cache", &scz); + if (SUCCEEDED(hr)) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"remove", -1)) + { + pPackage->authoredCacheType = BOOTSTRAPPER_CACHE_TYPE_REMOVE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"keep", -1)) + { + pPackage->authoredCacheType = BOOTSTRAPPER_CACHE_TYPE_KEEP; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"force", -1)) + { + pPackage->authoredCacheType = BOOTSTRAPPER_CACHE_TYPE_FORCE; + } + else + { + hr = E_UNEXPECTED; + ExitOnRootFailure(hr, "Invalid cache type: %ls", scz); + } + } + ExitOnFailure(hr, "Failed to get @Cache."); + + // @CacheId + hr = XmlGetAttributeEx(pixnNode, L"CacheId", &pPackage->sczCacheId); + ExitOnFailure(hr, "Failed to get @CacheId."); + + // @Size + hr = XmlGetAttributeLargeNumber(pixnNode, L"Size", &pPackage->qwSize); + ExitOnFailure(hr, "Failed to get @Size."); + + // @InstallSize + hr = XmlGetAttributeLargeNumber(pixnNode, L"InstallSize", &pPackage->qwInstallSize); + ExitOnFailure(hr, "Failed to get @InstallSize."); + + // @PerMachine + hr = XmlGetYesNoAttribute(pixnNode, L"PerMachine", &pPackage->fPerMachine); + ExitOnFailure(hr, "Failed to get @PerMachine."); + + // @Permanent + hr = XmlGetYesNoAttribute(pixnNode, L"Permanent", &pPackage->fUninstallable); + ExitOnFailure(hr, "Failed to get @Permanent."); + pPackage->fUninstallable = !pPackage->fUninstallable; // TODO: change "Uninstallable" variable name to permanent, until then Uninstallable is the opposite of Permanent so fix the variable. + pPackage->fCanAffectRegistration = pPackage->fUninstallable; + + // @Vital + hr = XmlGetYesNoAttribute(pixnNode, L"Vital", &pPackage->fVital); + ExitOnFailure(hr, "Failed to get @Vital."); + + // @LogPathVariable + hr = XmlGetAttributeEx(pixnNode, L"LogPathVariable", &pPackage->sczLogPathVariable); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @LogPathVariable."); + } + + // @RollbackLogPathVariable + hr = XmlGetAttributeEx(pixnNode, L"RollbackLogPathVariable", &pPackage->sczRollbackLogPathVariable); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @RollbackLogPathVariable."); + } + + // @InstallCondition + hr = XmlGetAttributeEx(pixnNode, L"InstallCondition", &pPackage->sczInstallCondition); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @InstallCondition."); + } + + // @RollbackBoundaryForward + hr = XmlGetAttributeEx(pixnNode, L"RollbackBoundaryForward", &scz); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @RollbackBoundaryForward."); + + hr = FindRollbackBoundaryById(pPackages, scz, &pPackage->pRollbackBoundaryForward); + ExitOnFailure(hr, "Failed to find forward transaction boundary: %ls", scz); + } + + // @RollbackBoundaryBackward + hr = XmlGetAttributeEx(pixnNode, L"RollbackBoundaryBackward", &scz); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @RollbackBoundaryBackward."); + + hr = FindRollbackBoundaryById(pPackages, scz, &pPackage->pRollbackBoundaryBackward); + ExitOnFailure(hr, "Failed to find backward transaction boundary: %ls", scz); + } + + // read type specific attributes + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"ExePackage", -1)) + { + pPackage->type = BURN_PACKAGE_TYPE_EXE; + + hr = ExeEngineParsePackageFromXml(pixnNode, pPackage); // TODO: Modularization + ExitOnFailure(hr, "Failed to parse EXE package."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"MsiPackage", -1)) + { + pPackage->type = BURN_PACKAGE_TYPE_MSI; + + hr = MsiEngineParsePackageFromXml(pixnNode, pPackage); // TODO: Modularization + ExitOnFailure(hr, "Failed to parse MSI package."); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"MspPackage", -1)) + { + pPackage->type = BURN_PACKAGE_TYPE_MSP; + + hr = MspEngineParsePackageFromXml(pixnNode, pPackage); // TODO: Modularization + ExitOnFailure(hr, "Failed to parse MSP package."); + + ++cMspPackages; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"MsuPackage", -1)) + { + pPackage->type = BURN_PACKAGE_TYPE_MSU; + + hr = MsuEngineParsePackageFromXml(pixnNode, pPackage); // TODO: Modularization + ExitOnFailure(hr, "Failed to parse MSU package."); + } + else + { + // ignore other package types for now + } + + // parse payload references + hr = ParsePayloadRefsFromXml(pPackage, pPayloads, pixnNode); + ExitOnFailure(hr, "Failed to parse payload references."); + + // parse dependency providers + hr = DependencyParseProvidersFromXml(pPackage, pixnNode); + ExitOnFailure(hr, "Failed to parse dependency providers."); + + // prepare next iteration + ReleaseNullObject(pixnNode); + ReleaseNullBSTR(bstrNodeName); + } + + if (cMspPackages) + { + pPackages->rgPatchInfo = static_cast(MemAlloc(sizeof(MSIPATCHSEQUENCEINFOW) * cMspPackages, TRUE)); + ExitOnNull(pPackages->rgPatchInfo, hr, E_OUTOFMEMORY, "Failed to allocate memory for MSP patch sequence information."); + + pPackages->rgPatchInfoToPackage = static_cast(MemAlloc(sizeof(BURN_PACKAGE*) * cMspPackages, TRUE)); + ExitOnNull(pPackages->rgPatchInfoToPackage, hr, E_OUTOFMEMORY, "Failed to allocate memory for patch sequence information to package lookup."); + + for (DWORD i = 0; i < pPackages->cPackages; ++i) + { + BURN_PACKAGE* pPackage = &pPackages->rgPackages[i]; + + if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + pPackages->rgPatchInfo[pPackages->cPatchInfo].szPatchData = pPackage->Msp.sczApplicabilityXml; + pPackages->rgPatchInfo[pPackages->cPatchInfo].ePatchDataType = MSIPATCH_DATATYPE_XMLBLOB; + pPackages->rgPatchInfoToPackage[pPackages->cPatchInfo] = pPackage; + ++pPackages->cPatchInfo; + + // Loop through all MSI packages seeing if any of them slipstream this MSP. + for (DWORD j = 0; j < pPackages->cPackages; ++j) + { + BURN_PACKAGE* pMsiPackage = &pPackages->rgPackages[j]; + + if (BURN_PACKAGE_TYPE_MSI == pMsiPackage->type) + { + for (DWORD k = 0; k < pMsiPackage->Msi.cSlipstreamMspPackages; ++k) + { + if (pMsiPackage->Msi.rgsczSlipstreamMspPackageIds[k] && CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pPackage->sczId, -1, pMsiPackage->Msi.rgsczSlipstreamMspPackageIds[k], -1)) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pMsiPackage->Msi.rgSlipstreamMsps + k; + pSlipstreamMsp->pMspPackage = pPackage; + pSlipstreamMsp->dwMsiChainedPatchIndex = BURN_PACKAGE_INVALID_PATCH_INDEX; + + ReleaseNullStr(pMsiPackage->Msi.rgsczSlipstreamMspPackageIds[k]); // we don't need the slipstream package id any longer so free it. + } + } + } + } + } + } + } + + AssertSz(pPackages->cPatchInfo == cMspPackages, "Count of packages patch info should be equal to the number of MSP packages."); + +#if DEBUG + // Loop through all MSI packages seeing if any of them are missing their slipstream MSP. + for (DWORD i = 0; i < pPackages->cPackages; ++i) + { + BURN_PACKAGE* pPackage = &pPackages->rgPackages[i]; + + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) + { + for (DWORD k = 0; k < pPackage->Msi.cSlipstreamMspPackages; ++k) + { + if (pPackage->Msi.rgsczSlipstreamMspPackageIds[k]) + { + AssertSz(FALSE, "MSI slipstream MSP package doesn't exist."); + } + } + } + } +#endif + + hr = ParsePatchTargetCode(pPackages, pixnBundle); + ExitOnFailure(hr, "Failed to parse target product codes."); + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseBSTR(bstrNodeName); + ReleaseStr(scz); + + return hr; +} + +extern "C" void PackageUninitialize( + __in BURN_PACKAGE* pPackage + ) +{ + ReleaseStr(pPackage->sczId); + ReleaseStr(pPackage->sczLogPathVariable); + ReleaseStr(pPackage->sczRollbackLogPathVariable); + ReleaseStr(pPackage->sczInstallCondition); + ReleaseStr(pPackage->sczCacheId); + + if (pPackage->rgDependencyProviders) + { + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + DependencyUninitializeProvider(pPackage->rgDependencyProviders + i); + } + MemFree(pPackage->rgDependencyProviders); + } + + ReleaseMem(pPackage->payloads.rgItems); + + switch (pPackage->type) + { + case BURN_PACKAGE_TYPE_EXE: + ExeEnginePackageUninitialize(pPackage); // TODO: Modularization + break; + case BURN_PACKAGE_TYPE_MSI: + MsiEnginePackageUninitialize(pPackage); // TODO: Modularization + break; + case BURN_PACKAGE_TYPE_MSP: + MspEnginePackageUninitialize(pPackage); // TODO: Modularization + break; + case BURN_PACKAGE_TYPE_MSU: + MsuEnginePackageUninitialize(pPackage); // TODO: Modularization + break; + } +} + +extern "C" void PackagesUninitialize( + __in BURN_PACKAGES* pPackages + ) +{ + if (pPackages->rgRollbackBoundaries) + { + for (DWORD i = 0; i < pPackages->cRollbackBoundaries; ++i) + { + ReleaseStr(pPackages->rgRollbackBoundaries[i].sczId); + ReleaseStr(pPackages->rgRollbackBoundaries[i].sczLogPath); + } + MemFree(pPackages->rgRollbackBoundaries); + } + + if (pPackages->rgPackages) + { + for (DWORD i = 0; i < pPackages->cPackages; ++i) + { + PackageUninitialize(pPackages->rgPackages + i); + } + MemFree(pPackages->rgPackages); + } + + if (pPackages->rgPatchTargetCodes) + { + for (DWORD i = 0; i < pPackages->cPatchTargetCodes; ++i) + { + ReleaseStr(pPackages->rgPatchTargetCodes[i].sczTargetCode); + } + MemFree(pPackages->rgPatchTargetCodes); + } + + ReleaseMem(pPackages->rgPatchInfo); + ReleaseMem(pPackages->rgPatchInfoToPackage); + + // clear struct + memset(pPackages, 0, sizeof(BURN_PACKAGES)); +} + +extern "C" HRESULT PackageFindById( + __in BURN_PACKAGES* pPackages, + __in_z LPCWSTR wzId, + __out BURN_PACKAGE** ppPackage + ) +{ + HRESULT hr = S_OK; + BURN_PACKAGE* pPackage = NULL; + + for (DWORD i = 0; i < pPackages->cPackages; ++i) + { + pPackage = &pPackages->rgPackages[i]; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pPackage->sczId, -1, wzId, -1)) + { + *ppPackage = pPackage; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} + + +extern "C" HRESULT PackageFindRelatedById( + __in BURN_RELATED_BUNDLES* pRelatedBundles, + __in_z LPCWSTR wzId, + __out BURN_PACKAGE** ppPackage + ) +{ + HRESULT hr = S_OK; + BURN_PACKAGE* pPackage = NULL; + + for (DWORD i = 0; i < pRelatedBundles->cRelatedBundles; ++i) + { + pPackage = &pRelatedBundles->rgRelatedBundles[i].package; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pPackage->sczId, -1, wzId, -1)) + { + *ppPackage = pPackage; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} + +/******************************************************************** + PackageGetProperty - Determines if the property is defined + and optionally copies the property value. + + Note: The caller must free psczValue if requested. + + Note: Returns E_NOTFOUND if the property was not defined or if the + package does not support properties. + +*********************************************************************/ +extern "C" HRESULT PackageGetProperty( + __in const BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzProperty, + __out_z_opt LPWSTR* psczValue + ) +{ + HRESULT hr = E_NOTFOUND; + BURN_MSIPROPERTY* rgProperties = NULL; + DWORD cProperties = 0; + + // For MSIs and MSPs, enumerate the properties looking for wzProperty. + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) + { + rgProperties = pPackage->Msi.rgProperties; + cProperties = pPackage->Msi.cProperties; + } + else if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + rgProperties = pPackage->Msp.rgProperties; + cProperties = pPackage->Msp.cProperties; + } + + for (DWORD i = 0; i < cProperties; ++i) + { + const BURN_MSIPROPERTY* pProperty = &rgProperties[i]; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pProperty->sczId, -1, wzProperty, -1)) + { + if (psczValue) + { + hr = StrAllocString(psczValue, pProperty->sczValue, 0); + ExitOnFailure(hr, "Failed to copy the property value."); + } + + ExitFunction1(hr = S_OK); + } + } + +LExit: + return hr; +} + +extern "C" HRESULT PackageFindRollbackBoundaryById( + __in BURN_PACKAGES* pPackages, + __in_z LPCWSTR wzId, + __out BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary + ) +{ + HRESULT hr = S_OK; + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL; + + for (DWORD i = 0; i < pPackages->cRollbackBoundaries; ++i) + { + pRollbackBoundary = &pPackages->rgRollbackBoundaries[i]; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pRollbackBoundary->sczId, -1, wzId, -1)) + { + *ppRollbackBoundary = pRollbackBoundary; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} + + +// internal function declarations + +static HRESULT ParsePayloadRefsFromXml( + __in BURN_PACKAGE* pPackage, + __in BURN_PAYLOADS* pPayloads, + __in IXMLDOMNode* pixnPackage + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + LPWSTR sczId = NULL; + + // select package nodes + hr = XmlSelectNodes(pixnPackage, L"PayloadRef", &pixnNodes); + ExitOnFailure(hr, "Failed to select package nodes."); + + // get package node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get package node count."); + + if (!cNodes) + { + ExitFunction1(hr = S_OK); + } + + // allocate memory for payload pointers + pPackage->payloads.rgItems = (BURN_PAYLOAD_GROUP_ITEM*)MemAlloc(sizeof(BURN_PAYLOAD_GROUP_ITEM) * cNodes, TRUE); + ExitOnNull(pPackage->payloads.rgItems, hr, E_OUTOFMEMORY, "Failed to allocate memory for package payloads."); + + pPackage->payloads.cItems = cNodes; + + // parse package elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_PAYLOAD_GROUP_ITEM* pPackagePayload = pPackage->payloads.rgItems + i; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &sczId); + ExitOnFailure(hr, "Failed to get Id attribute."); + + // find payload + hr = PayloadFindById(pPayloads, sczId, &pPackagePayload->pPayload); + ExitOnFailure(hr, "Failed to find payload."); + + pPackage->payloads.qwTotalSize += pPackagePayload->pPayload->qwFileSize; + + // prepare next iteration + ReleaseNullObject(pixnNode); + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(sczId); + + return hr; +} + +static HRESULT ParsePatchTargetCode( + __in BURN_PACKAGES* pPackages, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + BSTR bstrNodeText = NULL; + BOOL fProduct; + + hr = XmlSelectNodes(pixnBundle, L"PatchTargetCode", &pixnNodes); + ExitOnFailure(hr, "Failed to select PatchTargetCode nodes."); + + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get PatchTargetCode node count."); + + if (!cNodes) + { + ExitFunction1(hr = S_OK); + } + + pPackages->rgPatchTargetCodes = (BURN_PATCH_TARGETCODE*)MemAlloc(sizeof(BURN_PATCH_TARGETCODE) * cNodes, TRUE); + ExitOnNull(pPackages->rgPatchTargetCodes, hr, E_OUTOFMEMORY, "Failed to allocate memory for patch targetcodes."); + + pPackages->cPatchTargetCodes = cNodes; + + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_PATCH_TARGETCODE* pTargetCode = pPackages->rgPatchTargetCodes + i; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + hr = XmlGetAttributeEx(pixnNode, L"TargetCode", &pTargetCode->sczTargetCode); + ExitOnFailure(hr, "Failed to get @TargetCode attribute."); + + hr = XmlGetYesNoAttribute(pixnNode, L"Product", &fProduct); + if (E_NOTFOUND == hr) + { + fProduct = FALSE; + hr = S_OK; + } + ExitOnFailure(hr, "Failed to get @Product."); + + pTargetCode->type = fProduct ? BURN_PATCH_TARGETCODE_TYPE_PRODUCT : BURN_PATCH_TARGETCODE_TYPE_UPGRADE; + + // prepare next iteration + ReleaseNullBSTR(bstrNodeText); + ReleaseNullObject(pixnNode); + } + +LExit: + ReleaseBSTR(bstrNodeText); + ReleaseObject(pixnNode); + ReleaseObject(pixnNodes); + + return hr; +} + +static HRESULT FindRollbackBoundaryById( + __in BURN_PACKAGES* pPackages, + __in_z LPCWSTR wzId, + __out BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary + ) +{ + HRESULT hr = S_OK; + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL; + + for (DWORD i = 0; i < pPackages->cRollbackBoundaries; ++i) + { + pRollbackBoundary = &pPackages->rgRollbackBoundaries[i]; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pRollbackBoundary->sczId, -1, wzId, -1)) + { + *ppRollbackBoundary = pRollbackBoundary; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} diff --git a/src/burn/engine/package.h b/src/burn/engine/package.h new file mode 100644 index 00000000..89a3d6e9 --- /dev/null +++ b/src/burn/engine/package.h @@ -0,0 +1,380 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + +struct _BURN_RELATED_BUNDLES; +typedef _BURN_RELATED_BUNDLES BURN_RELATED_BUNDLES; + +struct _BURN_PACKAGE; +typedef _BURN_PACKAGE BURN_PACKAGE; + +// constants + +const DWORD BURN_PACKAGE_INVALID_PATCH_INDEX = 0x80000000; + +enum BURN_EXE_EXIT_CODE_TYPE +{ + BURN_EXE_EXIT_CODE_TYPE_NONE, + BURN_EXE_EXIT_CODE_TYPE_SUCCESS, + BURN_EXE_EXIT_CODE_TYPE_ERROR, + BURN_EXE_EXIT_CODE_TYPE_SCHEDULE_REBOOT, + BURN_EXE_EXIT_CODE_TYPE_FORCE_REBOOT, +}; + +enum BURN_EXE_PROTOCOL_TYPE +{ + BURN_EXE_PROTOCOL_TYPE_NONE, + BURN_EXE_PROTOCOL_TYPE_BURN, + BURN_EXE_PROTOCOL_TYPE_NETFX4, +}; + +enum BURN_PACKAGE_TYPE +{ + BURN_PACKAGE_TYPE_NONE, + BURN_PACKAGE_TYPE_EXE, + BURN_PACKAGE_TYPE_MSI, + BURN_PACKAGE_TYPE_MSP, + BURN_PACKAGE_TYPE_MSU, +}; + +enum BURN_DEPENDENCY_ACTION +{ + BURN_DEPENDENCY_ACTION_NONE, + BURN_DEPENDENCY_ACTION_REGISTER, + BURN_DEPENDENCY_ACTION_UNREGISTER, +}; + +enum BURN_PATCH_TARGETCODE_TYPE +{ + BURN_PATCH_TARGETCODE_TYPE_UNKNOWN, + BURN_PATCH_TARGETCODE_TYPE_PRODUCT, + BURN_PATCH_TARGETCODE_TYPE_UPGRADE, +}; + +enum BOOTSTRAPPER_FEATURE_ACTION +{ + BOOTSTRAPPER_FEATURE_ACTION_NONE, + BOOTSTRAPPER_FEATURE_ACTION_ADDLOCAL, + BOOTSTRAPPER_FEATURE_ACTION_ADDSOURCE, + BOOTSTRAPPER_FEATURE_ACTION_ADDDEFAULT, + BOOTSTRAPPER_FEATURE_ACTION_REINSTALL, + BOOTSTRAPPER_FEATURE_ACTION_ADVERTISE, + BOOTSTRAPPER_FEATURE_ACTION_REMOVE, +}; + +enum BURN_PACKAGE_REGISTRATION_STATE +{ + BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN, + BURN_PACKAGE_REGISTRATION_STATE_ABSENT, + BURN_PACKAGE_REGISTRATION_STATE_IGNORED, + BURN_PACKAGE_REGISTRATION_STATE_PRESENT, +}; + +enum BURN_PATCH_SKIP_STATE +{ + BURN_PATCH_SKIP_STATE_NONE, + BURN_PATCH_SKIP_STATE_TARGET_UNINSTALL, + BURN_PATCH_SKIP_STATE_SLIPSTREAM, +}; + +// structs + +typedef struct _BURN_EXE_EXIT_CODE +{ + BURN_EXE_EXIT_CODE_TYPE type; + DWORD dwCode; + BOOL fWildcard; +} BURN_EXE_EXIT_CODE; + +typedef struct _BURN_EXE_COMMAND_LINE_ARGUMENT +{ + LPWSTR sczInstallArgument; + LPWSTR sczUninstallArgument; + LPWSTR sczRepairArgument; + LPWSTR sczCondition; +} BURN_EXE_COMMAND_LINE_ARGUMENT; + +typedef struct _BURN_MSPTARGETPRODUCT +{ + MSIINSTALLCONTEXT context; + DWORD dwOrder; + WCHAR wzTargetProductCode[39]; + BURN_PACKAGE* pChainedTargetPackage; + BOOL fInstalled; + BOOL fSlipstream; + BOOL fSlipstreamRequired; // this means the target product is not present on the machine, but is available in the chain as a slipstream target. + + BOOTSTRAPPER_PACKAGE_STATE patchPackageState; // only valid after Detect. + BOOTSTRAPPER_REQUEST_STATE defaultRequested; // only valid during Plan. + BOOTSTRAPPER_REQUEST_STATE requested; // only valid during Plan. + BOOTSTRAPPER_ACTION_STATE execute; // only valid during Plan. + BOOTSTRAPPER_ACTION_STATE rollback; // only valid during Plan. + BURN_PATCH_SKIP_STATE executeSkip; // only valid during Plan. + BURN_PATCH_SKIP_STATE rollbackSkip; // only valid during Plan. + + BURN_PACKAGE_REGISTRATION_STATE registrationState; // initialized during Detect, updated during Apply. + BURN_PACKAGE_REGISTRATION_STATE transactionRegistrationState;// only valid during Apply inside an MSI transaction. +} BURN_MSPTARGETPRODUCT; + +typedef struct _BURN_MSIPROPERTY +{ + LPWSTR sczId; + LPWSTR sczValue; // used during forward execution + LPWSTR sczRollbackValue; // used during rollback + LPWSTR sczCondition; +} BURN_MSIPROPERTY; + +typedef struct _BURN_MSIFEATURE +{ + LPWSTR sczId; + LPWSTR sczAddLocalCondition; + LPWSTR sczAddSourceCondition; + LPWSTR sczAdvertiseCondition; + LPWSTR sczRollbackAddLocalCondition; + LPWSTR sczRollbackAddSourceCondition; + LPWSTR sczRollbackAdvertiseCondition; + + BOOTSTRAPPER_FEATURE_STATE currentState; // only valid after Detect. + BOOTSTRAPPER_FEATURE_STATE expectedState; // only valid during Plan. + BOOTSTRAPPER_FEATURE_STATE defaultRequested; // only valid during Plan. + BOOTSTRAPPER_FEATURE_STATE requested; // only valid during Plan. + BOOTSTRAPPER_FEATURE_ACTION execute; // only valid during Plan. + BOOTSTRAPPER_FEATURE_ACTION rollback; // only valid during Plan. +} BURN_MSIFEATURE; + +typedef struct _BURN_RELATED_MSI +{ + LPWSTR sczUpgradeCode; + VERUTIL_VERSION* pMinVersion; + VERUTIL_VERSION* pMaxVersion; + BOOL fMinProvided; + BOOL fMaxProvided; + BOOL fMinInclusive; + BOOL fMaxInclusive; + BOOL fOnlyDetect; + BOOL fLangInclusive; + + DWORD* rgdwLanguages; + DWORD cLanguages; +} BURN_RELATED_MSI; + +typedef struct _BURN_CHAINED_PATCH +{ + BURN_PACKAGE* pMspPackage; + DWORD dwMspTargetProductIndex; // index into the Msp.rgTargetProducts +} BURN_CHAINED_PATCH; + +typedef struct _BURN_SLIPSTREAM_MSP +{ + BURN_PACKAGE* pMspPackage; + DWORD dwMsiChainedPatchIndex; // index into the Msi.rgChainedPatches + + BOOTSTRAPPER_ACTION_STATE execute; // only valid during Plan. + BOOTSTRAPPER_ACTION_STATE rollback; // only valid during Plan. +} BURN_SLIPSTREAM_MSP; + +typedef struct _BURN_DEPENDENCY_PROVIDER +{ + LPWSTR sczKey; + LPWSTR sczVersion; + LPWSTR sczDisplayName; + BOOL fImported; + + DEPENDENCY* rgDependents; // only valid after Detect. + UINT cDependents; // only valid after Detect. +} BURN_DEPENDENCY_PROVIDER; + +typedef struct _BURN_ROLLBACK_BOUNDARY +{ + LPWSTR sczId; + BOOL fVital; + BOOL fTransaction; + BOOL fActiveTransaction; // only valid during Apply. + LPWSTR sczLogPath; +} BURN_ROLLBACK_BOUNDARY; + +typedef struct _BURN_PATCH_TARGETCODE +{ + LPWSTR sczTargetCode; + BURN_PATCH_TARGETCODE_TYPE type; +} BURN_PATCH_TARGETCODE; + +typedef struct _BURN_PACKAGE +{ + LPWSTR sczId; + + LPWSTR sczLogPathVariable; // name of the variable that will be set to the log path. + LPWSTR sczRollbackLogPathVariable; // name of the variable that will be set to the rollback path. + + LPWSTR sczInstallCondition; + BOOL fPerMachine; + BOOL fUninstallable; + BOOL fVital; + BOOL fCanAffectRegistration; + + BOOTSTRAPPER_CACHE_TYPE authoredCacheType; + LPWSTR sczCacheId; + + DWORD64 qwInstallSize; + DWORD64 qwSize; + + BURN_ROLLBACK_BOUNDARY* pRollbackBoundaryForward; // used during install and repair. + BURN_ROLLBACK_BOUNDARY* pRollbackBoundaryBackward; // used during uninstall. + + BOOTSTRAPPER_PACKAGE_STATE currentState; // only valid after Detect. + BOOL fCached; // only valid after Detect. + BOOL fPackageProviderExists; // only valid after Detect. + BOOTSTRAPPER_CACHE_TYPE cacheType; // only valid during Plan. + BOOTSTRAPPER_REQUEST_STATE defaultRequested;// only valid during Plan. + BOOTSTRAPPER_REQUEST_STATE requested; // only valid during Plan. + BOOL fPlannedCache; // only valid during Plan. + BOOL fPlannedUncache; // only valid during Plan. + BOOTSTRAPPER_ACTION_STATE execute; // only valid during Plan. + BOOTSTRAPPER_ACTION_STATE rollback; // only valid during Plan. + BURN_DEPENDENCY_ACTION providerExecute; // only valid during Plan. + BURN_DEPENDENCY_ACTION providerRollback; // only valid during Plan. + BURN_DEPENDENCY_ACTION dependencyExecute; // only valid during Plan. + BURN_DEPENDENCY_ACTION dependencyRollback; // only valid during Plan. + BOOL fDependencyManagerWasHere; // only valid during Plan. + LPWSTR sczCacheFolder; // only valid during Apply. + HRESULT hrCacheResult; // only valid during Apply. + + BURN_PACKAGE_REGISTRATION_STATE cacheRegistrationState; // initialized during Detect, updated during Apply. + BURN_PACKAGE_REGISTRATION_STATE installRegistrationState; // initialized during Detect, updated during Apply. + BURN_PACKAGE_REGISTRATION_STATE expectedCacheRegistrationState; // only valid after Plan. + BURN_PACKAGE_REGISTRATION_STATE expectedInstallRegistrationState;// only valid after Plan. + BURN_PACKAGE_REGISTRATION_STATE transactionRegistrationState; // only valid during Apply inside an MSI transaction. + + BURN_PAYLOAD_GROUP payloads; + + BURN_DEPENDENCY_PROVIDER* rgDependencyProviders; + DWORD cDependencyProviders; + + BURN_PACKAGE_TYPE type; + union + { + struct + { + LPWSTR sczDetectCondition; + LPWSTR sczInstallArguments; + LPWSTR sczRepairArguments; + LPWSTR sczUninstallArguments; + LPWSTR sczIgnoreDependencies; + LPCWSTR wzAncestors; // points directly into engine state. + + BOOL fPseudoBundle; + + BOOL fRepairable; + BURN_EXE_PROTOCOL_TYPE protocol; + + BOOL fSupportsAncestors; + + BURN_EXE_EXIT_CODE* rgExitCodes; + DWORD cExitCodes; + + BURN_EXE_COMMAND_LINE_ARGUMENT* rgCommandLineArguments; + DWORD cCommandLineArguments; + } Exe; + struct + { + LPWSTR sczProductCode; + DWORD dwLanguage; + VERUTIL_VERSION* pVersion; + VERUTIL_VERSION* pInstalledVersion; + LPWSTR sczUpgradeCode; + + BURN_MSIPROPERTY* rgProperties; + DWORD cProperties; + + BURN_MSIFEATURE* rgFeatures; + DWORD cFeatures; + + BURN_RELATED_MSI* rgRelatedMsis; + DWORD cRelatedMsis; + + BURN_SLIPSTREAM_MSP* rgSlipstreamMsps; + LPWSTR* rgsczSlipstreamMspPackageIds; + DWORD cSlipstreamMspPackages; + + BURN_CHAINED_PATCH* rgChainedPatches; + DWORD cChainedPatches; + } Msi; + struct + { + LPWSTR sczPatchCode; + LPWSTR sczApplicabilityXml; + + BURN_MSIPROPERTY* rgProperties; + DWORD cProperties; + + BURN_MSPTARGETPRODUCT* rgTargetProducts; + DWORD cTargetProductCodes; + } Msp; + struct + { + LPWSTR sczDetectCondition; + LPWSTR sczKB; + } Msu; + }; +} BURN_PACKAGE; + +typedef struct _BURN_PACKAGES +{ + BURN_ROLLBACK_BOUNDARY* rgRollbackBoundaries; + DWORD cRollbackBoundaries; + + BURN_PACKAGE* rgPackages; + DWORD cPackages; + + BURN_PATCH_TARGETCODE* rgPatchTargetCodes; + DWORD cPatchTargetCodes; + + MSIPATCHSEQUENCEINFOW* rgPatchInfo; + BURN_PACKAGE** rgPatchInfoToPackage; // direct lookup from patch information to the (MSP) package it describes. + // Thus this array is the exact same size as rgPatchInfo. + DWORD cPatchInfo; +} BURN_PACKAGES; + + +// function declarations + +HRESULT PackagesParseFromXml( + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOADS* pPayloads, + __in IXMLDOMNode* pixnBundle + ); +void PackageUninitialize( + __in BURN_PACKAGE* pPackage + ); +void PackagesUninitialize( + __in BURN_PACKAGES* pPackages + ); +HRESULT PackageFindById( + __in BURN_PACKAGES* pPackages, + __in_z LPCWSTR wzId, + __out BURN_PACKAGE** ppPackage + ); +HRESULT PackageFindRelatedById( + __in BURN_RELATED_BUNDLES* pRelatedBundles, + __in_z LPCWSTR wzId, + __out BURN_PACKAGE** ppPackage + ); +HRESULT PackageGetProperty( + __in const BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzProperty, + __out_z_opt LPWSTR* psczValue + ); +HRESULT PackageFindRollbackBoundaryById( + __in BURN_PACKAGES* pPackages, + __in_z LPCWSTR wzId, + __out BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/packages.config b/src/burn/engine/packages.config new file mode 100644 index 00000000..7219a3da --- /dev/null +++ b/src/burn/engine/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/burn/engine/payload.cpp b/src/burn/engine/payload.cpp new file mode 100644 index 00000000..72eb3476 --- /dev/null +++ b/src/burn/engine/payload.cpp @@ -0,0 +1,314 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// internal function declarations + +static HRESULT FindEmbeddedBySourcePath( + __in BURN_PAYLOADS* pPayloads, + __in_opt BURN_CONTAINER* pContainer, + __in_z LPCWSTR wzStreamName, + __out BURN_PAYLOAD** ppPayload + ); + + +// function definitions + +extern "C" HRESULT PayloadsParseFromXml( + __in BURN_PAYLOADS* pPayloads, + __in_opt BURN_CONTAINERS* pContainers, + __in_opt BURN_PAYLOAD_GROUP* pLayoutPayloads, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + LPWSTR scz = NULL; + + // select payload nodes + hr = XmlSelectNodes(pixnBundle, L"Payload", &pixnNodes); + ExitOnFailure(hr, "Failed to select payload nodes."); + + // get payload node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get payload node count."); + + if (!cNodes) + { + ExitFunction(); + } + + // allocate memory for payloads + pPayloads->rgPayloads = (BURN_PAYLOAD*)MemAlloc(sizeof(BURN_PAYLOAD) * cNodes, TRUE); + ExitOnNull(pPayloads->rgPayloads, hr, E_OUTOFMEMORY, "Failed to allocate memory for payload structs."); + + pPayloads->cPayloads = cNodes; + + // parse search elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_PAYLOAD* pPayload = &pPayloads->rgPayloads[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &pPayload->sczKey); + ExitOnFailure(hr, "Failed to get @Id."); + + // @FilePath + hr = XmlGetAttributeEx(pixnNode, L"FilePath", &pPayload->sczFilePath); + ExitOnFailure(hr, "Failed to get @FilePath."); + + // @Packaging + hr = XmlGetAttributeEx(pixnNode, L"Packaging", &scz); + ExitOnFailure(hr, "Failed to get @Packaging."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"embedded", -1)) + { + pPayload->packaging = BURN_PAYLOAD_PACKAGING_EMBEDDED; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"external", -1)) + { + pPayload->packaging = BURN_PAYLOAD_PACKAGING_EXTERNAL; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Packaging: %ls", scz); + } + + // @Container + if (pContainers) + { + hr = XmlGetAttributeEx(pixnNode, L"Container", &scz); + if (E_NOTFOUND != hr || BURN_PAYLOAD_PACKAGING_EMBEDDED == pPayload->packaging) + { + ExitOnFailure(hr, "Failed to get @Container."); + + // find container + hr = ContainerFindById(pContainers, scz, &pPayload->pContainer); + ExitOnFailure(hr, "Failed to to find container: %ls", scz); + } + } + + // @LayoutOnly + hr = XmlGetYesNoAttribute(pixnNode, L"LayoutOnly", &pPayload->fLayoutOnly); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @LayoutOnly."); + } + + // @SourcePath + hr = XmlGetAttributeEx(pixnNode, L"SourcePath", &pPayload->sczSourcePath); + ExitOnFailure(hr, "Failed to get @SourcePath."); + + // @DownloadUrl + hr = XmlGetAttributeEx(pixnNode, L"DownloadUrl", &pPayload->downloadSource.sczUrl); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @DownloadUrl."); + } + + // @FileSize + hr = XmlGetAttributeEx(pixnNode, L"FileSize", &scz); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @FileSize."); + + hr = StrStringToUInt64(scz, 0, &pPayload->qwFileSize); + ExitOnFailure(hr, "Failed to parse @FileSize."); + } + + // @Hash + hr = XmlGetAttributeEx(pixnNode, L"Hash", &scz); + ExitOnFailure(hr, "Failed to get @Hash."); + + hr = StrAllocHexDecode(scz, &pPayload->pbHash, &pPayload->cbHash); + ExitOnFailure(hr, "Failed to hex decode the Payload/@Hash."); + + if (pPayload->fLayoutOnly && pLayoutPayloads) + { + hr = MemEnsureArraySize(reinterpret_cast(&pLayoutPayloads->rgItems), pLayoutPayloads->cItems + 1, sizeof(BURN_PAYLOAD_GROUP_ITEM), 5); + ExitOnFailure(hr, "Failed to allocate memory for layout payloads."); + + pLayoutPayloads->rgItems[pLayoutPayloads->cItems].pPayload = pPayload; + ++pLayoutPayloads->cItems; + + pLayoutPayloads->qwTotalSize += pPayload->qwFileSize; + } + + // prepare next iteration + ReleaseNullObject(pixnNode); + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(scz); + + return hr; +} + +extern "C" void PayloadUninitialize( + __in BURN_PAYLOAD* pPayload + ) +{ + if (pPayload) + { + ReleaseStr(pPayload->sczKey); + ReleaseStr(pPayload->sczFilePath); + ReleaseMem(pPayload->pbHash); + ReleaseStr(pPayload->sczSourcePath); + ReleaseStr(pPayload->sczLocalFilePath); + ReleaseStr(pPayload->downloadSource.sczUrl); + ReleaseStr(pPayload->downloadSource.sczUser); + ReleaseStr(pPayload->downloadSource.sczPassword); + ReleaseStr(pPayload->sczUnverifiedPath); + } +} + +extern "C" void PayloadsUninitialize( + __in BURN_PAYLOADS* pPayloads + ) +{ + if (pPayloads->rgPayloads) + { + for (DWORD i = 0; i < pPayloads->cPayloads; ++i) + { + PayloadUninitialize(pPayloads->rgPayloads + i); + } + MemFree(pPayloads->rgPayloads); + } + + // clear struct + memset(pPayloads, 0, sizeof(BURN_PAYLOADS)); +} + +extern "C" HRESULT PayloadExtractUXContainer( + __in BURN_PAYLOADS* pPayloads, + __in BURN_CONTAINER_CONTEXT* pContainerContext, + __in_z LPCWSTR wzTargetDir + ) +{ + HRESULT hr = S_OK; + LPWSTR sczStreamName = NULL; + LPWSTR sczDirectory = NULL; + BURN_PAYLOAD* pPayload = NULL; + + // extract all payloads + for (;;) + { + // get next stream + hr = ContainerNextStream(pContainerContext, &sczStreamName); + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + break; + } + ExitOnFailure(hr, "Failed to get next stream."); + + // find payload by stream name + hr = PayloadFindEmbeddedBySourcePath(pPayloads, sczStreamName, &pPayload); + ExitOnFailure(hr, "Failed to find embedded payload: %ls", sczStreamName); + + // make file path + hr = PathConcat(wzTargetDir, pPayload->sczFilePath, &pPayload->sczLocalFilePath); + ExitOnFailure(hr, "Failed to concat file paths."); + + // extract file + hr = PathGetDirectory(pPayload->sczLocalFilePath, &sczDirectory); + ExitOnFailure(hr, "Failed to get directory portion of local file path"); + + hr = DirEnsureExists(sczDirectory, NULL); + ExitOnFailure(hr, "Failed to ensure directory exists"); + + hr = ContainerStreamToFile(pContainerContext, pPayload->sczLocalFilePath); + ExitOnFailure(hr, "Failed to extract file."); + + // flag that the payload has been acquired + pPayload->state = BURN_PAYLOAD_STATE_ACQUIRED; + } + + // locate any payloads that were not extracted + for (DWORD i = 0; i < pPayloads->cPayloads; ++i) + { + pPayload = &pPayloads->rgPayloads[i]; + + // if the payload has not been acquired + if (BURN_PAYLOAD_STATE_ACQUIRED > pPayload->state) + { + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Payload was not found in container: %ls", pPayload->sczKey); + } + } + +LExit: + ReleaseStr(sczStreamName); + ReleaseStr(sczDirectory); + + return hr; +} + +extern "C" HRESULT PayloadFindById( + __in BURN_PAYLOADS* pPayloads, + __in_z LPCWSTR wzId, + __out BURN_PAYLOAD** ppPayload + ) +{ + HRESULT hr = S_OK; + BURN_PAYLOAD* pPayload = NULL; + + for (DWORD i = 0; i < pPayloads->cPayloads; ++i) + { + pPayload = &pPayloads->rgPayloads[i]; + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pPayload->sczKey, -1, wzId, -1)) + { + *ppPayload = pPayload; + ExitFunction1(hr = S_OK); + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} + +extern "C" HRESULT PayloadFindEmbeddedBySourcePath( + __in BURN_PAYLOADS* pPayloads, + __in_z LPCWSTR wzStreamName, + __out BURN_PAYLOAD** ppPayload + ) +{ + HRESULT hr = S_OK; + BURN_PAYLOAD* pPayload = NULL; + + for (DWORD i = 0; i < pPayloads->cPayloads; ++i) + { + pPayload = &pPayloads->rgPayloads[i]; + + if (BURN_PAYLOAD_PACKAGING_EMBEDDED == pPayload->packaging) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pPayload->sczSourcePath, -1, wzStreamName, -1)) + { + *ppPayload = pPayload; + ExitFunction1(hr = S_OK); + } + } + } + + hr = E_NOTFOUND; + +LExit: + return hr; +} + + +// internal function definitions diff --git a/src/burn/engine/payload.h b/src/burn/engine/payload.h new file mode 100644 index 00000000..f28b437f --- /dev/null +++ b/src/burn/engine/payload.h @@ -0,0 +1,107 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + +enum BURN_PAYLOAD_PACKAGING +{ + BURN_PAYLOAD_PACKAGING_NONE, + BURN_PAYLOAD_PACKAGING_EMBEDDED, + BURN_PAYLOAD_PACKAGING_EXTERNAL, +}; + +enum BURN_PAYLOAD_STATE +{ + BURN_PAYLOAD_STATE_NONE, + BURN_PAYLOAD_STATE_ACQUIRED, + BURN_PAYLOAD_STATE_CACHED, +}; + + +// structs + +typedef struct _BURN_PAYLOAD +{ + LPWSTR sczKey; + BURN_PAYLOAD_PACKAGING packaging; + BOOL fLayoutOnly; + DWORD64 qwFileSize; + LPWSTR sczFilePath; // file path relative to the execute location + + BYTE* pbHash; + DWORD cbHash; + + LPWSTR sczSourcePath; + BURN_CONTAINER* pContainer; + DOWNLOAD_SOURCE downloadSource; + + // mutable members + BURN_PAYLOAD_STATE state; + LPWSTR sczLocalFilePath; // location of extracted or downloaded copy + + LPWSTR sczUnverifiedPath; + DWORD cRemainingInstances; +} BURN_PAYLOAD; + +typedef struct _BURN_PAYLOADS +{ + BURN_PAYLOAD* rgPayloads; + DWORD cPayloads; +} BURN_PAYLOADS; + +typedef struct _BURN_PAYLOAD_GROUP_ITEM +{ + BURN_PAYLOAD* pPayload; + + // mutable members + BOOL fCached; + DWORD64 qwCommittedCacheProgress; +} BURN_PAYLOAD_GROUP_ITEM; + +typedef struct _BURN_PAYLOAD_GROUP +{ + BURN_PAYLOAD_GROUP_ITEM* rgItems; + DWORD cItems; + DWORD64 qwTotalSize; +} BURN_PAYLOAD_GROUP; + +// functions + +HRESULT PayloadsParseFromXml( + __in BURN_PAYLOADS* pPayloads, + __in_opt BURN_CONTAINERS* pContainers, + __in_opt BURN_PAYLOAD_GROUP* pLayoutPayloads, + __in IXMLDOMNode* pixnBundle + ); +void PayloadUninitialize( + __in BURN_PAYLOAD* pPayload + ); +void PayloadsUninitialize( + __in BURN_PAYLOADS* pPayloads + ); +HRESULT PayloadExtractUXContainer( + __in BURN_PAYLOADS* pPayloads, + __in BURN_CONTAINER_CONTEXT* pContainerContext, + __in_z LPCWSTR wzTargetDir + ); +HRESULT PayloadFindById( + __in BURN_PAYLOADS* pPayloads, + __in_z LPCWSTR wzId, + __out BURN_PAYLOAD** ppPayload + ); +HRESULT PayloadFindEmbeddedBySourcePath( + __in BURN_PAYLOADS* pPayloads, + __in_z LPCWSTR wzStreamName, + __out BURN_PAYLOAD** ppPayload + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/pipe.cpp b/src/burn/engine/pipe.cpp new file mode 100644 index 00000000..a9fd24e8 --- /dev/null +++ b/src/burn/engine/pipe.cpp @@ -0,0 +1,821 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +static const DWORD PIPE_64KB = 64 * 1024; +static const DWORD PIPE_WAIT_FOR_CONNECTION = 100; // wait a 10th of a second, +static const DWORD PIPE_RETRY_FOR_CONNECTION = 1800; // for up to 3 minutes. + +static const LPCWSTR PIPE_NAME_FORMAT_STRING = L"\\\\.\\pipe\\%ls"; +static const LPCWSTR CACHE_PIPE_NAME_FORMAT_STRING = L"\\\\.\\pipe\\%ls.Cache"; + +static HRESULT AllocatePipeMessage( + __in DWORD dwMessage, + __in_bcount_opt(cbData) LPVOID pvData, + __in SIZE_T cbData, + __out_bcount(cb) LPVOID* ppvMessage, + __out SIZE_T* cbMessage + ); +static void FreePipeMessage( + __in BURN_PIPE_MESSAGE *pMsg + ); +static HRESULT WritePipeMessage( + __in HANDLE hPipe, + __in DWORD dwMessage, + __in_bcount_opt(cbData) LPVOID pvData, + __in SIZE_T cbData + ); +static HRESULT GetPipeMessage( + __in HANDLE hPipe, + __in BURN_PIPE_MESSAGE* pMsg + ); +static HRESULT ChildPipeConnected( + __in HANDLE hPipe, + __in_z LPCWSTR wzSecret, + __inout DWORD* pdwProcessId + ); + + + +/******************************************************************* + PipeConnectionInitialize - initialize pipe connection data. + +*******************************************************************/ +void PipeConnectionInitialize( + __in BURN_PIPE_CONNECTION* pConnection + ) +{ + memset(pConnection, 0, sizeof(BURN_PIPE_CONNECTION)); + pConnection->hPipe = INVALID_HANDLE_VALUE; + pConnection->hCachePipe = INVALID_HANDLE_VALUE; +} + +/******************************************************************* + PipeConnectionUninitialize - free data in a pipe connection. + +*******************************************************************/ +void PipeConnectionUninitialize( + __in BURN_PIPE_CONNECTION* pConnection + ) +{ + ReleaseFileHandle(pConnection->hCachePipe); + ReleaseFileHandle(pConnection->hPipe); + ReleaseHandle(pConnection->hProcess); + ReleaseStr(pConnection->sczSecret); + ReleaseStr(pConnection->sczName); + + memset(pConnection, 0, sizeof(BURN_PIPE_CONNECTION)); + pConnection->hPipe = INVALID_HANDLE_VALUE; + pConnection->hCachePipe = INVALID_HANDLE_VALUE; +} + +/******************************************************************* + PipeSendMessage - + +*******************************************************************/ +extern "C" HRESULT PipeSendMessage( + __in HANDLE hPipe, + __in DWORD dwMessage, + __in_bcount_opt(cbData) LPVOID pvData, + __in SIZE_T cbData, + __in_opt PFN_PIPE_MESSAGE_CALLBACK pfnCallback, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + BURN_PIPE_RESULT result = { }; + + hr = WritePipeMessage(hPipe, dwMessage, pvData, cbData); + ExitOnFailure(hr, "Failed to write send message to pipe."); + + hr = PipePumpMessages(hPipe, pfnCallback, pvContext, &result); + ExitOnFailure(hr, "Failed to pump messages during send message to pipe."); + + *pdwResult = result.dwResult; + +LExit: + return hr; +} + +/******************************************************************* + PipePumpMessages - + +*******************************************************************/ +extern "C" HRESULT PipePumpMessages( + __in HANDLE hPipe, + __in_opt PFN_PIPE_MESSAGE_CALLBACK pfnCallback, + __in_opt LPVOID pvContext, + __in BURN_PIPE_RESULT* pResult + ) +{ + HRESULT hr = S_OK; + BURN_PIPE_MESSAGE msg = { }; + SIZE_T iData = 0; + LPSTR sczMessage = NULL; + DWORD dwResult = 0; + + // Pump messages from child process. + while (S_OK == (hr = GetPipeMessage(hPipe, &msg))) + { + switch (msg.dwMessage) + { + case BURN_PIPE_MESSAGE_TYPE_LOG: + iData = 0; + + hr = BuffReadStringAnsi((BYTE*)msg.pvData, msg.cbData, &iData, &sczMessage); + ExitOnFailure(hr, "Failed to read log message."); + + hr = LogStringWorkRaw(sczMessage); + ExitOnFailure(hr, "Failed to write log message:'%hs'.", sczMessage); + + dwResult = static_cast(hr); + break; + + case BURN_PIPE_MESSAGE_TYPE_COMPLETE: + if (!msg.pvData || sizeof(DWORD) != msg.cbData) + { + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "No status returned to PipePumpMessages()"); + } + + pResult->dwResult = *static_cast(msg.pvData); + ExitFunction1(hr = S_OK); // exit loop. + + case BURN_PIPE_MESSAGE_TYPE_TERMINATE: + iData = 0; + + hr = BuffReadNumber(static_cast(msg.pvData), msg.cbData, &iData, &pResult->dwResult); + ExitOnFailure(hr, "Failed to read returned result to PipePumpMessages()"); + + if (sizeof(DWORD) * 2 == msg.cbData) + { + hr = BuffReadNumber(static_cast(msg.pvData), msg.cbData, &iData, (DWORD*)&pResult->fRestart); + ExitOnFailure(hr, "Failed to read returned restart to PipePumpMessages()"); + } + + ExitFunction1(hr = S_OK); // exit loop. + + default: + if (pfnCallback) + { + hr = pfnCallback(&msg, pvContext, &dwResult); + } + else + { + hr = E_INVALIDARG; + } + ExitOnFailure(hr, "Failed to process message: %u", msg.dwMessage); + break; + } + + // post result + hr = WritePipeMessage(hPipe, static_cast(BURN_PIPE_MESSAGE_TYPE_COMPLETE), &dwResult, sizeof(dwResult)); + ExitOnFailure(hr, "Failed to post result to child process."); + + FreePipeMessage(&msg); + } + ExitOnFailure(hr, "Failed to get message over pipe"); + + if (S_FALSE == hr) + { + hr = S_OK; + } + +LExit: + ReleaseStr(sczMessage); + FreePipeMessage(&msg); + + return hr; +} + +/******************************************************************* + PipeCreateNameAndSecret - + +*******************************************************************/ +extern "C" HRESULT PipeCreateNameAndSecret( + __out_z LPWSTR *psczConnectionName, + __out_z LPWSTR *psczSecret + ) +{ + HRESULT hr = S_OK; + WCHAR wzGuid[GUID_STRING_LENGTH]; + LPWSTR sczConnectionName = NULL; + LPWSTR sczSecret = NULL; + + // Create the unique pipe name. + hr = GuidFixedCreate(wzGuid); + ExitOnRootFailure(hr, "Failed to create pipe guid."); + + hr = StrAllocFormatted(&sczConnectionName, L"BurnPipe.%s", wzGuid); + ExitOnFailure(hr, "Failed to allocate pipe name."); + + // Create the unique client secret. + hr = GuidFixedCreate(wzGuid); + ExitOnRootFailure(hr, "Failed to create pipe secret."); + + hr = StrAllocString(&sczSecret, wzGuid, 0); + ExitOnFailure(hr, "Failed to allocate pipe secret."); + + *psczConnectionName = sczConnectionName; + sczConnectionName = NULL; + *psczSecret = sczSecret; + sczSecret = NULL; + +LExit: + ReleaseStr(sczSecret); + ReleaseStr(sczConnectionName); + + return hr; +} + +/******************************************************************* + PipeCreatePipes - create the pipes and event to signal child process. + +*******************************************************************/ +extern "C" HRESULT PipeCreatePipes( + __in BURN_PIPE_CONNECTION* pConnection, + __in BOOL fCreateCachePipe, + __out HANDLE* phEvent + ) +{ + Assert(pConnection->sczName); + Assert(INVALID_HANDLE_VALUE == pConnection->hPipe); + Assert(INVALID_HANDLE_VALUE == pConnection->hCachePipe); + + HRESULT hr = S_OK; + PSECURITY_DESCRIPTOR psd = NULL; + SECURITY_ATTRIBUTES sa = { }; + LPWSTR sczFullPipeName = NULL; + HANDLE hPipe = INVALID_HANDLE_VALUE; + HANDLE hCachePipe = INVALID_HANDLE_VALUE; + + // Only the grant special rights when the pipe is being used for "embedded" + // scenarios (aka: there is no cache pipe). + if (!fCreateCachePipe) + { + // Create the security descriptor that grants read/write/sync access to Everyone. + // TODO: consider locking down "WD" to LogonIds (logon session) + LPCWSTR wzSddl = L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW0x00100000;;;WD)"; + if (!::ConvertStringSecurityDescriptorToSecurityDescriptorW(wzSddl, SDDL_REVISION_1, &psd, NULL)) + { + ExitWithLastError(hr, "Failed to create the security descriptor for the connection event and pipe."); + } + + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = psd; + sa.bInheritHandle = FALSE; + } + + // Create the pipe. + hr = StrAllocFormatted(&sczFullPipeName, PIPE_NAME_FORMAT_STRING, pConnection->sczName); + ExitOnFailure(hr, "Failed to allocate full name of pipe: %ls", pConnection->sczName); + + // TODO: consider using overlapped IO to do waits on the pipe and still be able to cancel and such. + hPipe = ::CreateNamedPipeW(sczFullPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, PIPE_64KB, PIPE_64KB, 1, psd ? &sa : NULL); + if (INVALID_HANDLE_VALUE == hPipe) + { + ExitWithLastError(hr, "Failed to create pipe: %ls", sczFullPipeName); + } + + if (fCreateCachePipe) + { + // Create the cache pipe. + hr = StrAllocFormatted(&sczFullPipeName, CACHE_PIPE_NAME_FORMAT_STRING, pConnection->sczName); + ExitOnFailure(hr, "Failed to allocate full name of cache pipe: %ls", pConnection->sczName); + + hCachePipe = ::CreateNamedPipeW(sczFullPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, PIPE_64KB, PIPE_64KB, 1, NULL); + if (INVALID_HANDLE_VALUE == hCachePipe) + { + ExitWithLastError(hr, "Failed to create pipe: %ls", sczFullPipeName); + } + } + + pConnection->hCachePipe = hCachePipe; + hCachePipe = INVALID_HANDLE_VALUE; + + pConnection->hPipe = hPipe; + hPipe = INVALID_HANDLE_VALUE; + + // TODO: remove the following + *phEvent = NULL; + +LExit: + ReleaseFileHandle(hCachePipe); + ReleaseFileHandle(hPipe); + ReleaseStr(sczFullPipeName); + + if (psd) + { + ::LocalFree(psd); + } + + return hr; +} + +/******************************************************************* + PipeLaunchParentProcess - Called from the per-machine process to create + a per-user process and set up the + communication pipe. + +*******************************************************************/ +const LPCWSTR BURN_COMMANDLINE_SWITCH_UNELEVATED = L"burn.unelevated"; +HRESULT PipeLaunchParentProcess( + __in_z LPCWSTR wzCommandLine, + __in int nCmdShow, + __in_z LPWSTR sczConnectionName, + __in_z LPWSTR sczSecret, + __in BOOL /*fDisableUnelevate*/ + ) +{ + HRESULT hr = S_OK; + DWORD dwProcessId = 0; + LPWSTR sczBurnPath = NULL; + LPWSTR sczParameters = NULL; + HANDLE hProcess = NULL; + + dwProcessId = ::GetCurrentProcessId(); + + hr = PathForCurrentProcess(&sczBurnPath, NULL); + ExitOnFailure(hr, "Failed to get current process path."); + + hr = StrAllocFormatted(&sczParameters, L"-%ls %ls %ls %u %ls", BURN_COMMANDLINE_SWITCH_UNELEVATED, sczConnectionName, sczSecret, dwProcessId, wzCommandLine); + ExitOnFailure(hr, "Failed to allocate parameters for unelevated process."); + +#ifdef ENABLE_UNELEVATE + if (fDisableUnelevate) + { + hr = ProcExec(sczBurnPath, sczParameters, nCmdShow, &hProcess); + ExitOnFailure(hr, "Failed to launch parent process with unelevate disabled: %ls", sczBurnPath); + } + else + { + // Try to launch unelevated and if that fails for any reason, try launch our process normally (even though that may make it elevated). + hr = ProcExecuteAsInteractiveUser(sczBurnPath, sczParameters, &hProcess); + if (FAILED(hr)) + { + hr = ShelExecUnelevated(sczBurnPath, sczParameters, L"open", NULL, nCmdShow); + if (FAILED(hr)) + { + hr = ShelExec(sczBurnPath, sczParameters, L"open", NULL, nCmdShow, NULL, NULL); + ExitOnFailure(hr, "Failed to launch parent process: %ls", sczBurnPath); + } + } + } +#else + hr = ProcExec(sczBurnPath, sczParameters, nCmdShow, &hProcess); + ExitOnFailure(hr, "Failed to launch parent process with unelevate disabled: %ls", sczBurnPath); +#endif + +LExit: + ReleaseHandle(hProcess); + ReleaseStr(sczParameters); + ReleaseStr(sczBurnPath); + + return hr; +} + +/******************************************************************* + PipeLaunchChildProcess - Called from the per-user process to create + the per-machine process and set up the + communication pipe. + +*******************************************************************/ +extern "C" HRESULT PipeLaunchChildProcess( + __in_z LPCWSTR wzExecutablePath, + __in BURN_PIPE_CONNECTION* pConnection, + __in BOOL fElevate, + __in_opt HWND hwndParent + ) +{ + HRESULT hr = S_OK; + DWORD dwCurrentProcessId = ::GetCurrentProcessId(); + LPWSTR sczParameters = NULL; + LPCWSTR wzVerb = NULL; + HANDLE hProcess = NULL; + + hr = StrAllocFormatted(&sczParameters, L"-q -%ls %ls %ls %u", BURN_COMMANDLINE_SWITCH_ELEVATED, pConnection->sczName, pConnection->sczSecret, dwCurrentProcessId); + ExitOnFailure(hr, "Failed to allocate parameters for elevated process."); + + wzVerb = !fElevate ? L"open" : L"runas"; + + // Since ShellExecuteEx doesn't support passing inherited handles, don't bother with CoreAppendFileHandleSelfToCommandLine. + // We could fallback to using ::DuplicateHandle to inject the file handle later if necessary. + hr = ShelExec(wzExecutablePath, sczParameters, wzVerb, NULL, SW_SHOWNA, hwndParent, &hProcess); + ExitOnFailure(hr, "Failed to launch elevated child process: %ls", wzExecutablePath); + + pConnection->dwProcessId = ::GetProcessId(hProcess); + pConnection->hProcess = hProcess; + hProcess = NULL; + +LExit: + ReleaseHandle(hProcess); + ReleaseStr(sczParameters); + + return hr; +} + +/******************************************************************* + PipeWaitForChildConnect - + +*******************************************************************/ +extern "C" HRESULT PipeWaitForChildConnect( + __in BURN_PIPE_CONNECTION* pConnection + ) +{ + HRESULT hr = S_OK; + HANDLE hPipes[2] = { pConnection->hPipe, pConnection->hCachePipe}; + LPCWSTR wzSecret = pConnection->sczSecret; + DWORD cbSecret = lstrlenW(wzSecret) * sizeof(WCHAR); + DWORD dwCurrentProcessId = ::GetCurrentProcessId(); + DWORD dwAck = 0; + + for (DWORD i = 0; i < countof(hPipes) && INVALID_HANDLE_VALUE != hPipes[i]; ++i) + { + HANDLE hPipe = hPipes[i]; + DWORD dwPipeState = PIPE_READMODE_BYTE | PIPE_NOWAIT; + + // Temporarily make the pipe non-blocking so we will not get stuck in ::ConnectNamedPipe() forever + // if the child decides not to show up. + if (!::SetNamedPipeHandleState(hPipe, &dwPipeState, NULL, NULL)) + { + ExitWithLastError(hr, "Failed to set pipe to non-blocking."); + } + + // Loop for a while waiting for a connection from child process. + DWORD cRetry = 0; + do + { + if (!::ConnectNamedPipe(hPipe, NULL)) + { + DWORD er = ::GetLastError(); + if (ERROR_PIPE_CONNECTED == er) + { + hr = S_OK; + break; + } + else if (ERROR_PIPE_LISTENING == er) + { + if (cRetry < PIPE_RETRY_FOR_CONNECTION) + { + hr = HRESULT_FROM_WIN32(er); + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + break; + } + + ++cRetry; + ::Sleep(PIPE_WAIT_FOR_CONNECTION); + } + else + { + hr = HRESULT_FROM_WIN32(er); + break; + } + } + } while (HRESULT_FROM_WIN32(ERROR_PIPE_LISTENING) == hr); + ExitOnRootFailure(hr, "Failed to wait for child to connect to pipe."); + + // Put the pipe back in blocking mode. + dwPipeState = PIPE_READMODE_BYTE | PIPE_WAIT; + if (!::SetNamedPipeHandleState(hPipe, &dwPipeState, NULL, NULL)) + { + ExitWithLastError(hr, "Failed to reset pipe to blocking."); + } + + // Prove we are the one that created the elevated process by passing the secret. + hr = FileWriteHandle(hPipe, reinterpret_cast(&cbSecret), sizeof(cbSecret)); + ExitOnFailure(hr, "Failed to write secret length to pipe."); + + hr = FileWriteHandle(hPipe, reinterpret_cast(wzSecret), cbSecret); + ExitOnFailure(hr, "Failed to write secret to pipe."); + + hr = FileWriteHandle(hPipe, reinterpret_cast(&dwCurrentProcessId), sizeof(dwCurrentProcessId)); + ExitOnFailure(hr, "Failed to write our process id to pipe."); + + // Wait until the elevated process responds that it is ready to go. + hr = FileReadHandle(hPipe, reinterpret_cast(&dwAck), sizeof(dwAck)); + ExitOnFailure(hr, "Failed to read ACK from pipe."); + + // The ACK should match out expected child process id. + //if (pConnection->dwProcessId != dwAck) + //{ + // hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + // ExitOnRootFailure(hr, "Incorrect ACK from elevated pipe: %u", dwAck); + //} + } + +LExit: + return hr; +} + +/******************************************************************* + PipeTerminateChildProcess - + +*******************************************************************/ +extern "C" HRESULT PipeTerminateChildProcess( + __in BURN_PIPE_CONNECTION* pConnection, + __in DWORD dwParentExitCode, + __in BOOL fRestart + ) +{ + HRESULT hr = S_OK; + BYTE* pbData = NULL; + SIZE_T cbData = 0; + + // Prepare the exit message. + hr = BuffWriteNumber(&pbData, &cbData, dwParentExitCode); + ExitOnFailure(hr, "Failed to write exit code to message buffer."); + + hr = BuffWriteNumber(&pbData, &cbData, fRestart); + ExitOnFailure(hr, "Failed to write restart to message buffer."); + + // Send the messages. + if (INVALID_HANDLE_VALUE != pConnection->hCachePipe) + { + hr = WritePipeMessage(pConnection->hCachePipe, static_cast(BURN_PIPE_MESSAGE_TYPE_TERMINATE), pbData, cbData); + ExitOnFailure(hr, "Failed to post terminate message to child process cache thread."); + } + + hr = WritePipeMessage(pConnection->hPipe, static_cast(BURN_PIPE_MESSAGE_TYPE_TERMINATE), pbData, cbData); + ExitOnFailure(hr, "Failed to post terminate message to child process."); + + // If we were able to get a handle to the other process, wait for it to exit. + if (pConnection->hProcess) + { + if (WAIT_FAILED == ::WaitForSingleObject(pConnection->hProcess, PIPE_WAIT_FOR_CONNECTION * PIPE_RETRY_FOR_CONNECTION)) + { + ExitWithLastError(hr, "Failed to wait for child process exit."); + } + +#ifdef DEBUG + DWORD dwChildExitCode = 0; + DWORD dwErrorCode = ERROR_SUCCESS; + BOOL fReturnedExitCode = ::GetExitCodeProcess(pConnection->hProcess, &dwChildExitCode); + if (!fReturnedExitCode) + { + dwErrorCode = ::GetLastError(); // if the other process is elevated and we are not, then we'll get ERROR_ACCESS_DENIED. + + // The unit test use a thread instead of a process so try to get the exit code from + // the thread because we failed to get it from the process. + if (ERROR_INVALID_HANDLE == dwErrorCode) + { + fReturnedExitCode = ::GetExitCodeThread(pConnection->hProcess, &dwChildExitCode); + } + } + AssertSz((fReturnedExitCode && dwChildExitCode == dwParentExitCode) || + (!fReturnedExitCode && ERROR_ACCESS_DENIED == dwErrorCode), + "Child elevated process did not return matching exit code to parent process."); +#endif + } + +LExit: + return hr; +} + +/******************************************************************* + PipeChildConnect - Called from the child process to connect back + to the pipe provided by the parent process. + +*******************************************************************/ +extern "C" HRESULT PipeChildConnect( + __in BURN_PIPE_CONNECTION* pConnection, + __in BOOL fConnectCachePipe + ) +{ + Assert(pConnection->sczName); + Assert(pConnection->sczSecret); + Assert(!pConnection->hProcess); + Assert(INVALID_HANDLE_VALUE == pConnection->hPipe); + Assert(INVALID_HANDLE_VALUE == pConnection->hCachePipe); + + HRESULT hr = S_OK; + LPWSTR sczPipeName = NULL; + + // Try to connect to the parent. + hr = StrAllocFormatted(&sczPipeName, PIPE_NAME_FORMAT_STRING, pConnection->sczName); + ExitOnFailure(hr, "Failed to allocate name of parent pipe."); + + hr = E_UNEXPECTED; + for (DWORD cRetry = 0; FAILED(hr) && cRetry < PIPE_RETRY_FOR_CONNECTION; ++cRetry) + { + pConnection->hPipe = ::CreateFileW(sczPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (INVALID_HANDLE_VALUE == pConnection->hPipe) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + if (E_FILENOTFOUND == hr) // if the pipe isn't created, call it a timeout waiting on the parent. + { + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + } + + ::Sleep(PIPE_WAIT_FOR_CONNECTION); + } + else // we have a connection, go with it. + { + hr = S_OK; + } + } + ExitOnRootFailure(hr, "Failed to open parent pipe: %ls", sczPipeName) + + // Verify the parent and notify it that the child connected. + hr = ChildPipeConnected(pConnection->hPipe, pConnection->sczSecret, &pConnection->dwProcessId); + ExitOnFailure(hr, "Failed to verify parent pipe: %ls", sczPipeName); + + if (fConnectCachePipe) + { + // Connect to the parent for the cache pipe. + hr = StrAllocFormatted(&sczPipeName, CACHE_PIPE_NAME_FORMAT_STRING, pConnection->sczName); + ExitOnFailure(hr, "Failed to allocate name of parent cache pipe."); + + pConnection->hCachePipe = ::CreateFileW(sczPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (INVALID_HANDLE_VALUE == pConnection->hCachePipe) + { + ExitWithLastError(hr, "Failed to open parent pipe: %ls", sczPipeName) + } + + // Verify the parent and notify it that the child connected. + hr = ChildPipeConnected(pConnection->hCachePipe, pConnection->sczSecret, &pConnection->dwProcessId); + ExitOnFailure(hr, "Failed to verify parent pipe: %ls", sczPipeName); + } + + pConnection->hProcess = ::OpenProcess(SYNCHRONIZE, FALSE, pConnection->dwProcessId); + ExitOnNullWithLastError(pConnection->hProcess, hr, "Failed to open companion process with PID: %u", pConnection->dwProcessId); + +LExit: + ReleaseStr(sczPipeName); + + return hr; +} + + +static HRESULT AllocatePipeMessage( + __in DWORD dwMessage, + __in_bcount_opt(cbData) LPVOID pvData, + __in SIZE_T cbData, + __out_bcount(cb) LPVOID* ppvMessage, + __out SIZE_T* cbMessage + ) +{ + HRESULT hr = S_OK; + LPVOID pv = NULL; + SIZE_T cb = 0; + + // If no data was provided, ensure the count of bytes is zero. + if (!pvData) + { + cbData = 0; + } + + // Allocate the message. + cb = sizeof(dwMessage) + sizeof(cbData) + cbData; + pv = MemAlloc(cb, FALSE); + ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate memory for message."); + + memcpy_s(pv, cb, &dwMessage, sizeof(dwMessage)); + memcpy_s(static_cast(pv) + sizeof(dwMessage), cb - sizeof(dwMessage), &cbData, sizeof(cbData)); + if (cbData) + { + memcpy_s(static_cast(pv) + sizeof(dwMessage) + sizeof(cbData), cb - sizeof(dwMessage) - sizeof(cbData), pvData, cbData); + } + + *cbMessage = cb; + *ppvMessage = pv; + pv = NULL; + +LExit: + ReleaseMem(pv); + return hr; +} + +static void FreePipeMessage( + __in BURN_PIPE_MESSAGE *pMsg + ) +{ + if (pMsg->fAllocatedData) + { + ReleaseNullMem(pMsg->pvData); + pMsg->fAllocatedData = FALSE; + } +} + +static HRESULT WritePipeMessage( + __in HANDLE hPipe, + __in DWORD dwMessage, + __in_bcount_opt(cbData) LPVOID pvData, + __in SIZE_T cbData + ) +{ + HRESULT hr = S_OK; + LPVOID pv = NULL; + SIZE_T cb = 0; + + hr = AllocatePipeMessage(dwMessage, pvData, cbData, &pv, &cb); + ExitOnFailure(hr, "Failed to allocate message to write."); + + // Write the message. + hr = FileWriteHandle(hPipe, reinterpret_cast(pv), cb); + ExitOnFailure(hr, "Failed to write message type to pipe."); + +LExit: + ReleaseMem(pv); + return hr; +} + +static HRESULT GetPipeMessage( + __in HANDLE hPipe, + __in BURN_PIPE_MESSAGE* pMsg + ) +{ + HRESULT hr = S_OK; + BYTE pbMessageAndByteCount[sizeof(DWORD) + sizeof(SIZE_T)] = { }; + + hr = FileReadHandle(hPipe, pbMessageAndByteCount, sizeof(pbMessageAndByteCount)); + if (HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE) == hr) + { + memset(pbMessageAndByteCount, 0, sizeof(pbMessageAndByteCount)); + hr = S_FALSE; + } + ExitOnFailure(hr, "Failed to read message from pipe."); + + pMsg->dwMessage = *(DWORD*)(pbMessageAndByteCount); + pMsg->cbData = *(SIZE_T*)(pbMessageAndByteCount + sizeof(DWORD)); + if (pMsg->cbData) + { + pMsg->pvData = MemAlloc(pMsg->cbData, FALSE); + ExitOnNull(pMsg->pvData, hr, E_OUTOFMEMORY, "Failed to allocate data for message."); + + hr = FileReadHandle(hPipe, reinterpret_cast(pMsg->pvData), pMsg->cbData); + ExitOnFailure(hr, "Failed to read data for message."); + + pMsg->fAllocatedData = TRUE; + } + +LExit: + if (!pMsg->fAllocatedData && pMsg->pvData) + { + MemFree(pMsg->pvData); + } + + return hr; +} + +static HRESULT ChildPipeConnected( + __in HANDLE hPipe, + __in_z LPCWSTR wzSecret, + __inout DWORD* pdwProcessId + ) +{ + HRESULT hr = S_OK; + LPWSTR sczVerificationSecret = NULL; + DWORD cbVerificationSecret = 0; + DWORD dwVerificationProcessId = 0; + DWORD dwAck = ::GetCurrentProcessId(); // send our process id as the ACK. + + // Read the verification secret. + hr = FileReadHandle(hPipe, reinterpret_cast(&cbVerificationSecret), sizeof(cbVerificationSecret)); + ExitOnFailure(hr, "Failed to read size of verification secret from parent pipe."); + + if (255 < cbVerificationSecret / sizeof(WCHAR)) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Verification secret from parent is too big."); + } + + hr = StrAlloc(&sczVerificationSecret, cbVerificationSecret / sizeof(WCHAR) + 1); + ExitOnFailure(hr, "Failed to allocate buffer for verification secret."); + + FileReadHandle(hPipe, reinterpret_cast(sczVerificationSecret), cbVerificationSecret); + ExitOnFailure(hr, "Failed to read verification secret from parent pipe."); + + // Verify the secrets match. + if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, 0, sczVerificationSecret, -1, wzSecret, -1)) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Verification secret from parent does not match."); + } + + // Read the verification process id. + hr = FileReadHandle(hPipe, reinterpret_cast(&dwVerificationProcessId), sizeof(dwVerificationProcessId)); + ExitOnFailure(hr, "Failed to read verification process id from parent pipe."); + + // If a process id was not provided, we'll trust the process id from the parent. + if (*pdwProcessId == 0) + { + *pdwProcessId = dwVerificationProcessId; + } + else if (*pdwProcessId != dwVerificationProcessId) // verify the ids match. + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Verification process id from parent does not match."); + } + + // All is well, tell the parent process. + hr = FileWriteHandle(hPipe, reinterpret_cast(&dwAck), sizeof(dwAck)); + ExitOnFailure(hr, "Failed to inform parent process that child is running."); + +LExit: + ReleaseStr(sczVerificationSecret); + return hr; +} diff --git a/src/burn/engine/pipe.h b/src/burn/engine/pipe.h new file mode 100644 index 00000000..429cd824 --- /dev/null +++ b/src/burn/engine/pipe.h @@ -0,0 +1,113 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _BURN_PIPE_CONNECTION +{ + LPWSTR sczName; + LPWSTR sczSecret; + DWORD dwProcessId; + + HANDLE hProcess; + HANDLE hPipe; + HANDLE hCachePipe; +} BURN_PIPE_CONNECTION; + +typedef enum _BURN_PIPE_MESSAGE_TYPE : DWORD +{ + BURN_PIPE_MESSAGE_TYPE_LOG = 0xF0000001, + BURN_PIPE_MESSAGE_TYPE_COMPLETE = 0xF0000002, + BURN_PIPE_MESSAGE_TYPE_TERMINATE = 0xF0000003, +} BURN_PIPE_MESSAGE_TYPE; + +typedef struct _BURN_PIPE_MESSAGE +{ + DWORD dwMessage; + SIZE_T cbData; + + BOOL fAllocatedData; + LPVOID pvData; +} BURN_PIPE_MESSAGE; + +typedef struct _BURN_PIPE_RESULT +{ + DWORD dwResult; + BOOL fRestart; +} BURN_PIPE_RESULT; + + +typedef HRESULT (*PFN_PIPE_MESSAGE_CALLBACK)( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); + + +// Common functions. +void PipeConnectionInitialize( + __in BURN_PIPE_CONNECTION* pConnection + ); +void PipeConnectionUninitialize( + __in BURN_PIPE_CONNECTION* pConnection + ); +HRESULT PipeSendMessage( + __in HANDLE hPipe, + __in DWORD dwMessage, + __in_bcount_opt(cbData) LPVOID pvData, + __in SIZE_T cbData, + __in_opt PFN_PIPE_MESSAGE_CALLBACK pfnCallback, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); +HRESULT PipePumpMessages( + __in HANDLE hPipe, + __in_opt PFN_PIPE_MESSAGE_CALLBACK pfnCallback, + __in_opt LPVOID pvContext, + __in BURN_PIPE_RESULT* pResult + ); + +// Parent functions. +HRESULT PipeCreateNameAndSecret( + __out_z LPWSTR *psczConnectionName, + __out_z LPWSTR *psczSecret + ); +HRESULT PipeCreatePipes( + __in BURN_PIPE_CONNECTION* pConnection, + __in BOOL fCreateCachePipe, + __out HANDLE* phEvent + ); +HRESULT PipeLaunchParentProcess( + __in LPCWSTR wzCommandLine, + __in int nCmdShow, + __in_z LPWSTR sczConnectionName, + __in_z LPWSTR sczSecret, + __in BOOL fDisableUnelevate + ); +HRESULT PipeLaunchChildProcess( + __in_z LPCWSTR wzExecutablePath, + __in BURN_PIPE_CONNECTION* pConnection, + __in BOOL fElevate, + __in_opt HWND hwndParent + ); +HRESULT PipeWaitForChildConnect( + __in BURN_PIPE_CONNECTION* pConnection + ); +HRESULT PipeTerminateChildProcess( + __in BURN_PIPE_CONNECTION* pConnection, + __in DWORD dwParentExitCode, + __in BOOL fRestart + ); + +// Child functions. +HRESULT PipeChildConnect( + __in BURN_PIPE_CONNECTION* pConnection, + __in BOOL fConnectCachePipe + ); + +#ifdef __cplusplus +} +#endif diff --git a/src/burn/engine/plan.cpp b/src/burn/engine/plan.cpp new file mode 100644 index 00000000..9a4aa5f1 --- /dev/null +++ b/src/burn/engine/plan.cpp @@ -0,0 +1,2699 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +#define PlanDumpLevel REPORT_DEBUG + +// internal struct definitions + + +// internal function definitions + +static void UninitializeRegistrationAction( + __in BURN_DEPENDENT_REGISTRATION_ACTION* pAction + ); +static void UninitializeCacheAction( + __in BURN_CACHE_ACTION* pCacheAction + ); +static void ResetPlannedContainerState( + __in BURN_CONTAINER* pContainer + ); +static void ResetPlannedPayloadsState( + __in BURN_PAYLOADS* pPayloads + ); +static void ResetPlannedPayloadGroupState( + __in BURN_PAYLOAD_GROUP* pPayloadGroup + ); +static void ResetPlannedPackageState( + __in BURN_PACKAGE* pPackage + ); +static void ResetPlannedRollbackBoundaryState( + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ); +static HRESULT PlanPackagesHelper( + __in BURN_PACKAGE* rgPackages, + __in DWORD cPackages, + __in BOOL fPlanCleanPackages, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ); +static HRESULT InitializePackage( + __in BURN_PLAN* pPlan, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_VARIABLES* pVariables, + __in BURN_PACKAGE* pPackage, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ); +static HRESULT ProcessPackage( + __in BOOL fBundlePerMachine, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __inout HANDLE* phSyncpointEvent, + __inout BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary + ); +static HRESULT ProcessPackageRollbackBoundary( + __in BURN_PLAN* pPlan, + __in_opt BURN_ROLLBACK_BOUNDARY* pEffectiveRollbackBoundary, + __inout BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary + ); +static HRESULT GetActionDefaultRequestState( + __in BOOTSTRAPPER_ACTION action, + __in BOOL fPermanent, + __in BOOTSTRAPPER_PACKAGE_STATE currentState, + __out BOOTSTRAPPER_REQUEST_STATE* pRequestState + ); +static HRESULT AddRegistrationAction( + __in BURN_PLAN* pPlan, + __in BURN_DEPENDENT_REGISTRATION_ACTION_TYPE type, + __in_z LPCWSTR wzDependentProviderKey, + __in_z LPCWSTR wzOwnerBundleId + ); +static HRESULT AddCachePackage( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __out HANDLE* phSyncpointEvent + ); +static HRESULT AddCachePackageHelper( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __out HANDLE* phSyncpointEvent + ); +static HRESULT AddCacheSlipstreamMsps( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ); +static BOOL AlreadyPlannedCachePackage( + __in BURN_PLAN* pPlan, + __in_z LPCWSTR wzPackageId, + __out HANDLE* phSyncpointEvent + ); +static DWORD GetNextCheckpointId( + __in BURN_PLAN* pPlan + ); +static HRESULT AppendCacheAction( + __in BURN_PLAN* pPlan, + __out BURN_CACHE_ACTION** ppCacheAction + ); +static HRESULT AppendRollbackCacheAction( + __in BURN_PLAN* pPlan, + __out BURN_CACHE_ACTION** ppCacheAction + ); +static HRESULT ProcessPayloadGroup( + __in BURN_PLAN* pPlan, + __in BURN_PAYLOAD_GROUP* pPayloadGroup + ); +static void RemoveUnnecessaryActions( + __in BOOL fExecute, + __in BURN_EXECUTE_ACTION* rgActions, + __in DWORD cActions + ); +static void FinalizePatchActions( + __in BOOL fExecute, + __in BURN_EXECUTE_ACTION* rgActions, + __in DWORD cActions + ); +static void CalculateExpectedRegistrationStates( + __in BURN_PACKAGE* rgPackages, + __in DWORD cPackages + ); +static HRESULT PlanDependencyActions( + __in BOOL fBundlePerMachine, + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ); +static HRESULT CalculateExecuteActions( + __in BURN_PACKAGE* pPackage, + __in_opt BURN_ROLLBACK_BOUNDARY* pActiveRollbackBoundary + ); +static BOOL NeedsCache( + __in BURN_PACKAGE* pPackage, + __in BOOL fExecute + ); +static BOOL ForceCache( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ); + +// function definitions + +extern "C" void PlanReset( + __in BURN_PLAN* pPlan, + __in BURN_CONTAINERS* pContainers, + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOAD_GROUP* pLayoutPayloads + ) +{ + ReleaseNullStr(pPlan->sczLayoutDirectory); + PackageUninitialize(&pPlan->forwardCompatibleBundle); + + if (pPlan->rgRegistrationActions) + { + for (DWORD i = 0; i < pPlan->cRegistrationActions; ++i) + { + UninitializeRegistrationAction(&pPlan->rgRegistrationActions[i]); + } + MemFree(pPlan->rgRegistrationActions); + } + + if (pPlan->rgRollbackRegistrationActions) + { + for (DWORD i = 0; i < pPlan->cRollbackRegistrationActions; ++i) + { + UninitializeRegistrationAction(&pPlan->rgRollbackRegistrationActions[i]); + } + MemFree(pPlan->rgRollbackRegistrationActions); + } + + if (pPlan->rgCacheActions) + { + for (DWORD i = 0; i < pPlan->cCacheActions; ++i) + { + UninitializeCacheAction(&pPlan->rgCacheActions[i]); + } + MemFree(pPlan->rgCacheActions); + } + + if (pPlan->rgExecuteActions) + { + for (DWORD i = 0; i < pPlan->cExecuteActions; ++i) + { + PlanUninitializeExecuteAction(&pPlan->rgExecuteActions[i]); + } + MemFree(pPlan->rgExecuteActions); + } + + if (pPlan->rgRollbackActions) + { + for (DWORD i = 0; i < pPlan->cRollbackActions; ++i) + { + PlanUninitializeExecuteAction(&pPlan->rgRollbackActions[i]); + } + MemFree(pPlan->rgRollbackActions); + } + + if (pPlan->rgCleanActions) + { + // Nothing needs to be freed inside clean actions today. + MemFree(pPlan->rgCleanActions); + } + + if (pPlan->rgPlannedProviders) + { + ReleaseDependencyArray(pPlan->rgPlannedProviders, pPlan->cPlannedProviders); + } + + if (pPlan->rgContainerProgress) + { + MemFree(pPlan->rgContainerProgress); + } + + if (pPlan->shContainerProgress) + { + ReleaseDict(pPlan->shContainerProgress); + } + + if (pPlan->rgPayloadProgress) + { + MemFree(pPlan->rgPayloadProgress); + } + + if (pPlan->shPayloadProgress) + { + ReleaseDict(pPlan->shPayloadProgress); + } + + if (pPlan->pPayloads) + { + ResetPlannedPayloadsState(pPlan->pPayloads); + } + + memset(pPlan, 0, sizeof(BURN_PLAN)); + + if (pContainers->rgContainers) + { + for (DWORD i = 0; i < pContainers->cContainers; ++i) + { + ResetPlannedContainerState(&pContainers->rgContainers[i]); + } + } + + // Reset the planned actions for each package. + if (pPackages->rgPackages) + { + for (DWORD i = 0; i < pPackages->cPackages; ++i) + { + ResetPlannedPackageState(&pPackages->rgPackages[i]); + } + } + + ResetPlannedPayloadGroupState(pLayoutPayloads); + + // Reset the planned state for each rollback boundary. + if (pPackages->rgRollbackBoundaries) + { + for (DWORD i = 0; i < pPackages->cRollbackBoundaries; ++i) + { + ResetPlannedRollbackBoundaryState(&pPackages->rgRollbackBoundaries[i]); + } + } +} + +extern "C" void PlanUninitializeExecuteAction( + __in BURN_EXECUTE_ACTION* pExecuteAction + ) +{ + switch (pExecuteAction->type) + { + case BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE: + ReleaseStr(pExecuteAction->exePackage.sczIgnoreDependencies); + ReleaseStr(pExecuteAction->exePackage.sczAncestors); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE: + ReleaseStr(pExecuteAction->msiPackage.sczLogPath); + ReleaseMem(pExecuteAction->msiPackage.rgFeatures); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSP_TARGET: + ReleaseStr(pExecuteAction->mspTarget.sczTargetProductCode); + ReleaseStr(pExecuteAction->mspTarget.sczLogPath); + ReleaseMem(pExecuteAction->mspTarget.rgOrderedPatches); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE: + ReleaseStr(pExecuteAction->msuPackage.sczLogPath); + break; + + case BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY: + ReleaseStr(pExecuteAction->packageDependency.sczBundleProviderKey); + break; + } +} + +extern "C" HRESULT PlanSetVariables( + __in BOOTSTRAPPER_ACTION action, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + + hr = VariableSetNumeric(pVariables, BURN_BUNDLE_ACTION, action, TRUE); + ExitOnFailure(hr, "Failed to set the bundle action built-in variable."); + +LExit: + return hr; +} + +extern "C" HRESULT PlanDefaultPackageRequestState( + __in BURN_PACKAGE_TYPE packageType, + __in BOOTSTRAPPER_PACKAGE_STATE currentState, + __in BOOL fPermanent, + __in BOOTSTRAPPER_ACTION action, + __in BOOTSTRAPPER_PACKAGE_CONDITION_RESULT installCondition, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __out BOOTSTRAPPER_REQUEST_STATE* pRequestState + ) +{ + HRESULT hr = S_OK; + BOOTSTRAPPER_REQUEST_STATE defaultRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; + + // If doing layout, then always default to requesting the package be cached. + if (BOOTSTRAPPER_ACTION_LAYOUT == action) + { + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_CACHE; + } + else if (BOOTSTRAPPER_RELATION_PATCH == relationType && BURN_PACKAGE_TYPE_MSP == packageType) + { + // For patch related bundles, only install a patch if currently absent during install, modify, or repair. + if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT == currentState && BOOTSTRAPPER_ACTION_INSTALL <= action) + { + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; + } + else + { + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; + } + } + else if (BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED == currentState && BOOTSTRAPPER_ACTION_UNINSTALL != action) + { + // Superseded means the package is on the machine but not active, so only uninstall operations are allowed. + // All other operations do nothing. + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; + } + else if (BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE == currentState && !(BOOTSTRAPPER_ACTION_UNINSTALL == action && BURN_PACKAGE_TYPE_MSP == packageType)) + { + // Obsolete means the package is not on the machine and should not be installed, *except* patches can be obsolete + // and present so allow them to be removed during uninstall. Everyone else, gets nothing. + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; + } + else // pick the best option for the action state and install condition. + { + hr = GetActionDefaultRequestState(action, fPermanent, currentState, &defaultRequestState); + ExitOnFailure(hr, "Failed to get default request state for action."); + + // If we're doing an install, use the install condition + // to determine whether to use the default request state or make the package absent. + if (BOOTSTRAPPER_ACTION_UNINSTALL != action && BOOTSTRAPPER_PACKAGE_CONDITION_FALSE == installCondition) + { + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_ABSENT; + } + else // just set the package to the default request state. + { + *pRequestState = defaultRequestState; + } + } + +LExit: + return hr; +} + +extern "C" HRESULT PlanLayoutBundle( + __in BURN_PLAN* pPlan, + __in_z LPCWSTR wzExecutableName, + __in DWORD64 qwBundleSize, + __in BURN_VARIABLES* pVariables, + __in BURN_PAYLOAD_GROUP* pLayoutPayloads + ) +{ + HRESULT hr = S_OK; + BURN_CACHE_ACTION* pCacheAction = NULL; + LPWSTR sczExecutablePath = NULL; + + // Get the layout directory. + hr = VariableGetString(pVariables, BURN_BUNDLE_LAYOUT_DIRECTORY, &pPlan->sczLayoutDirectory); + if (E_NOTFOUND == hr) // if not set, use the current directory as the layout directory. + { + hr = VariableGetString(pVariables, BURN_BUNDLE_SOURCE_PROCESS_FOLDER, &pPlan->sczLayoutDirectory); + if (E_NOTFOUND == hr) // if not set, use the current directory as the layout directory. + { + hr = PathForCurrentProcess(&sczExecutablePath, NULL); + ExitOnFailure(hr, "Failed to get path for current executing process as layout directory."); + + hr = PathGetDirectory(sczExecutablePath, &pPlan->sczLayoutDirectory); + ExitOnFailure(hr, "Failed to get executing process as layout directory."); + } + } + ExitOnFailure(hr, "Failed to get bundle layout directory property."); + + hr = PathBackslashTerminate(&pPlan->sczLayoutDirectory); + ExitOnFailure(hr, "Failed to ensure layout directory is backslash terminated."); + + hr = ProcessPayloadGroup(pPlan, pLayoutPayloads); + ExitOnFailure(hr, "Failed to process payload group for bundle."); + + // Plan the layout of the bundle engine itself. + hr = AppendCacheAction(pPlan, &pCacheAction); + ExitOnFailure(hr, "Failed to append bundle start action."); + + pCacheAction->type = BURN_CACHE_ACTION_TYPE_LAYOUT_BUNDLE; + + hr = StrAllocString(&pCacheAction->bundleLayout.sczExecutableName, wzExecutableName, 0); + ExitOnFailure(hr, "Failed to to copy executable name for bundle."); + + hr = CacheCalculateBundleLayoutWorkingPath(pPlan->wzBundleId, &pCacheAction->bundleLayout.sczUnverifiedPath); + ExitOnFailure(hr, "Failed to calculate bundle layout working path."); + + pCacheAction->bundleLayout.qwBundleSize = qwBundleSize; + pCacheAction->bundleLayout.pPayloadGroup = pLayoutPayloads; + + // Acquire + Verify + Finalize + pPlan->qwCacheSizeTotal += 3 * qwBundleSize; + + ++pPlan->cOverallProgressTicksTotal; + +LExit: + ReleaseStr(sczExecutablePath); + + return hr; +} + +extern "C" HRESULT PlanForwardCompatibleBundles( + __in BURN_USER_EXPERIENCE* pUX, + __in BOOTSTRAPPER_COMMAND* pCommand, + __in BURN_PLAN* pPlan, + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_ACTION action + ) +{ + HRESULT hr = S_OK; + BOOL fRecommendIgnore = TRUE; + BOOL fIgnoreBundle = FALSE; + + if (!pRegistration->fForwardCompatibleBundleExists) + { + ExitFunction(); + } + + // Only change the recommendation if an active parent was provided. + if (pRegistration->sczActiveParent && *pRegistration->sczActiveParent) + { + // On install, recommend running the forward compatible bundle because there is an active parent. This + // will essentially register the parent with the forward compatible bundle. + if (BOOTSTRAPPER_ACTION_INSTALL == action) + { + fRecommendIgnore = FALSE; + } + else if (BOOTSTRAPPER_ACTION_UNINSTALL == action || + BOOTSTRAPPER_ACTION_MODIFY == action || + BOOTSTRAPPER_ACTION_REPAIR == action) + { + // When modifying the bundle, only recommend running the forward compatible bundle if the parent + // is already registered as a dependent of the provider key. + if (pRegistration->fParentRegisteredAsDependent) + { + fRecommendIgnore = FALSE; + } + } + } + + for (DWORD iRelatedBundle = 0; iRelatedBundle < pRegistration->relatedBundles.cRelatedBundles; ++iRelatedBundle) + { + BURN_RELATED_BUNDLE* pRelatedBundle = pRegistration->relatedBundles.rgRelatedBundles + iRelatedBundle; + if (!pRelatedBundle->fForwardCompatible) + { + continue; + } + + fIgnoreBundle = fRecommendIgnore; + + hr = UserExperienceOnPlanForwardCompatibleBundle(pUX, pRelatedBundle->package.sczId, pRelatedBundle->relationType, pRelatedBundle->sczTag, pRelatedBundle->package.fPerMachine, pRelatedBundle->pVersion, &fIgnoreBundle); + ExitOnRootFailure(hr, "BA aborted plan forward compatible bundle."); + + if (!fIgnoreBundle) + { + hr = PseudoBundleInitializePassthrough(&pPlan->forwardCompatibleBundle, pCommand, NULL, pRegistration->sczActiveParent, pRegistration->sczAncestors, &pRelatedBundle->package); + ExitOnFailure(hr, "Failed to initialize pass through bundle."); + + pPlan->fEnabledForwardCompatibleBundle = TRUE; + break; + } + } + +LExit: + return hr; +} + +extern "C" HRESULT PlanPackages( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PACKAGES* pPackages, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ) +{ + HRESULT hr = S_OK; + + hr = PlanPackagesHelper(pPackages->rgPackages, pPackages->cPackages, TRUE, pUX, pPlan, pLog, pVariables, display, relationType); + + return hr; +} + +extern "C" HRESULT PlanRegistration( + __in BURN_PLAN* pPlan, + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_RESUME_TYPE /*resumeType*/, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __inout BOOL* pfContinuePlanning + ) +{ + HRESULT hr = S_OK; + STRINGDICT_HANDLE sdBundleDependents = NULL; + STRINGDICT_HANDLE sdIgnoreDependents = NULL; + + pPlan->fCanAffectMachineState = TRUE; // register the bundle since we're modifying machine state. + pPlan->fDisallowRemoval = FALSE; // by default the bundle can be planned to be removed + pPlan->fIgnoreAllDependents = pRegistration->fIgnoreAllDependents; + + // Ensure the bundle is cached if not running from the cache. + if (!CacheBundleRunningFromCache()) + { + pPlan->dwRegistrationOperations |= BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE; + } + + // Always write registration since things may have changed or it just needs to be "fixed up". + pPlan->dwRegistrationOperations |= BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION; + + // Always update our estimated size registration when installing/modify/repair since things + // may have been added or removed or it just needs to be "fixed up". + pPlan->dwRegistrationOperations |= BURN_REGISTRATION_ACTION_OPERATIONS_UPDATE_SIZE; + + if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) + { + // If our provider key was detected and it points to our current bundle then we can + // unregister the bundle dependency. + if (pRegistration->sczDetectedProviderKeyBundleId && + CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pRegistration->sczId, -1, pRegistration->sczDetectedProviderKeyBundleId, -1)) + { + pPlan->dependencyRegistrationAction = BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER; + } + else // log that another bundle already owned our registration, hopefully this only happens when a newer version + { // of a bundle installed and is in the process of upgrading us. + LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_PROVIDER_KEY_REMOVAL, pRegistration->sczProviderKey, pRegistration->sczDetectedProviderKeyBundleId); + } + + // Create the dictionary of dependents that should be ignored. + hr = DictCreateStringList(&sdIgnoreDependents, 5, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create the string dictionary."); + + // If the self-dependent dependent exists, plan its removal. If we did not do this, we + // would prevent self-removal. + if (pRegistration->fSelfRegisteredAsDependent) + { + hr = AddRegistrationAction(pPlan, BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_UNREGISTER, pRegistration->wzSelfDependent, pRegistration->sczId); + ExitOnFailure(hr, "Failed to allocate registration action."); + + hr = DependencyAddIgnoreDependencies(sdIgnoreDependents, pRegistration->wzSelfDependent); + ExitOnFailure(hr, "Failed to add self-dependent to ignore dependents."); + } + + if (!pPlan->fIgnoreAllDependents) + { + // If we are not doing an upgrade, we check to see if there are still dependents on us and if so we skip planning. + // However, when being upgraded, we always execute our uninstall because a newer version of us is probably + // already on the machine and we need to clean up the stuff specific to this bundle. + if (BOOTSTRAPPER_RELATION_UPGRADE != relationType) + { + // If there were other dependencies to ignore, add them. + for (DWORD iDependency = 0; iDependency < pRegistration->cIgnoredDependencies; ++iDependency) + { + DEPENDENCY* pDependency = pRegistration->rgIgnoredDependencies + iDependency; + + hr = DictKeyExists(sdIgnoreDependents, pDependency->sczKey); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to check the dictionary of ignored dependents."); + } + else + { + hr = DictAddKey(sdIgnoreDependents, pDependency->sczKey); + ExitOnFailure(hr, "Failed to add dependent key to ignored dependents."); + } + } + + // For addon or patch bundles, dependent related bundles should be ignored. This allows + // that addon or patch to be removed even though bundles it targets still are registered. + for (DWORD i = 0; i < pRegistration->relatedBundles.cRelatedBundles; ++i) + { + const BURN_RELATED_BUNDLE* pRelatedBundle = pRegistration->relatedBundles.rgRelatedBundles + i; + + if (BOOTSTRAPPER_RELATION_DEPENDENT == pRelatedBundle->relationType) + { + for (DWORD j = 0; j < pRelatedBundle->package.cDependencyProviders; ++j) + { + const BURN_DEPENDENCY_PROVIDER* pProvider = pRelatedBundle->package.rgDependencyProviders + j; + + hr = DependencyAddIgnoreDependencies(sdIgnoreDependents, pProvider->sczKey); + ExitOnFailure(hr, "Failed to add dependent bundle provider key to ignore dependents."); + } + } + } + + // If there are any (non-ignored and not-planned-to-be-removed) dependents left, skip planning. + for (DWORD iDependent = 0; iDependent < pRegistration->cDependents; ++iDependent) + { + DEPENDENCY* pDependent = pRegistration->rgDependents + iDependent; + + hr = DictKeyExists(sdIgnoreDependents, pDependent->sczKey); + if (E_NOTFOUND == hr) + { + hr = S_OK; + + // TODO: callback to the BA and let it have the option to ignore this dependent? + if (!pPlan->fDisallowRemoval) + { + pPlan->fDisallowRemoval = TRUE; // ensure the registration stays + *pfContinuePlanning = FALSE; // skip the rest of planning. + + LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_DUE_TO_DEPENDENTS); + } + + LogId(REPORT_VERBOSE, MSG_DEPENDENCY_BUNDLE_DEPENDENT, pDependent->sczKey, LoggingStringOrUnknownIfNull(pDependent->sczName)); + } + ExitOnFailure(hr, "Failed to check for remaining dependents during planning."); + } + } + } + } + else + { + BOOL fAddonOrPatchBundle = (pRegistration->cAddonCodes || pRegistration->cPatchCodes); + + // Always plan to write our provider key registration when installing/modify/repair to "fix it" + // if broken. + pPlan->dependencyRegistrationAction = BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER; + + // Create the dictionary of bundle dependents. + hr = DictCreateStringList(&sdBundleDependents, 5, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create the string dictionary."); + + for (DWORD iDependent = 0; iDependent < pRegistration->cDependents; ++iDependent) + { + DEPENDENCY* pDependent = pRegistration->rgDependents + iDependent; + + hr = DictKeyExists(sdBundleDependents, pDependent->sczKey); + if (E_NOTFOUND == hr) + { + hr = DictAddKey(sdBundleDependents, pDependent->sczKey); + ExitOnFailure(hr, "Failed to add dependent key to bundle dependents."); + } + ExitOnFailure(hr, "Failed to check the dictionary of bundle dependents."); + } + + // Register each dependent related bundle. The ensures that addons and patches are reference + // counted and stick around until the last targeted bundle is removed. + for (DWORD i = 0; i < pRegistration->relatedBundles.cRelatedBundles; ++i) + { + const BURN_RELATED_BUNDLE* pRelatedBundle = pRegistration->relatedBundles.rgRelatedBundles + i; + + if (BOOTSTRAPPER_RELATION_DEPENDENT == pRelatedBundle->relationType) + { + for (DWORD j = 0; j < pRelatedBundle->package.cDependencyProviders; ++j) + { + const BURN_DEPENDENCY_PROVIDER* pProvider = pRelatedBundle->package.rgDependencyProviders + j; + + hr = DictKeyExists(sdBundleDependents, pProvider->sczKey); + if (E_NOTFOUND == hr) + { + hr = DictAddKey(sdBundleDependents, pProvider->sczKey); + ExitOnFailure(hr, "Failed to add new dependent key to bundle dependents."); + + hr = AddRegistrationAction(pPlan, BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_REGISTER, pProvider->sczKey, pRelatedBundle->package.sczId); + ExitOnFailure(hr, "Failed to add registration action for dependent related bundle."); + } + ExitOnFailure(hr, "Failed to check the dictionary of bundle dependents."); + } + } + } + + // Only do the following if we decided there was a dependent self to register. If so and and an explicit parent was + // provided, register dependent self. Otherwise, if this bundle is not an addon or patch bundle then self-regisiter + // as our own dependent. + if (pRegistration->wzSelfDependent && !pRegistration->fSelfRegisteredAsDependent && (pRegistration->sczActiveParent || !fAddonOrPatchBundle)) + { + hr = AddRegistrationAction(pPlan, BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_REGISTER, pRegistration->wzSelfDependent, pRegistration->sczId); + ExitOnFailure(hr, "Failed to add registration action for self dependent."); + } + } + +LExit: + ReleaseDict(sdBundleDependents); + ReleaseDict(sdIgnoreDependents); + + return hr; +} + +extern "C" HRESULT PlanPassThroughBundle( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ) +{ + HRESULT hr = S_OK; + + // Plan passthrough package. + // Passthrough packages are never cleaned up by the calling bundle (they delete themselves when appropriate) + // so we don't need to plan clean up. + hr = PlanPackagesHelper(pPackage, 1, FALSE, pUX, pPlan, pLog, pVariables, display, relationType); + ExitOnFailure(hr, "Failed to process passthrough package."); + +LExit: + return hr; +} + +extern "C" HRESULT PlanUpdateBundle( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ) +{ + HRESULT hr = S_OK; + + // Plan update package. + hr = PlanPackagesHelper(pPackage, 1, TRUE, pUX, pPlan, pLog, pVariables, display, relationType); + ExitOnFailure(hr, "Failed to process update package."); + +LExit: + return hr; +} + +static HRESULT PlanPackagesHelper( + __in BURN_PACKAGE* rgPackages, + __in DWORD cPackages, + __in BOOL fPlanCleanPackages, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ) +{ + HRESULT hr = S_OK; + BOOL fBundlePerMachine = pPlan->fPerMachine; // bundle is per-machine if plan starts per-machine. + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = NULL; + HANDLE hSyncpointEvent = NULL; + + // Initialize the packages. + for (DWORD i = 0; i < cPackages; ++i) + { + DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; + BURN_PACKAGE* pPackage = rgPackages + iPackage; + + hr = InitializePackage(pPlan, pUX, pVariables, pPackage, relationType); + ExitOnFailure(hr, "Failed to initialize package."); + } + + // Initialize the patch targets after all packages, since they could rely on the requested state of packages that are after the patch's package in the chain. + for (DWORD i = 0; i < cPackages; ++i) + { + DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; + BURN_PACKAGE* pPackage = rgPackages + iPackage; + + if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + hr = MspEnginePlanInitializePackage(pPackage, pUX); + ExitOnFailure(hr, "Failed to initialize plan package: %ls", pPackage->sczId); + } + } + + // Plan the packages. + for (DWORD i = 0; i < cPackages; ++i) + { + DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; + BURN_PACKAGE* pPackage = rgPackages + iPackage; + + hr = ProcessPackage(fBundlePerMachine, pUX, pPlan, pPackage, pLog, pVariables, display, &hSyncpointEvent, &pRollbackBoundary); + ExitOnFailure(hr, "Failed to process package."); + } + + // If we still have an open rollback boundary, complete it. + if (pRollbackBoundary) + { + hr = PlanRollbackBoundaryComplete(pPlan); + ExitOnFailure(hr, "Failed to plan final rollback boundary complete."); + + pRollbackBoundary = NULL; + } + + if (fPlanCleanPackages) + { + // Plan clean up of packages. + for (DWORD i = 0; i < cPackages; ++i) + { + DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; + BURN_PACKAGE* pPackage = rgPackages + iPackage; + + hr = PlanCleanPackage(pPlan, pPackage); + ExitOnFailure(hr, "Failed to plan clean package."); + } + } + + // Remove unnecessary actions. + hr = PlanFinalizeActions(pPlan); + ExitOnFailure(hr, "Failed to remove unnecessary actions from plan."); + + CalculateExpectedRegistrationStates(rgPackages, cPackages); + + // Let the BA know the actions that were planned. + for (DWORD i = 0; i < cPackages; ++i) + { + DWORD iPackage = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? cPackages - 1 - i : i; + BURN_PACKAGE* pPackage = rgPackages + iPackage; + + UserExperienceOnPlannedPackage(pUX, pPackage->sczId, pPackage->execute, pPackage->rollback, pPackage->fPlannedCache, pPackage->fPlannedUncache); + } + +LExit: + return hr; +} + +static HRESULT InitializePackage( + __in BURN_PLAN* pPlan, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_VARIABLES* pVariables, + __in BURN_PACKAGE* pPackage, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ) +{ + HRESULT hr = S_OK; + BOOTSTRAPPER_PACKAGE_CONDITION_RESULT installCondition = BOOTSTRAPPER_PACKAGE_CONDITION_DEFAULT; + BOOL fInstallCondition = FALSE; + BOOL fBeginCalled = FALSE; + + if (pPackage->fCanAffectRegistration) + { + pPackage->expectedCacheRegistrationState = pPackage->cacheRegistrationState; + pPackage->expectedInstallRegistrationState = pPackage->installRegistrationState; + } + + if (pPackage->sczInstallCondition && *pPackage->sczInstallCondition) + { + hr = ConditionEvaluate(pVariables, pPackage->sczInstallCondition, &fInstallCondition); + ExitOnFailure(hr, "Failed to evaluate install condition."); + + installCondition = fInstallCondition ? BOOTSTRAPPER_PACKAGE_CONDITION_TRUE : BOOTSTRAPPER_PACKAGE_CONDITION_FALSE; + } + + // Remember the default requested state so the engine doesn't get blamed for planning the wrong thing if the BA changes it. + hr = PlanDefaultPackageRequestState(pPackage->type, pPackage->currentState, !pPackage->fUninstallable, pPlan->action, installCondition, relationType, &pPackage->defaultRequested); + ExitOnFailure(hr, "Failed to set default package state."); + + pPackage->requested = pPackage->defaultRequested; + fBeginCalled = TRUE; + + hr = UserExperienceOnPlanPackageBegin(pUX, pPackage->sczId, pPackage->currentState, pPackage->fCached, installCondition, &pPackage->requested, &pPackage->cacheType); + ExitOnRootFailure(hr, "BA aborted plan package begin."); + + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) + { + hr = MsiEnginePlanInitializePackage(pPackage, pVariables, pUX); + ExitOnFailure(hr, "Failed to initialize plan package: %ls", pPackage->sczId); + } + +LExit: + if (fBeginCalled) + { + UserExperienceOnPlanPackageComplete(pUX, pPackage->sczId, hr, pPackage->requested); + } + + return hr; +} + +static HRESULT ProcessPackage( + __in BOOL fBundlePerMachine, + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __inout HANDLE* phSyncpointEvent, + __inout BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary + ) +{ + HRESULT hr = S_OK; + BURN_ROLLBACK_BOUNDARY* pEffectiveRollbackBoundary = NULL; + + pEffectiveRollbackBoundary = (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) ? pPackage->pRollbackBoundaryBackward : pPackage->pRollbackBoundaryForward; + hr = ProcessPackageRollbackBoundary(pPlan, pEffectiveRollbackBoundary, ppRollbackBoundary); + ExitOnFailure(hr, "Failed to process package rollback boundary."); + + if (BOOTSTRAPPER_ACTION_LAYOUT == pPlan->action) + { + if (BOOTSTRAPPER_REQUEST_STATE_NONE != pPackage->requested) + { + hr = PlanLayoutPackage(pPlan, pPackage); + ExitOnFailure(hr, "Failed to plan layout package."); + } + } + else + { + if (BOOTSTRAPPER_REQUEST_STATE_NONE != pPackage->requested) + { + // If the package is in a requested state, plan it. + hr = PlanExecutePackage(fBundlePerMachine, display, pUX, pPlan, pPackage, pLog, pVariables, phSyncpointEvent); + ExitOnFailure(hr, "Failed to plan execute package."); + } + else + { + if (ForceCache(pPlan, pPackage)) + { + hr = AddCachePackage(pPlan, pPackage, phSyncpointEvent); + ExitOnFailure(hr, "Failed to plan cache package."); + + if (pPackage->fPerMachine) + { + pPlan->fPerMachine = TRUE; + } + } + + // Make sure the package is properly ref-counted even if no plan is requested. + hr = PlanDependencyActions(fBundlePerMachine, pPlan, pPackage); + ExitOnFailure(hr, "Failed to plan dependency actions for package: %ls", pPackage->sczId); + } + } + + // Add the checkpoint after each package and dependency registration action. + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute || BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback || BURN_DEPENDENCY_ACTION_NONE != pPackage->dependencyExecute) + { + hr = PlanExecuteCheckpoint(pPlan); + ExitOnFailure(hr, "Failed to append execute checkpoint."); + } + +LExit: + return hr; +} + +static HRESULT ProcessPackageRollbackBoundary( + __in BURN_PLAN* pPlan, + __in_opt BURN_ROLLBACK_BOUNDARY* pEffectiveRollbackBoundary, + __inout BURN_ROLLBACK_BOUNDARY** ppRollbackBoundary + ) +{ + HRESULT hr = S_OK; + + // If the package marks the start of a rollback boundary, start a new one. + if (pEffectiveRollbackBoundary) + { + // Complete previous rollback boundary. + if (*ppRollbackBoundary) + { + hr = PlanRollbackBoundaryComplete(pPlan); + ExitOnFailure(hr, "Failed to plan rollback boundary complete."); + } + + // Start new rollback boundary. + hr = PlanRollbackBoundaryBegin(pPlan, pEffectiveRollbackBoundary); + ExitOnFailure(hr, "Failed to plan rollback boundary begin."); + + *ppRollbackBoundary = pEffectiveRollbackBoundary; + } + +LExit: + return hr; +} + +extern "C" HRESULT PlanLayoutContainer( + __in BURN_PLAN* pPlan, + __in BURN_CONTAINER* pContainer + ) +{ + HRESULT hr = S_OK; + BURN_CACHE_ACTION* pCacheAction = NULL; + + Assert(!pContainer->fPlanned); + pContainer->fPlanned = TRUE; + + if (pPlan->sczLayoutDirectory) + { + if (!pContainer->fAttached) + { + hr = AppendCacheAction(pPlan, &pCacheAction); + ExitOnFailure(hr, "Failed to append package start action."); + + pCacheAction->type = BURN_CACHE_ACTION_TYPE_CONTAINER; + pCacheAction->container.pContainer = pContainer; + + // Acquire + Verify + Finalize + pPlan->qwCacheSizeTotal += 3 * pContainer->qwFileSize; + } + } + else + { + if (!pContainer->fActuallyAttached) + { + // Acquire + pPlan->qwCacheSizeTotal += pContainer->qwFileSize; + } + } + + if (!pContainer->sczUnverifiedPath) + { + if (pContainer->fActuallyAttached) + { + hr = PathForCurrentProcess(&pContainer->sczUnverifiedPath, NULL); + ExitOnFailure(hr, "Failed to get path for executing module as attached container working path."); + } + else + { + hr = CacheCalculateContainerWorkingPath(pPlan->wzBundleId, pContainer, &pContainer->sczUnverifiedPath); + ExitOnFailure(hr, "Failed to calculate unverified path for container."); + } + } + +LExit: + return hr; +} + +extern "C" HRESULT PlanLayoutPackage( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + BURN_CACHE_ACTION* pCacheAction = NULL; + + hr = ProcessPayloadGroup(pPlan, &pPackage->payloads); + ExitOnFailure(hr, "Failed to process payload group for package: %ls.", pPackage->sczId); + + hr = AppendCacheAction(pPlan, &pCacheAction); + ExitOnFailure(hr, "Failed to append package start action."); + + pCacheAction->type = BURN_CACHE_ACTION_TYPE_PACKAGE; + pCacheAction->package.pPackage = pPackage; + + ++pPlan->cOverallProgressTicksTotal; + +LExit: + return hr; +} + +extern "C" HRESULT PlanExecutePackage( + __in BOOL fPerMachine, + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __inout HANDLE* phSyncpointEvent + ) +{ + HRESULT hr = S_OK; + BOOL fRequestedCache = BOOTSTRAPPER_REQUEST_STATE_CACHE == pPackage->requested || ForceCache(pPlan, pPackage); + + hr = CalculateExecuteActions(pPackage, pPlan->pActiveRollbackBoundary); + ExitOnFailure(hr, "Failed to calculate plan actions for package: %ls", pPackage->sczId); + + // Calculate package states based on reference count and plan certain dependency actions prior to planning the package execute action. + hr = DependencyPlanPackageBegin(fPerMachine, pPackage, pPlan); + ExitOnFailure(hr, "Failed to begin plan dependency actions for package: %ls", pPackage->sczId); + + if (fRequestedCache || NeedsCache(pPackage, TRUE)) + { + hr = AddCachePackage(pPlan, pPackage, phSyncpointEvent); + ExitOnFailure(hr, "Failed to plan cache package."); + } + else if (!pPackage->fCached && NeedsCache(pPackage, FALSE)) + { + // TODO: this decision should be made during apply instead of plan based on whether the package is actually cached. + // If the package is not in the cache, disable any rollback that would require the package from the cache. + LogId(REPORT_STANDARD, MSG_PLAN_DISABLING_ROLLBACK_NO_CACHE, pPackage->sczId, LoggingActionStateToString(pPackage->rollback)); + pPackage->rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + } + + // Add the cache and install size to estimated size if it will be on the machine at the end of the install + if (BOOTSTRAPPER_REQUEST_STATE_PRESENT == pPackage->requested || + fRequestedCache || + (BOOTSTRAPPER_PACKAGE_STATE_PRESENT == pPackage->currentState && BOOTSTRAPPER_REQUEST_STATE_ABSENT < pPackage->requested) + ) + { + // If the package will remain in the cache, add the package size to the estimated size + if (BOOTSTRAPPER_CACHE_TYPE_REMOVE < pPackage->cacheType) + { + pPlan->qwEstimatedSize += pPackage->qwSize; + } + + // If the package will end up installed on the machine, add the install size to the estimated size. + if (BOOTSTRAPPER_REQUEST_STATE_CACHE < pPackage->requested) + { + // MSP packages get cached automatically by windows installer with any embedded cabs, so include that in the size as well + if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + pPlan->qwEstimatedSize += pPackage->qwSize; + } + + pPlan->qwEstimatedSize += pPackage->qwInstallSize; + } + } + + // Add execute actions. + switch (pPackage->type) + { + case BURN_PACKAGE_TYPE_EXE: + hr = ExeEnginePlanAddPackage(NULL, pPackage, pPlan, pLog, pVariables, *phSyncpointEvent); + break; + + case BURN_PACKAGE_TYPE_MSI: + hr = MsiEnginePlanAddPackage(display, pUserExperience, pPackage, pPlan, pLog, pVariables, *phSyncpointEvent); + break; + + case BURN_PACKAGE_TYPE_MSP: + hr = MspEnginePlanAddPackage(display, pUserExperience, pPackage, pPlan, pLog, pVariables, *phSyncpointEvent); + break; + + case BURN_PACKAGE_TYPE_MSU: + hr = MsuEnginePlanAddPackage(pPackage, pPlan, pLog, pVariables, *phSyncpointEvent); + break; + + default: + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Invalid package type."); + } + ExitOnFailure(hr, "Failed to add plan actions for package: %ls", pPackage->sczId); + + // Plan certain dependency actions after planning the package execute action. + hr = DependencyPlanPackageComplete(pPackage, pPlan); + ExitOnFailure(hr, "Failed to complete plan dependency actions for package: %ls", pPackage->sczId); + + // If we are going to take any action on this package, add progress for it. + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute || BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback) + { + LoggingIncrementPackageSequence(); + + ++pPlan->cExecutePackagesTotal; + ++pPlan->cOverallProgressTicksTotal; + + // If package is per-machine and is being executed, flag the plan to be per-machine as well. + if (pPackage->fPerMachine) + { + pPlan->fPerMachine = TRUE; + } + } + +LExit: + return hr; +} + +extern "C" HRESULT PlanDefaultRelatedBundleRequestState( + __in BOOTSTRAPPER_RELATION_TYPE commandRelationType, + __in BOOTSTRAPPER_RELATION_TYPE relatedBundleRelationType, + __in BOOTSTRAPPER_ACTION action, + __in VERUTIL_VERSION* pRegistrationVersion, + __in VERUTIL_VERSION* pRelatedBundleVersion, + __inout BOOTSTRAPPER_REQUEST_STATE* pRequestState + ) +{ + HRESULT hr = S_OK; + int nCompareResult = 0; + + // Never touch related bundles during Cache. + if (BOOTSTRAPPER_ACTION_CACHE == action) + { + ExitFunction1(*pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE); + } + + switch (relatedBundleRelationType) + { + case BOOTSTRAPPER_RELATION_UPGRADE: + if (BOOTSTRAPPER_RELATION_UPGRADE != commandRelationType && BOOTSTRAPPER_ACTION_UNINSTALL < action) + { + hr = VerCompareParsedVersions(pRegistrationVersion, pRelatedBundleVersion, &nCompareResult); + ExitOnFailure(hr, "Failed to compare bundle version '%ls' to related bundle version '%ls'", pRegistrationVersion ? pRegistrationVersion->sczVersion : NULL, pRelatedBundleVersion ? pRelatedBundleVersion->sczVersion : NULL); + + *pRequestState = (nCompareResult < 0) ? BOOTSTRAPPER_REQUEST_STATE_NONE : BOOTSTRAPPER_REQUEST_STATE_ABSENT; + } + break; + case BOOTSTRAPPER_RELATION_PATCH: __fallthrough; + case BOOTSTRAPPER_RELATION_ADDON: + if (BOOTSTRAPPER_ACTION_UNINSTALL == action) + { + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_ABSENT; + } + else if (BOOTSTRAPPER_ACTION_INSTALL == action || BOOTSTRAPPER_ACTION_MODIFY == action) + { + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; + } + else if (BOOTSTRAPPER_ACTION_REPAIR == action) + { + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR; + } + break; + case BOOTSTRAPPER_RELATION_DEPENDENT: + // Automatically repair dependent bundles to restore missing + // packages after uninstall unless we're being upgraded with the + // assumption that upgrades are cumulative (as intended). + if (BOOTSTRAPPER_RELATION_UPGRADE != commandRelationType && BOOTSTRAPPER_ACTION_UNINSTALL == action) + { + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR; + } + break; + case BOOTSTRAPPER_RELATION_DETECT: + break; + default: + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Unexpected relation type encountered during plan: %d", relatedBundleRelationType); + break; + } + +LExit: + return hr; +} + +extern "C" HRESULT PlanRelatedBundlesBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BURN_PLAN* pPlan + ) +{ + HRESULT hr = S_OK; + LPWSTR* rgsczAncestors = NULL; + UINT cAncestors = 0; + STRINGDICT_HANDLE sdAncestors = NULL; + + if (pRegistration->sczAncestors) + { + hr = StrSplitAllocArray(&rgsczAncestors, &cAncestors, pRegistration->sczAncestors, L";"); + ExitOnFailure(hr, "Failed to create string array from ancestors."); + + hr = DictCreateStringListFromArray(&sdAncestors, rgsczAncestors, cAncestors, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create dictionary from ancestors array."); + } + + for (DWORD i = 0; i < pRegistration->relatedBundles.cRelatedBundles; ++i) + { + BURN_RELATED_BUNDLE* pRelatedBundle = pRegistration->relatedBundles.rgRelatedBundles + i; + + if (!pRelatedBundle->fPlannable) + { + continue; + } + + pRelatedBundle->package.defaultRequested = BOOTSTRAPPER_REQUEST_STATE_NONE; + pRelatedBundle->package.requested = BOOTSTRAPPER_REQUEST_STATE_NONE; + + // Do not execute the same bundle twice. + if (sdAncestors) + { + hr = DictKeyExists(sdAncestors, pRelatedBundle->package.sczId); + if (SUCCEEDED(hr)) + { + LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_RELATED_BUNDLE_SCHEDULED, pRelatedBundle->package.sczId, LoggingRelationTypeToString(pRelatedBundle->relationType)); + continue; + } + else if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to lookup the bundle ID in the ancestors dictionary."); + } + } + else if (BOOTSTRAPPER_RELATION_DEPENDENT == pRelatedBundle->relationType && BOOTSTRAPPER_RELATION_NONE != relationType) + { + // Avoid repair loops for older bundles that do not handle ancestors. + LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_RELATED_BUNDLE_DEPENDENT, pRelatedBundle->package.sczId, LoggingRelationTypeToString(pRelatedBundle->relationType), LoggingRelationTypeToString(relationType)); + continue; + } + + // Pass along any ancestors and ourself to prevent infinite loops. + pRelatedBundle->package.Exe.wzAncestors = pRegistration->sczBundlePackageAncestors; + + hr = PlanDefaultRelatedBundleRequestState(relationType, pRelatedBundle->relationType, pPlan->action, pRegistration->pVersion, pRelatedBundle->pVersion, &pRelatedBundle->package.requested); + ExitOnFailure(hr, "Failed to get default request state for related bundle."); + + pRelatedBundle->package.defaultRequested = pRelatedBundle->package.requested; + + hr = UserExperienceOnPlanRelatedBundle(pUserExperience, pRelatedBundle->package.sczId, &pRelatedBundle->package.requested); + ExitOnRootFailure(hr, "BA aborted plan related bundle."); + + // Log when the BA changed the bundle state so the engine doesn't get blamed for planning the wrong thing. + if (pRelatedBundle->package.requested != pRelatedBundle->package.defaultRequested) + { + LogId(REPORT_STANDARD, MSG_PLANNED_BUNDLE_UX_CHANGED_REQUEST, pRelatedBundle->package.sczId, LoggingRequestStateToString(pRelatedBundle->package.requested), LoggingRequestStateToString(pRelatedBundle->package.defaultRequested)); + } + + // If uninstalling and the dependent related bundle may be executed, ignore its provider key to allow for downgrades with ref-counting. + if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action && BOOTSTRAPPER_RELATION_DEPENDENT == pRelatedBundle->relationType && BOOTSTRAPPER_REQUEST_STATE_NONE != pRelatedBundle->package.requested) + { + if (0 < pRelatedBundle->package.cDependencyProviders) + { + // Bundles only support a single provider key. + const BURN_DEPENDENCY_PROVIDER* pProvider = pRelatedBundle->package.rgDependencyProviders; + + hr = DepDependencyArrayAlloc(&pPlan->rgPlannedProviders, &pPlan->cPlannedProviders, pProvider->sczKey, pProvider->sczDisplayName); + ExitOnFailure(hr, "Failed to add the package provider key \"%ls\" to the planned list.", pProvider->sczKey); + } + } + } + +LExit: + ReleaseDict(sdAncestors); + ReleaseStrArray(rgsczAncestors, cAncestors); + + return hr; +} + +extern "C" HRESULT PlanRelatedBundlesComplete( + __in BURN_REGISTRATION* pRegistration, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in DWORD dwExecuteActionEarlyIndex + ) +{ + HRESULT hr = S_OK; + LPWSTR sczIgnoreDependencies = NULL; + STRINGDICT_HANDLE sdProviderKeys = NULL; + + // Get the list of dependencies to ignore to pass to related bundles. + hr = DependencyAllocIgnoreDependencies(pPlan, &sczIgnoreDependencies); + ExitOnFailure(hr, "Failed to get the list of dependencies to ignore."); + + hr = DictCreateStringList(&sdProviderKeys, pPlan->cExecuteActions, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create dictionary for planned packages."); + + BOOL fExecutingAnyPackage = FALSE; + + for (DWORD i = 0; i < pPlan->cExecuteActions; ++i) + { + if (BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE == pPlan->rgExecuteActions[i].type && BOOTSTRAPPER_ACTION_STATE_NONE != pPlan->rgExecuteActions[i].exePackage.action) + { + fExecutingAnyPackage = TRUE; + + BURN_PACKAGE* pPackage = pPlan->rgExecuteActions[i].packageProvider.pPackage; + if (BURN_PACKAGE_TYPE_EXE == pPackage->type && BURN_EXE_PROTOCOL_TYPE_BURN == pPackage->Exe.protocol) + { + if (0 < pPackage->cDependencyProviders) + { + // Bundles only support a single provider key. + const BURN_DEPENDENCY_PROVIDER* pProvider = pPackage->rgDependencyProviders; + DictAddKey(sdProviderKeys, pProvider->sczKey); + } + } + } + else + { + switch (pPlan->rgExecuteActions[i].type) + { + case BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE: + fExecutingAnyPackage |= (BOOTSTRAPPER_ACTION_STATE_NONE != pPlan->rgExecuteActions[i].msiPackage.action); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSP_TARGET: + fExecutingAnyPackage |= (BOOTSTRAPPER_ACTION_STATE_NONE != pPlan->rgExecuteActions[i].mspTarget.action); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE: + fExecutingAnyPackage |= (BOOTSTRAPPER_ACTION_STATE_NONE != pPlan->rgExecuteActions[i].msuPackage.action); + break; + } + } + } + + for (DWORD i = 0; i < pRegistration->relatedBundles.cRelatedBundles; ++i) + { + DWORD *pdwInsertIndex = NULL; + BURN_RELATED_BUNDLE* pRelatedBundle = pRegistration->relatedBundles.rgRelatedBundles + i; + + if (!pRelatedBundle->fPlannable) + { + continue; + } + + // Do not execute if a major upgrade to the related bundle is an embedded bundle (Provider keys are the same) + if (0 < pRelatedBundle->package.cDependencyProviders) + { + // Bundles only support a single provider key. + const BURN_DEPENDENCY_PROVIDER* pProvider = pRelatedBundle->package.rgDependencyProviders; + hr = DictKeyExists(sdProviderKeys, pProvider->sczKey); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to check the dictionary for a related bundle provider key: \"%ls\".", pProvider->sczKey); + // Key found, so there is an embedded bundle with the same provider key that will be executed. So this related bundle should not be added to the plan + LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_RELATED_BUNDLE_EMBEDDED_BUNDLE_NEWER, pRelatedBundle->package.sczId, LoggingRelationTypeToString(pRelatedBundle->relationType), pProvider->sczKey); + continue; + } + else + { + hr = S_OK; + } + } + + // For an uninstall, there is no need to repair dependent bundles if no packages are executing. + if (!fExecutingAnyPackage && BOOTSTRAPPER_RELATION_DEPENDENT == pRelatedBundle->relationType && BOOTSTRAPPER_REQUEST_STATE_REPAIR == pRelatedBundle->package.requested && BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) + { + pRelatedBundle->package.requested = BOOTSTRAPPER_REQUEST_STATE_NONE; + LogId(REPORT_STANDARD, MSG_PLAN_SKIPPED_DEPENDENT_BUNDLE_REPAIR, pRelatedBundle->package.sczId, LoggingRelationTypeToString(pRelatedBundle->relationType)); + } + + if (BOOTSTRAPPER_RELATION_ADDON == pRelatedBundle->relationType || BOOTSTRAPPER_RELATION_PATCH == pRelatedBundle->relationType) + { + // Addon and patch bundles will be passed a list of dependencies to ignore for planning. + hr = StrAllocString(&pRelatedBundle->package.Exe.sczIgnoreDependencies, sczIgnoreDependencies, 0); + ExitOnFailure(hr, "Failed to copy the list of dependencies to ignore."); + + // Uninstall addons and patches early in the chain, before other packages are uninstalled. + if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) + { + pdwInsertIndex = &dwExecuteActionEarlyIndex; + } + } + + if (BOOTSTRAPPER_REQUEST_STATE_NONE != pRelatedBundle->package.requested) + { + hr = ExeEnginePlanCalculatePackage(&pRelatedBundle->package); + ExitOnFailure(hr, "Failed to calcuate plan for related bundle: %ls", pRelatedBundle->package.sczId); + + // Calculate package states based on reference count for addon and patch related bundles. + if (BOOTSTRAPPER_RELATION_ADDON == pRelatedBundle->relationType || BOOTSTRAPPER_RELATION_PATCH == pRelatedBundle->relationType) + { + hr = DependencyPlanPackageBegin(pRegistration->fPerMachine, &pRelatedBundle->package, pPlan); + ExitOnFailure(hr, "Failed to begin plan dependency actions to package: %ls", pRelatedBundle->package.sczId); + + // If uninstalling a related bundle, make sure the bundle is uninstalled after removing registration. + if (pdwInsertIndex && BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action) + { + ++(*pdwInsertIndex); + } + } + + hr = ExeEnginePlanAddPackage(pdwInsertIndex, &pRelatedBundle->package, pPlan, pLog, pVariables, NULL); + ExitOnFailure(hr, "Failed to add to plan related bundle: %ls", pRelatedBundle->package.sczId); + + // Calculate package states based on reference count for addon and patch related bundles. + if (BOOTSTRAPPER_RELATION_ADDON == pRelatedBundle->relationType || BOOTSTRAPPER_RELATION_PATCH == pRelatedBundle->relationType) + { + hr = DependencyPlanPackageComplete(&pRelatedBundle->package, pPlan); + ExitOnFailure(hr, "Failed to complete plan dependency actions for related bundle package: %ls", pRelatedBundle->package.sczId); + } + + // If we are going to take any action on this package, add progress for it. + if (BOOTSTRAPPER_ACTION_STATE_NONE != pRelatedBundle->package.execute || BOOTSTRAPPER_ACTION_STATE_NONE != pRelatedBundle->package.rollback) + { + LoggingIncrementPackageSequence(); + + ++pPlan->cExecutePackagesTotal; + ++pPlan->cOverallProgressTicksTotal; + } + + // If package is per-machine and is being executed, flag the plan to be per-machine as well. + if (pRelatedBundle->package.fPerMachine) + { + pPlan->fPerMachine = TRUE; + } + } + else if (BOOTSTRAPPER_RELATION_ADDON == pRelatedBundle->relationType || BOOTSTRAPPER_RELATION_PATCH == pRelatedBundle->relationType) + { + // Make sure the package is properly ref-counted even if no plan is requested. + hr = DependencyPlanPackageBegin(pRegistration->fPerMachine, &pRelatedBundle->package, pPlan); + ExitOnFailure(hr, "Failed to begin plan dependency actions for related bundle package: %ls", pRelatedBundle->package.sczId); + + hr = DependencyPlanPackage(pdwInsertIndex, &pRelatedBundle->package, pPlan); + ExitOnFailure(hr, "Failed to plan related bundle package provider actions."); + + hr = DependencyPlanPackageComplete(&pRelatedBundle->package, pPlan); + ExitOnFailure(hr, "Failed to complete plan dependency actions for related bundle package: %ls", pRelatedBundle->package.sczId); + } + } + +LExit: + ReleaseDict(sdProviderKeys); + ReleaseStr(sczIgnoreDependencies); + + return hr; +} + +extern "C" HRESULT PlanFinalizeActions( + __in BURN_PLAN* pPlan + ) +{ + HRESULT hr = S_OK; + + FinalizePatchActions(TRUE, pPlan->rgExecuteActions, pPlan->cExecuteActions); + + FinalizePatchActions(FALSE, pPlan->rgRollbackActions, pPlan->cRollbackActions); + + RemoveUnnecessaryActions(TRUE, pPlan->rgExecuteActions, pPlan->cExecuteActions); + + RemoveUnnecessaryActions(FALSE, pPlan->rgRollbackActions, pPlan->cRollbackActions); + + return hr; +} + +extern "C" HRESULT PlanCleanPackage( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + BOOL fPlanCleanPackage = FALSE; + BURN_CLEAN_ACTION* pCleanAction = NULL; + + // The following is a complex set of logic that determines when a package should be cleaned from the cache. + if (BOOTSTRAPPER_CACHE_TYPE_FORCE > pPackage->cacheType || BOOTSTRAPPER_ACTION_CACHE > pPlan->action) + { + // The following are all different reasons why the package should be cleaned from the cache. + // The else-ifs are used to make the conditions easier to see (rather than have them combined + // in one huge condition). + if (BOOTSTRAPPER_CACHE_TYPE_KEEP > pPackage->cacheType) // easy, package is not supposed to stay cached. + { + fPlanCleanPackage = TRUE; + } + else if ((BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT == pPackage->requested || + BOOTSTRAPPER_REQUEST_STATE_ABSENT == pPackage->requested) && // requested to be removed and + BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) // actually being removed. + { + fPlanCleanPackage = TRUE; + } + else if ((BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT == pPackage->requested || + BOOTSTRAPPER_REQUEST_STATE_ABSENT == pPackage->requested) && // requested to be removed but + BOOTSTRAPPER_ACTION_STATE_NONE == pPackage->execute && // execute is do nothing and + !pPackage->fDependencyManagerWasHere && // dependency manager didn't change execute and + BOOTSTRAPPER_PACKAGE_STATE_PRESENT > pPackage->currentState) // currently not installed. + { + fPlanCleanPackage = TRUE; + } + else if (BOOTSTRAPPER_ACTION_UNINSTALL == pPlan->action && // uninstalling and + BOOTSTRAPPER_REQUEST_STATE_NONE == pPackage->requested && // requested do nothing (aka: default) and + BOOTSTRAPPER_ACTION_STATE_NONE == pPackage->execute && // execute is still do nothing and + !pPackage->fDependencyManagerWasHere && // dependency manager didn't change execute and + BOOTSTRAPPER_PACKAGE_STATE_PRESENT > pPackage->currentState) // currently not installed. + { + fPlanCleanPackage = TRUE; + } + } + + if (fPlanCleanPackage) + { + hr = MemEnsureArraySize(reinterpret_cast(&pPlan->rgCleanActions), pPlan->cCleanActions + 1, sizeof(BURN_CLEAN_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of clean actions."); + + pCleanAction = pPlan->rgCleanActions + pPlan->cCleanActions; + ++pPlan->cCleanActions; + + pCleanAction->pPackage = pPackage; + + pPackage->fPlannedUncache = TRUE; + + if (pPackage->fCanAffectRegistration) + { + pPackage->expectedCacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + } + +LExit: + return hr; +} + +extern "C" HRESULT PlanExecuteCacheSyncAndRollback( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __in HANDLE hCacheEvent + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pAction = NULL; + + hr = PlanAppendExecuteAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append wait action for caching."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_WAIT_SYNCPOINT; + pAction->syncpoint.hEvent = hCacheEvent; + + hr = PlanAppendRollbackAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append rollback action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_UNCACHE_PACKAGE; + pAction->uncachePackage.pPackage = pPackage; + + hr = PlanExecuteCheckpoint(pPlan); + ExitOnFailure(hr, "Failed to append execute checkpoint for cache rollback."); + +LExit: + return hr; +} + +extern "C" HRESULT PlanExecuteCheckpoint( + __in BURN_PLAN* pPlan + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pAction = NULL; + DWORD dwCheckpointId = GetNextCheckpointId(pPlan); + + // execute checkpoint + hr = PlanAppendExecuteAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append execute action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_CHECKPOINT; + pAction->checkpoint.dwId = dwCheckpointId; + pAction->checkpoint.pActiveRollbackBoundary = pPlan->pActiveRollbackBoundary; + + // rollback checkpoint + hr = PlanAppendRollbackAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append rollback action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_CHECKPOINT; + pAction->checkpoint.dwId = dwCheckpointId; + pAction->checkpoint.pActiveRollbackBoundary = pPlan->pActiveRollbackBoundary; + +LExit: + return hr; +} + +extern "C" HRESULT PlanInsertExecuteAction( + __in DWORD dwIndex, + __in BURN_PLAN* pPlan, + __out BURN_EXECUTE_ACTION** ppExecuteAction + ) +{ + HRESULT hr = S_OK; + + hr = MemInsertIntoArray((void**)&pPlan->rgExecuteActions, dwIndex, 1, pPlan->cExecuteActions + 1, sizeof(BURN_EXECUTE_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of execute actions."); + + *ppExecuteAction = pPlan->rgExecuteActions + dwIndex; + ++pPlan->cExecuteActions; + +LExit: + return hr; +} + +extern "C" HRESULT PlanInsertRollbackAction( + __in DWORD dwIndex, + __in BURN_PLAN* pPlan, + __out BURN_EXECUTE_ACTION** ppRollbackAction + ) +{ + HRESULT hr = S_OK; + + hr = MemInsertIntoArray((void**)&pPlan->rgRollbackActions, dwIndex, 1, pPlan->cRollbackActions + 1, sizeof(BURN_EXECUTE_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of rollback actions."); + + *ppRollbackAction = pPlan->rgRollbackActions + dwIndex; + ++pPlan->cRollbackActions; + +LExit: + return hr; +} + +extern "C" HRESULT PlanAppendExecuteAction( + __in BURN_PLAN* pPlan, + __out BURN_EXECUTE_ACTION** ppExecuteAction + ) +{ + HRESULT hr = S_OK; + + hr = MemEnsureArraySize((void**)&pPlan->rgExecuteActions, pPlan->cExecuteActions + 1, sizeof(BURN_EXECUTE_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of execute actions."); + + *ppExecuteAction = pPlan->rgExecuteActions + pPlan->cExecuteActions; + ++pPlan->cExecuteActions; + +LExit: + return hr; +} + +extern "C" HRESULT PlanAppendRollbackAction( + __in BURN_PLAN* pPlan, + __out BURN_EXECUTE_ACTION** ppRollbackAction + ) +{ + HRESULT hr = S_OK; + + hr = MemEnsureArraySize((void**)&pPlan->rgRollbackActions, pPlan->cRollbackActions + 1, sizeof(BURN_EXECUTE_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of rollback actions."); + + *ppRollbackAction = pPlan->rgRollbackActions + pPlan->cRollbackActions; + ++pPlan->cRollbackActions; + +LExit: + return hr; +} + +extern "C" HRESULT PlanRollbackBoundaryBegin( + __in BURN_PLAN* pPlan, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pExecuteAction = NULL; + + AssertSz(!pPlan->pActiveRollbackBoundary, "PlanRollbackBoundaryBegin called without completing previous RollbackBoundary"); + pPlan->pActiveRollbackBoundary = pRollbackBoundary; + + // Add begin rollback boundary to execute plan. + hr = PlanAppendExecuteAction(pPlan, &pExecuteAction); + ExitOnFailure(hr, "Failed to append rollback boundary begin action."); + + pExecuteAction->type = BURN_EXECUTE_ACTION_TYPE_ROLLBACK_BOUNDARY; + pExecuteAction->rollbackBoundary.pRollbackBoundary = pRollbackBoundary; + + // Add begin rollback boundary to rollback plan. + hr = PlanAppendRollbackAction(pPlan, &pExecuteAction); + ExitOnFailure(hr, "Failed to append rollback boundary begin action."); + + pExecuteAction->type = BURN_EXECUTE_ACTION_TYPE_ROLLBACK_BOUNDARY; + pExecuteAction->rollbackBoundary.pRollbackBoundary = pRollbackBoundary; + + // Add begin MSI transaction to execute plan. + if (pRollbackBoundary->fTransaction) + { + hr = PlanExecuteCheckpoint(pPlan); + ExitOnFailure(hr, "Failed to append checkpoint before MSI transaction begin action."); + + hr = PlanAppendExecuteAction(pPlan, &pExecuteAction); + ExitOnFailure(hr, "Failed to append MSI transaction begin action."); + + pExecuteAction->type = BURN_EXECUTE_ACTION_TYPE_BEGIN_MSI_TRANSACTION; + pExecuteAction->msiTransaction.pRollbackBoundary = pRollbackBoundary; + } + +LExit: + return hr; +} + +extern "C" HRESULT PlanRollbackBoundaryComplete( + __in BURN_PLAN* pPlan + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pExecuteAction = NULL; + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary = pPlan->pActiveRollbackBoundary; + + AssertSz(pRollbackBoundary, "PlanRollbackBoundaryComplete called without an active RollbackBoundary"); + + if (pRollbackBoundary && pRollbackBoundary->fTransaction) + { + // Add commit MSI transaction to execute plan. + hr = PlanAppendExecuteAction(pPlan, &pExecuteAction); + ExitOnFailure(hr, "Failed to append MSI transaction commit action."); + + pExecuteAction->type = BURN_EXECUTE_ACTION_TYPE_COMMIT_MSI_TRANSACTION; + pExecuteAction->msiTransaction.pRollbackBoundary = pRollbackBoundary; + } + + pPlan->pActiveRollbackBoundary = NULL; + + // Add checkpoints. + hr = PlanExecuteCheckpoint(pPlan); + +LExit: + return hr; +} + +/******************************************************************* + PlanSetResumeCommand - Initializes resume command string + +*******************************************************************/ +extern "C" HRESULT PlanSetResumeCommand( + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_ACTION action, + __in BOOTSTRAPPER_COMMAND* pCommand, + __in BURN_LOGGING* pLog + ) +{ + HRESULT hr = S_OK; + + // build the resume command-line. + hr = CoreRecreateCommandLine(&pRegistration->sczResumeCommandLine, action, pCommand->display, pCommand->restart, pCommand->relationType, pCommand->fPassthrough, pRegistration->sczActiveParent, pRegistration->sczAncestors, pLog->sczPath, pCommand->wzCommandLine); + ExitOnFailure(hr, "Failed to recreate resume command-line."); + +LExit: + return hr; +} + + +// internal function definitions + +static void UninitializeRegistrationAction( + __in BURN_DEPENDENT_REGISTRATION_ACTION* pAction + ) +{ + ReleaseStr(pAction->sczDependentProviderKey); + ReleaseStr(pAction->sczBundleId); + memset(pAction, 0, sizeof(BURN_DEPENDENT_REGISTRATION_ACTION)); +} + +static void UninitializeCacheAction( + __in BURN_CACHE_ACTION* pCacheAction + ) +{ + switch (pCacheAction->type) + { + case BURN_CACHE_ACTION_TYPE_SIGNAL_SYNCPOINT: + ReleaseHandle(pCacheAction->syncpoint.hEvent); + break; + + case BURN_CACHE_ACTION_TYPE_LAYOUT_BUNDLE: + ReleaseStr(pCacheAction->bundleLayout.sczExecutableName); + ReleaseStr(pCacheAction->bundleLayout.sczUnverifiedPath); + break; + } +} + +static void ResetPlannedContainerState( + __in BURN_CONTAINER* pContainer + ) +{ + pContainer->fPlanned = FALSE; + pContainer->qwExtractSizeTotal = 0; + pContainer->qwCommittedCacheProgress = 0; + pContainer->qwCommittedExtractProgress = 0; + pContainer->hrExtract = S_OK; +} + +static void ResetPlannedPayloadsState( + __in BURN_PAYLOADS* pPayloads + ) +{ + for (DWORD i = 0; i < pPayloads->cPayloads; ++i) + { + BURN_PAYLOAD* pPayload = pPayloads->rgPayloads + i; + + pPayload->cRemainingInstances = 0; + pPayload->state = BURN_PAYLOAD_STATE_NONE; + ReleaseNullStr(pPayload->sczLocalFilePath); + } +} + +static void ResetPlannedPayloadGroupState( + __in BURN_PAYLOAD_GROUP* pPayloadGroup + ) +{ + for (DWORD i = 0; i < pPayloadGroup->cItems; ++i) + { + BURN_PAYLOAD_GROUP_ITEM* pItem = pPayloadGroup->rgItems + i; + + pItem->fCached = FALSE; + pItem->qwCommittedCacheProgress = 0; + } +} + +static void ResetPlannedPackageState( + __in BURN_PACKAGE* pPackage + ) +{ + // Reset package state that is a result of planning. + pPackage->cacheType = pPackage->authoredCacheType; + pPackage->defaultRequested = BOOTSTRAPPER_REQUEST_STATE_NONE; + pPackage->requested = BOOTSTRAPPER_REQUEST_STATE_NONE; + pPackage->fPlannedCache = FALSE; + pPackage->fPlannedUncache = FALSE; + pPackage->execute = BOOTSTRAPPER_ACTION_STATE_NONE; + pPackage->rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + pPackage->providerExecute = BURN_DEPENDENCY_ACTION_NONE; + pPackage->providerRollback = BURN_DEPENDENCY_ACTION_NONE; + pPackage->dependencyExecute = BURN_DEPENDENCY_ACTION_NONE; + pPackage->dependencyRollback = BURN_DEPENDENCY_ACTION_NONE; + pPackage->fDependencyManagerWasHere = FALSE; + pPackage->expectedCacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + pPackage->expectedInstallRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN; + + ReleaseNullStr(pPackage->sczCacheFolder); + + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) + { + for (DWORD i = 0; i < pPackage->Msi.cFeatures; ++i) + { + BURN_MSIFEATURE* pFeature = &pPackage->Msi.rgFeatures[i]; + + pFeature->expectedState = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; + pFeature->defaultRequested = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; + pFeature->requested = BOOTSTRAPPER_FEATURE_STATE_UNKNOWN; + pFeature->execute = BOOTSTRAPPER_FEATURE_ACTION_NONE; + pFeature->rollback = BOOTSTRAPPER_FEATURE_ACTION_NONE; + } + + for (DWORD i = 0; i < pPackage->Msi.cSlipstreamMspPackages; ++i) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = &pPackage->Msi.rgSlipstreamMsps[i]; + + pSlipstreamMsp->execute = BOOTSTRAPPER_ACTION_STATE_NONE; + pSlipstreamMsp->rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + } + } + else if (BURN_PACKAGE_TYPE_MSP == pPackage->type && pPackage->Msp.rgTargetProducts) + { + for (DWORD i = 0; i < pPackage->Msp.cTargetProductCodes; ++i) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = &pPackage->Msp.rgTargetProducts[i]; + + pTargetProduct->defaultRequested = BOOTSTRAPPER_REQUEST_STATE_NONE; + pTargetProduct->requested = BOOTSTRAPPER_REQUEST_STATE_NONE; + pTargetProduct->execute = BOOTSTRAPPER_ACTION_STATE_NONE; + pTargetProduct->rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + pTargetProduct->executeSkip = BURN_PATCH_SKIP_STATE_NONE; + pTargetProduct->rollbackSkip = BURN_PATCH_SKIP_STATE_NONE; + } + } + + ResetPlannedPayloadGroupState(&pPackage->payloads); +} + +static void ResetPlannedRollbackBoundaryState( + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ) +{ + pRollbackBoundary->fActiveTransaction = FALSE; + ReleaseNullStr(pRollbackBoundary->sczLogPath); +} + +static HRESULT GetActionDefaultRequestState( + __in BOOTSTRAPPER_ACTION action, + __in BOOL fPermanent, + __in BOOTSTRAPPER_PACKAGE_STATE currentState, + __out BOOTSTRAPPER_REQUEST_STATE* pRequestState + ) +{ + HRESULT hr = S_OK; + + switch (action) + { + case BOOTSTRAPPER_ACTION_CACHE: + switch (currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; + break; + + default: + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_CACHE; + break; + } + break; + + case BOOTSTRAPPER_ACTION_INSTALL: __fallthrough; + case BOOTSTRAPPER_ACTION_UPDATE_REPLACE: __fallthrough; + case BOOTSTRAPPER_ACTION_UPDATE_REPLACE_EMBEDDED: + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; + break; + + case BOOTSTRAPPER_ACTION_REPAIR: + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_REPAIR; + break; + + case BOOTSTRAPPER_ACTION_UNINSTALL: + *pRequestState = fPermanent ? BOOTSTRAPPER_REQUEST_STATE_NONE : BOOTSTRAPPER_REQUEST_STATE_ABSENT; + break; + + case BOOTSTRAPPER_ACTION_MODIFY: + switch (currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_ABSENT; + break; + + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_PRESENT; + break; + + default: + *pRequestState = BOOTSTRAPPER_REQUEST_STATE_NONE; + break; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid action state."); + } + +LExit: + return hr; +} + +static HRESULT AddRegistrationAction( + __in BURN_PLAN* pPlan, + __in BURN_DEPENDENT_REGISTRATION_ACTION_TYPE type, + __in_z LPCWSTR wzDependentProviderKey, + __in_z LPCWSTR wzOwnerBundleId + ) +{ + HRESULT hr = S_OK; + BURN_DEPENDENT_REGISTRATION_ACTION_TYPE rollbackType = (BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_REGISTER == type) ? BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_UNREGISTER : BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_REGISTER; + BURN_DEPENDENT_REGISTRATION_ACTION* pAction = NULL; + + // Create forward registration action. + hr = MemEnsureArraySize((void**)&pPlan->rgRegistrationActions, pPlan->cRegistrationActions + 1, sizeof(BURN_DEPENDENT_REGISTRATION_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of registration actions."); + + pAction = pPlan->rgRegistrationActions + pPlan->cRegistrationActions; + ++pPlan->cRegistrationActions; + + pAction->type = type; + + hr = StrAllocString(&pAction->sczBundleId, wzOwnerBundleId, 0); + ExitOnFailure(hr, "Failed to copy owner bundle to registration action."); + + hr = StrAllocString(&pAction->sczDependentProviderKey, wzDependentProviderKey, 0); + ExitOnFailure(hr, "Failed to copy dependent provider key to registration action."); + + // Create rollback registration action. + hr = MemEnsureArraySize((void**)&pPlan->rgRollbackRegistrationActions, pPlan->cRollbackRegistrationActions + 1, sizeof(BURN_DEPENDENT_REGISTRATION_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of rollback registration actions."); + + pAction = pPlan->rgRollbackRegistrationActions + pPlan->cRollbackRegistrationActions; + ++pPlan->cRollbackRegistrationActions; + + pAction->type = rollbackType; + + hr = StrAllocString(&pAction->sczBundleId, wzOwnerBundleId, 0); + ExitOnFailure(hr, "Failed to copy owner bundle to registration action."); + + hr = StrAllocString(&pAction->sczDependentProviderKey, wzDependentProviderKey, 0); + ExitOnFailure(hr, "Failed to copy dependent provider key to rollback registration action."); + +LExit: + return hr; +} + +static HRESULT AddCachePackage( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __out HANDLE* phSyncpointEvent + ) +{ + HRESULT hr = S_OK; + + // If this is an MSI package with slipstream MSPs, ensure the MSPs are cached first. + if (BURN_PACKAGE_TYPE_MSI == pPackage->type && 0 < pPackage->Msi.cSlipstreamMspPackages) + { + hr = AddCacheSlipstreamMsps(pPlan, pPackage); + ExitOnFailure(hr, "Failed to plan slipstream patches for package."); + } + + hr = AddCachePackageHelper(pPlan, pPackage, phSyncpointEvent); + ExitOnFailure(hr, "Failed to plan cache package."); + +LExit: + return hr; +} + +static HRESULT AddCachePackageHelper( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __out HANDLE* phSyncpointEvent + ) +{ + AssertSz(pPackage->sczCacheId && *pPackage->sczCacheId, "AddCachePackageHelper() expects the package to have a cache id."); + + HRESULT hr = S_OK; + BURN_CACHE_ACTION* pCacheAction = NULL; + DWORD dwCheckpoint = 0; + + BOOL fPlanned = AlreadyPlannedCachePackage(pPlan, pPackage->sczId, phSyncpointEvent); + if (fPlanned) + { + ExitFunction(); + } + + // Cache checkpoints happen before the package is cached because downloading packages' + // payloads will not roll themselves back the way installation packages rollback on + // failure automatically. + dwCheckpoint = GetNextCheckpointId(pPlan); + + hr = AppendCacheAction(pPlan, &pCacheAction); + ExitOnFailure(hr, "Failed to append checkpoint before package start action."); + + pCacheAction->type = BURN_CACHE_ACTION_TYPE_CHECKPOINT; + pCacheAction->checkpoint.dwId = dwCheckpoint; + + // Only plan the cache rollback if the package is also going to be uninstalled; + // otherwise, future operations like repair will not be able to locate the cached package. + BOOL fPlanCacheRollback = (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->rollback); + + if (fPlanCacheRollback) + { + hr = AppendRollbackCacheAction(pPlan, &pCacheAction); + ExitOnFailure(hr, "Failed to append rollback cache action."); + + pCacheAction->type = BURN_CACHE_ACTION_TYPE_CHECKPOINT; + pCacheAction->checkpoint.dwId = dwCheckpoint; + } + + hr = PlanLayoutPackage(pPlan, pPackage); + ExitOnFailure(hr, "Failed to plan cache for package."); + + // Create syncpoint action. + hr = AppendCacheAction(pPlan, &pCacheAction); + ExitOnFailure(hr, "Failed to append cache action."); + + pCacheAction->type = BURN_CACHE_ACTION_TYPE_SIGNAL_SYNCPOINT; + pCacheAction->syncpoint.hEvent = ::CreateEventW(NULL, TRUE, FALSE, NULL); + ExitOnNullWithLastError(pCacheAction->syncpoint.hEvent, hr, "Failed to create syncpoint event."); + + *phSyncpointEvent = pCacheAction->syncpoint.hEvent; + + pPackage->fPlannedCache = TRUE; + if (pPackage->fCanAffectRegistration) + { + pPackage->expectedCacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + +LExit: + return hr; +} + +static HRESULT AddCacheSlipstreamMsps( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + HANDLE hIgnored = NULL; + + AssertSz(BURN_PACKAGE_TYPE_MSI == pPackage->type, "Only MSI packages can have slipstream patches."); + + for (DWORD i = 0; i < pPackage->Msi.cSlipstreamMspPackages; ++i) + { + BURN_PACKAGE* pMspPackage = pPackage->Msi.rgSlipstreamMsps[i].pMspPackage; + AssertSz(BURN_PACKAGE_TYPE_MSP == pMspPackage->type, "Only MSP packages can be slipstream patches."); + + hr = AddCachePackageHelper(pPlan, pMspPackage, &hIgnored); + ExitOnFailure(hr, "Failed to plan slipstream MSP: %ls", pMspPackage->sczId); + } + +LExit: + return hr; +} + +static BOOL AlreadyPlannedCachePackage( + __in BURN_PLAN* pPlan, + __in_z LPCWSTR wzPackageId, + __out HANDLE* phSyncpointEvent + ) +{ + BOOL fPlanned = FALSE; + + for (DWORD iCacheAction = 0; iCacheAction < pPlan->cCacheActions; ++iCacheAction) + { + BURN_CACHE_ACTION* pCacheAction = pPlan->rgCacheActions + iCacheAction; + + if (BURN_CACHE_ACTION_TYPE_PACKAGE == pCacheAction->type) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pCacheAction->package.pPackage->sczId, -1, wzPackageId, -1)) + { + if (iCacheAction + 1 < pPlan->cCacheActions && BURN_CACHE_ACTION_TYPE_SIGNAL_SYNCPOINT == pPlan->rgCacheActions[iCacheAction + 1].type) + { + *phSyncpointEvent = pPlan->rgCacheActions[iCacheAction + 1].syncpoint.hEvent; + } + + fPlanned = TRUE; + break; + } + } + } + + return fPlanned; +} + +static DWORD GetNextCheckpointId( + __in BURN_PLAN* pPlan + ) +{ + return ++pPlan->dwNextCheckpointId; +} + +static HRESULT AppendCacheAction( + __in BURN_PLAN* pPlan, + __out BURN_CACHE_ACTION** ppCacheAction + ) +{ + HRESULT hr = S_OK; + + hr = MemEnsureArraySize(reinterpret_cast(&pPlan->rgCacheActions), pPlan->cCacheActions + 1, sizeof(BURN_CACHE_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of cache actions."); + + *ppCacheAction = pPlan->rgCacheActions + pPlan->cCacheActions; + ++pPlan->cCacheActions; + +LExit: + return hr; +} + +static HRESULT AppendRollbackCacheAction( + __in BURN_PLAN* pPlan, + __out BURN_CACHE_ACTION** ppCacheAction + ) +{ + HRESULT hr = S_OK; + + hr = MemEnsureArraySize(reinterpret_cast(&pPlan->rgRollbackCacheActions), pPlan->cRollbackCacheActions + 1, sizeof(BURN_CACHE_ACTION), 5); + ExitOnFailure(hr, "Failed to grow plan's array of rollback cache actions."); + + *ppCacheAction = pPlan->rgRollbackCacheActions + pPlan->cRollbackCacheActions; + ++pPlan->cRollbackCacheActions; + +LExit: + return hr; +} + +static HRESULT ProcessPayloadGroup( + __in BURN_PLAN* pPlan, + __in BURN_PAYLOAD_GROUP* pPayloadGroup + ) +{ + HRESULT hr = S_OK; + + for (DWORD i = 0; i < pPayloadGroup->cItems; ++i) + { + BURN_PAYLOAD_GROUP_ITEM* pItem = pPayloadGroup->rgItems + i; + BURN_PAYLOAD* pPayload = pItem->pPayload; + + pPayload->cRemainingInstances += 1; + + if (pPayload->pContainer && !pPayload->pContainer->fPlanned) + { + hr = PlanLayoutContainer(pPlan, pPayload->pContainer); + ExitOnFailure(hr, "Failed to plan container: %ls", pPayload->pContainer->sczId); + } + + if (!pPlan->sczLayoutDirectory || !pPayload->pContainer) + { + // Acquire + Verify + Finalize + pPlan->qwCacheSizeTotal += 3 * pPayload->qwFileSize; + + if (!pPlan->sczLayoutDirectory) + { + // Staging + pPlan->qwCacheSizeTotal += pPayload->qwFileSize; + } + } + + if (!pPlan->sczLayoutDirectory && pPayload->pContainer && 1 == pPayload->cRemainingInstances) + { + // Extract + pPlan->qwCacheSizeTotal += pPayload->qwFileSize; + pPayload->pContainer->qwExtractSizeTotal += pPayload->qwFileSize; + } + + if (!pPayload->sczUnverifiedPath) + { + hr = CacheCalculatePayloadWorkingPath(pPlan->wzBundleId, pPayload, &pPayload->sczUnverifiedPath); + ExitOnFailure(hr, "Failed to calculate unverified path for payload."); + } + } + +LExit: + return hr; +} + +static void RemoveUnnecessaryActions( + __in BOOL fExecute, + __in BURN_EXECUTE_ACTION* rgActions, + __in DWORD cActions + ) +{ + LPCSTR szExecuteOrRollback = fExecute ? "execute" : "rollback"; + + for (DWORD i = 0; i < cActions; ++i) + { + BURN_EXECUTE_ACTION* pAction = rgActions + i; + + if (BURN_EXECUTE_ACTION_TYPE_MSP_TARGET == pAction->type && pAction->mspTarget.pChainedTargetPackage) + { + BURN_MSPTARGETPRODUCT* pFirstTargetProduct = pAction->mspTarget.rgOrderedPatches->pTargetProduct; + BURN_PATCH_SKIP_STATE skipState = fExecute ? pFirstTargetProduct->executeSkip : pFirstTargetProduct->rollbackSkip; + BOOTSTRAPPER_ACTION_STATE chainedTargetPackageAction = fExecute ? pAction->mspTarget.pChainedTargetPackage->execute : pAction->mspTarget.pChainedTargetPackage->rollback; + + switch (skipState) + { + case BURN_PATCH_SKIP_STATE_TARGET_UNINSTALL: + pAction->fDeleted = TRUE; + LogId(REPORT_STANDARD, MSG_PLAN_SKIP_PATCH_ACTION, pAction->mspTarget.pPackage->sczId, LoggingActionStateToString(pAction->mspTarget.action), pAction->mspTarget.pChainedTargetPackage->sczId, LoggingActionStateToString(chainedTargetPackageAction), szExecuteOrRollback); + break; + case BURN_PATCH_SKIP_STATE_SLIPSTREAM: + pAction->fDeleted = TRUE; + LogId(REPORT_STANDARD, MSG_PLAN_SKIP_SLIPSTREAM_ACTION, pAction->mspTarget.pPackage->sczId, LoggingActionStateToString(pAction->mspTarget.action), pAction->mspTarget.pChainedTargetPackage->sczId, LoggingActionStateToString(chainedTargetPackageAction), szExecuteOrRollback); + break; + } + } + } +} + +static void FinalizePatchActions( + __in BOOL fExecute, + __in BURN_EXECUTE_ACTION* rgActions, + __in DWORD cActions + ) +{ + for (DWORD i = 0; i < cActions; ++i) + { + BURN_EXECUTE_ACTION* pAction = rgActions + i; + + if (BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE == pAction->type) + { + BURN_PACKAGE* pPackage = pAction->msiPackage.pPackage; + AssertSz(BOOTSTRAPPER_ACTION_STATE_NONE < pAction->msiPackage.action, "Planned execute MSI action to do nothing"); + + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pAction->msiPackage.action) + { + // If we are uninstalling the MSI, we must skip all the patches. + for (DWORD j = 0; j < pPackage->Msi.cChainedPatches; ++j) + { + BURN_CHAINED_PATCH* pChainedPatch = pPackage->Msi.rgChainedPatches + j; + BURN_MSPTARGETPRODUCT* pTargetProduct = pChainedPatch->pMspPackage->Msp.rgTargetProducts + pChainedPatch->dwMspTargetProductIndex; + + if (fExecute) + { + pTargetProduct->execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + pTargetProduct->executeSkip = BURN_PATCH_SKIP_STATE_TARGET_UNINSTALL; + } + else + { + pTargetProduct->rollback = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + pTargetProduct->rollbackSkip = BURN_PATCH_SKIP_STATE_TARGET_UNINSTALL; + } + } + } + else + { + // If the slipstream target is being installed or upgraded (not uninstalled or repaired) then we will slipstream so skip + // the patch's standalone action. Also, if the slipstream target is being repaired and the patch is being + // repaired, skip this operation since it will be redundant. + // + // The primary goal here is to ensure that a slipstream patch that is yet not installed is installed even if the MSI + // is already on the machine. The slipstream must be installed standalone if the MSI is being repaired. + for (DWORD j = 0; j < pPackage->Msi.cSlipstreamMspPackages; ++j) + { + BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pPackage->Msi.rgSlipstreamMsps + j; + BURN_CHAINED_PATCH* pChainedPatch = pPackage->Msi.rgChainedPatches + pSlipstreamMsp->dwMsiChainedPatchIndex; + BURN_MSPTARGETPRODUCT* pTargetProduct = pSlipstreamMsp->pMspPackage->Msp.rgTargetProducts + pChainedPatch->dwMspTargetProductIndex; + BOOTSTRAPPER_ACTION_STATE action = fExecute ? pTargetProduct->execute : pTargetProduct->rollback; + BOOL fSlipstream = BOOTSTRAPPER_ACTION_STATE_UNINSTALL < action && + (BOOTSTRAPPER_ACTION_STATE_REPAIR != pAction->msiPackage.action || BOOTSTRAPPER_ACTION_STATE_REPAIR == action); + + if (fSlipstream) + { + if (fExecute) + { + pSlipstreamMsp->execute = action; + pTargetProduct->executeSkip = BURN_PATCH_SKIP_STATE_SLIPSTREAM; + } + else + { + pSlipstreamMsp->rollback = action; + pTargetProduct->rollbackSkip = BURN_PATCH_SKIP_STATE_SLIPSTREAM; + } + } + } + } + } + } +} + +static void CalculateExpectedRegistrationStates( + __in BURN_PACKAGE* rgPackages, + __in DWORD cPackages + ) +{ + for (DWORD i = 0; i < cPackages; ++i) + { + BURN_PACKAGE* pPackage = rgPackages + i; + + // MspPackages can have actions throughout the plan, so the plan needed to be finalized before anything could be calculated. + if (BURN_PACKAGE_TYPE_MSP == pPackage->type && !pPackage->fDependencyManagerWasHere) + { + pPackage->execute = BOOTSTRAPPER_ACTION_STATE_NONE; + pPackage->rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + + for (DWORD j = 0; j < pPackage->Msp.cTargetProductCodes; ++j) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + j; + + // The highest aggregate action state found will be used. + if (pPackage->execute < pTargetProduct->execute) + { + pPackage->execute = pTargetProduct->execute; + } + + if (pPackage->rollback < pTargetProduct->rollback) + { + pPackage->rollback = pTargetProduct->rollback; + } + } + } + + if (pPackage->fCanAffectRegistration) + { + if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL < pPackage->execute) + { + pPackage->expectedInstallRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + else if (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pPackage->execute) + { + pPackage->expectedInstallRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + + if (BURN_DEPENDENCY_ACTION_REGISTER == pPackage->dependencyExecute) + { + if (BURN_PACKAGE_REGISTRATION_STATE_IGNORED == pPackage->expectedCacheRegistrationState) + { + pPackage->expectedCacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + if (BURN_PACKAGE_REGISTRATION_STATE_IGNORED == pPackage->expectedInstallRegistrationState) + { + pPackage->expectedInstallRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + } + else if (BURN_DEPENDENCY_ACTION_UNREGISTER == pPackage->dependencyExecute) + { + if (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->expectedCacheRegistrationState) + { + pPackage->expectedCacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_IGNORED; + } + if (BURN_PACKAGE_REGISTRATION_STATE_PRESENT == pPackage->expectedInstallRegistrationState) + { + pPackage->expectedInstallRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_IGNORED; + } + } + } + } +} + +static HRESULT PlanDependencyActions( + __in BOOL fBundlePerMachine, + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + + hr = DependencyPlanPackageBegin(fBundlePerMachine, pPackage, pPlan); + ExitOnFailure(hr, "Failed to begin plan dependency actions for package: %ls", pPackage->sczId); + + hr = DependencyPlanPackage(NULL, pPackage, pPlan); + ExitOnFailure(hr, "Failed to plan package dependency actions."); + + hr = DependencyPlanPackageComplete(pPackage, pPlan); + ExitOnFailure(hr, "Failed to complete plan dependency actions for package: %ls", pPackage->sczId); + +LExit: + return hr; +} + +static HRESULT CalculateExecuteActions( + __in BURN_PACKAGE* pPackage, + __in_opt BURN_ROLLBACK_BOUNDARY* pActiveRollbackBoundary + ) +{ + HRESULT hr = S_OK; + BOOL fInsideMsiTransaction = pActiveRollbackBoundary && pActiveRollbackBoundary->fTransaction; + + // Calculate execute actions. + switch (pPackage->type) + { + case BURN_PACKAGE_TYPE_EXE: + hr = ExeEnginePlanCalculatePackage(pPackage); + break; + + case BURN_PACKAGE_TYPE_MSI: + hr = MsiEnginePlanCalculatePackage(pPackage, fInsideMsiTransaction); + break; + + case BURN_PACKAGE_TYPE_MSP: + hr = MspEnginePlanCalculatePackage(pPackage, fInsideMsiTransaction); + break; + + case BURN_PACKAGE_TYPE_MSU: + hr = MsuEnginePlanCalculatePackage(pPackage); + break; + + default: + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Invalid package type."); + } + +LExit: + return hr; +} + +static BOOL NeedsCache( + __in BURN_PACKAGE* pPackage, + __in BOOL fExecute + ) +{ + BOOTSTRAPPER_ACTION_STATE action = fExecute ? pPackage->execute : pPackage->rollback; + if (BURN_PACKAGE_TYPE_EXE == pPackage->type) // Exe packages require the package for all operations (even uninstall). + { + return BOOTSTRAPPER_ACTION_STATE_NONE != action; + } + else // The other package types can uninstall without the original package. + { + return BOOTSTRAPPER_ACTION_STATE_UNINSTALL < action; + } +} + +static BOOL ForceCache( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ) +{ + // All packages that have cacheType set to force should be cached if the bundle is going to be present. + return BOOTSTRAPPER_CACHE_TYPE_FORCE == pPackage->cacheType && BOOTSTRAPPER_ACTION_UNINSTALL < pPlan->action; +} + +static void CacheActionLog( + __in DWORD iAction, + __in BURN_CACHE_ACTION* pAction, + __in BOOL fRollback + ) +{ + LPCWSTR wzBase = fRollback ? L" Rollback cache" : L" Cache"; + switch (pAction->type) + { + case BURN_CACHE_ACTION_TYPE_CHECKPOINT: + LogStringLine(PlanDumpLevel, "%ls action[%u]: CHECKPOINT id: %u", wzBase, iAction, pAction->checkpoint.dwId); + break; + + case BURN_CACHE_ACTION_TYPE_LAYOUT_BUNDLE: + LogStringLine(PlanDumpLevel, "%ls action[%u]: LAYOUT_BUNDLE working path: %ls, exe name: %ls", wzBase, iAction, pAction->bundleLayout.sczUnverifiedPath, pAction->bundleLayout.sczExecutableName); + break; + + case BURN_CACHE_ACTION_TYPE_CONTAINER: + LogStringLine(PlanDumpLevel, "%ls action[%u]: CONTAINER container id: %ls, working path: %ls", wzBase, iAction, pAction->container.pContainer->sczId, pAction->container.pContainer->sczUnverifiedPath); + break; + + case BURN_CACHE_ACTION_TYPE_PACKAGE: + LogStringLine(PlanDumpLevel, "%ls action[%u]: PACKAGE id: %ls", wzBase, iAction, pAction->package.pPackage->sczId); + break; + + case BURN_CACHE_ACTION_TYPE_ROLLBACK_PACKAGE: + LogStringLine(PlanDumpLevel, "%ls action[%u]: ROLLBACK_PACKAGE id: %ls", wzBase, iAction, pAction->rollbackPackage.pPackage->sczId); + break; + + case BURN_CACHE_ACTION_TYPE_SIGNAL_SYNCPOINT: + LogStringLine(PlanDumpLevel, "%ls action[%u]: SIGNAL_SYNCPOINT event handle: 0x%p", wzBase, iAction, pAction->syncpoint.hEvent); + break; + + default: + AssertSz(FALSE, "Unknown cache action type."); + break; + } +} + +static void ExecuteActionLog( + __in DWORD iAction, + __in BURN_EXECUTE_ACTION* pAction, + __in BOOL fRollback + ) +{ + LPCWSTR wzBase = fRollback ? L" Rollback" : L" Execute"; + switch (pAction->type) + { + case BURN_EXECUTE_ACTION_TYPE_CHECKPOINT: + LogStringLine(PlanDumpLevel, "%ls action[%u]: CHECKPOINT id: %u, msi transaction id: %ls", wzBase, iAction, pAction->checkpoint.dwId, pAction->checkpoint.pActiveRollbackBoundary && pAction->checkpoint.pActiveRollbackBoundary->fTransaction ? pAction->checkpoint.pActiveRollbackBoundary->sczId : L"(none)"); + break; + + case BURN_EXECUTE_ACTION_TYPE_PACKAGE_PROVIDER: + LogStringLine(PlanDumpLevel, "%ls action[%u]: PACKAGE_PROVIDER package id: %ls, action: %hs", wzBase, iAction, pAction->packageProvider.pPackage->sczId, LoggingDependencyActionToString(pAction->packageProvider.action)); + break; + + case BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY: + LogStringLine(PlanDumpLevel, "%ls action[%u]: PACKAGE_DEPENDENCY package id: %ls, bundle provider key: %ls, action: %hs", wzBase, iAction, pAction->packageDependency.pPackage->sczId, pAction->packageDependency.sczBundleProviderKey, LoggingDependencyActionToString(pAction->packageDependency.action)); + break; + + case BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE: + LogStringLine(PlanDumpLevel, "%ls action[%u]: EXE_PACKAGE package id: %ls, action: %hs, ignore dependencies: %ls", wzBase, iAction, pAction->exePackage.pPackage->sczId, LoggingActionStateToString(pAction->exePackage.action), pAction->exePackage.sczIgnoreDependencies); + break; + + case BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE: + LogStringLine(PlanDumpLevel, "%ls action[%u]: MSI_PACKAGE package id: %ls, action: %hs, action msi property: %ls, ui level: %u, disable externaluihandler: %ls, log path: %ls, logging attrib: %u", wzBase, iAction, pAction->msiPackage.pPackage->sczId, LoggingActionStateToString(pAction->msiPackage.action), LoggingBurnMsiPropertyToString(pAction->msiPackage.actionMsiProperty), pAction->msiPackage.uiLevel, pAction->msiPackage.fDisableExternalUiHandler ? L"yes" : L"no", pAction->msiPackage.sczLogPath, pAction->msiPackage.dwLoggingAttributes); + for (DWORD j = 0; j < pAction->msiPackage.pPackage->Msi.cSlipstreamMspPackages; ++j) + { + const BURN_SLIPSTREAM_MSP* pSlipstreamMsp = pAction->msiPackage.pPackage->Msi.rgSlipstreamMsps + j; + LogStringLine(PlanDumpLevel, " Patch[%u]: msp package id: %ls, action: %hs", j, pSlipstreamMsp->pMspPackage->sczId, LoggingActionStateToString(fRollback ? pSlipstreamMsp->rollback : pSlipstreamMsp->execute)); + } + break; + + case BURN_EXECUTE_ACTION_TYPE_MSP_TARGET: + LogStringLine(PlanDumpLevel, "%ls action[%u]: MSP_TARGET package id: %ls, action: %hs, target product code: %ls, target per-machine: %ls, action msi property: %ls, ui level: %u, disable externaluihandler: %ls, log path: %ls", wzBase, iAction, pAction->mspTarget.pPackage->sczId, LoggingActionStateToString(pAction->mspTarget.action), pAction->mspTarget.sczTargetProductCode, pAction->mspTarget.fPerMachineTarget ? L"yes" : L"no", LoggingBurnMsiPropertyToString(pAction->mspTarget.actionMsiProperty), pAction->mspTarget.uiLevel, pAction->mspTarget.fDisableExternalUiHandler ? L"yes" : L"no", pAction->mspTarget.sczLogPath); + for (DWORD j = 0; j < pAction->mspTarget.cOrderedPatches; ++j) + { + LogStringLine(PlanDumpLevel, " Patch[%u]: order: %u, msp package id: %ls", j, pAction->mspTarget.rgOrderedPatches[j].pTargetProduct->dwOrder, pAction->mspTarget.rgOrderedPatches[j].pPackage->sczId); + } + break; + + case BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE: + LogStringLine(PlanDumpLevel, "%ls action[%u]: MSU_PACKAGE package id: %ls, action: %hs, log path: %ls", wzBase, iAction, pAction->msuPackage.pPackage->sczId, LoggingActionStateToString(pAction->msuPackage.action), pAction->msuPackage.sczLogPath); + break; + + case BURN_EXECUTE_ACTION_TYPE_ROLLBACK_BOUNDARY: + LogStringLine(PlanDumpLevel, "%ls action[%u]: ROLLBACK_BOUNDARY id: %ls, vital: %ls", wzBase, iAction, pAction->rollbackBoundary.pRollbackBoundary->sczId, pAction->rollbackBoundary.pRollbackBoundary->fVital ? L"yes" : L"no"); + break; + + case BURN_EXECUTE_ACTION_TYPE_WAIT_SYNCPOINT: + LogStringLine(PlanDumpLevel, "%ls action[%u]: WAIT_SYNCPOINT event handle: 0x%p", wzBase, iAction, pAction->syncpoint.hEvent); + break; + + case BURN_EXECUTE_ACTION_TYPE_UNCACHE_PACKAGE: + LogStringLine(PlanDumpLevel, "%ls action[%u]: UNCACHE_PACKAGE id: %ls", wzBase, iAction, pAction->uncachePackage.pPackage->sczId); + break; + + case BURN_EXECUTE_ACTION_TYPE_BEGIN_MSI_TRANSACTION: + LogStringLine(PlanDumpLevel, "%ls action[%u]: BEGIN_MSI_TRANSACTION id: %ls", wzBase, iAction, pAction->msiTransaction.pRollbackBoundary->sczId); + break; + + case BURN_EXECUTE_ACTION_TYPE_COMMIT_MSI_TRANSACTION: + LogStringLine(PlanDumpLevel, "%ls action[%u]: COMMIT_MSI_TRANSACTION id: %ls", wzBase, iAction, pAction->msiTransaction.pRollbackBoundary->sczId); + break; + + default: + AssertSz(FALSE, "Unknown execute action type."); + break; + } + + if (pAction->fDeleted) + { + LogStringLine(PlanDumpLevel, " (deleted action)"); + } +} + +extern "C" void PlanDump( + __in BURN_PLAN* pPlan + ) +{ + LogStringLine(PlanDumpLevel, "--- Begin plan dump ---"); + + LogStringLine(PlanDumpLevel, "Plan action: %hs", LoggingBurnActionToString(pPlan->action)); + LogStringLine(PlanDumpLevel, " per-machine: %hs", LoggingTrueFalseToString(pPlan->fPerMachine)); + LogStringLine(PlanDumpLevel, " disable-rollback: %hs", LoggingTrueFalseToString(pPlan->fDisableRollback)); + LogStringLine(PlanDumpLevel, " estimated size: %llu", pPlan->qwEstimatedSize); + if (pPlan->sczLayoutDirectory) + { + LogStringLine(PlanDumpLevel, " layout directory: %ls", pPlan->sczLayoutDirectory); + } + + LogStringLine(PlanDumpLevel, "Plan cache size: %llu", pPlan->qwCacheSizeTotal); + for (DWORD i = 0; i < pPlan->cCacheActions; ++i) + { + CacheActionLog(i, pPlan->rgCacheActions + i, FALSE); + } + + for (DWORD i = 0; i < pPlan->cRollbackCacheActions; ++i) + { + CacheActionLog(i, pPlan->rgRollbackCacheActions + i, TRUE); + } + + LogStringLine(PlanDumpLevel, "Plan execute package count: %u", pPlan->cExecutePackagesTotal); + LogStringLine(PlanDumpLevel, " overall progress ticks: %u", pPlan->cOverallProgressTicksTotal); + for (DWORD i = 0; i < pPlan->cExecuteActions; ++i) + { + ExecuteActionLog(i, pPlan->rgExecuteActions + i, FALSE); + } + + for (DWORD i = 0; i < pPlan->cRollbackActions; ++i) + { + ExecuteActionLog(i, pPlan->rgRollbackActions + i, TRUE); + } + + for (DWORD i = 0; i < pPlan->cCleanActions; ++i) + { + LogStringLine(PlanDumpLevel, " Clean action[%u]: CLEAN_PACKAGE package id: %ls", i, pPlan->rgCleanActions[i].pPackage->sczId); + } + + for (DWORD i = 0; i < pPlan->cPlannedProviders; ++i) + { + LogStringLine(PlanDumpLevel, " Dependency action[%u]: PLANNED_PROVIDER key: %ls, name: %ls", i, pPlan->rgPlannedProviders[i].sczKey, pPlan->rgPlannedProviders[i].sczName); + } + + LogStringLine(PlanDumpLevel, "--- End plan dump ---"); +} diff --git a/src/burn/engine/plan.h b/src/burn/engine/plan.h new file mode 100644 index 00000000..00ab5516 --- /dev/null +++ b/src/burn/engine/plan.h @@ -0,0 +1,456 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + +const DWORD BURN_PLAN_INVALID_ACTION_INDEX = 0x80000000; + +enum BURN_REGISTRATION_ACTION_OPERATIONS +{ + BURN_REGISTRATION_ACTION_OPERATIONS_NONE = 0x0, + BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE = 0x1, + BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION = 0x2, + BURN_REGISTRATION_ACTION_OPERATIONS_UPDATE_SIZE = 0x4, +}; + +enum BURN_DEPENDENCY_REGISTRATION_ACTION +{ + BURN_DEPENDENCY_REGISTRATION_ACTION_NONE, + BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER, + BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER, +}; + +enum BURN_DEPENDENT_REGISTRATION_ACTION_TYPE +{ + BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_NONE, + BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_REGISTER, + BURN_DEPENDENT_REGISTRATION_ACTION_TYPE_UNREGISTER, +}; + +enum BURN_CACHE_ACTION_TYPE +{ + BURN_CACHE_ACTION_TYPE_NONE, + BURN_CACHE_ACTION_TYPE_CHECKPOINT, + BURN_CACHE_ACTION_TYPE_LAYOUT_BUNDLE, + BURN_CACHE_ACTION_TYPE_PACKAGE, + BURN_CACHE_ACTION_TYPE_ROLLBACK_PACKAGE, + BURN_CACHE_ACTION_TYPE_SIGNAL_SYNCPOINT, + BURN_CACHE_ACTION_TYPE_CONTAINER, +}; + +enum BURN_EXECUTE_ACTION_TYPE +{ + BURN_EXECUTE_ACTION_TYPE_NONE, + BURN_EXECUTE_ACTION_TYPE_CHECKPOINT, + BURN_EXECUTE_ACTION_TYPE_WAIT_SYNCPOINT, + BURN_EXECUTE_ACTION_TYPE_UNCACHE_PACKAGE, + BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE, + BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE, + BURN_EXECUTE_ACTION_TYPE_MSP_TARGET, + BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE, + BURN_EXECUTE_ACTION_TYPE_PACKAGE_PROVIDER, + BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY, + BURN_EXECUTE_ACTION_TYPE_ROLLBACK_BOUNDARY, + BURN_EXECUTE_ACTION_TYPE_BEGIN_MSI_TRANSACTION, + BURN_EXECUTE_ACTION_TYPE_COMMIT_MSI_TRANSACTION, +}; + +enum BURN_CLEAN_ACTION_TYPE +{ + BURN_CLEAN_ACTION_TYPE_NONE, + BURN_CLEAN_ACTION_TYPE_BUNDLE, + BURN_CLEAN_ACTION_TYPE_PACKAGE, +}; + + +// structs + +typedef struct _BURN_DEPENDENT_REGISTRATION_ACTION +{ + BURN_DEPENDENT_REGISTRATION_ACTION_TYPE type; + LPWSTR sczBundleId; + LPWSTR sczDependentProviderKey; +} BURN_DEPENDENT_REGISTRATION_ACTION; + +typedef struct _BURN_CACHE_CONTAINER_PROGRESS +{ + LPWSTR wzId; + DWORD iIndex; + BOOL fCachedDuringApply; + BURN_CONTAINER* pContainer; +} BURN_CACHE_CONTAINER_PROGRESS; + +typedef struct _BURN_CACHE_PAYLOAD_PROGRESS +{ + LPWSTR wzId; + DWORD iIndex; + BOOL fCachedDuringApply; + BURN_PAYLOAD* pPayload; +} BURN_CACHE_PAYLOAD_PROGRESS; + +typedef struct _BURN_CACHE_ACTION +{ + BURN_CACHE_ACTION_TYPE type; + union + { + struct + { + DWORD dwId; + } checkpoint; + struct + { + LPWSTR sczExecutableName; + LPWSTR sczUnverifiedPath; + DWORD64 qwBundleSize; + BURN_PAYLOAD_GROUP* pPayloadGroup; + } bundleLayout; + struct + { + BURN_PACKAGE* pPackage; + } package; + struct + { + BURN_PACKAGE* pPackage; + } rollbackPackage; + struct + { + HANDLE hEvent; + } syncpoint; + struct + { + BURN_CONTAINER* pContainer; + } container; + }; +} BURN_CACHE_ACTION; + +typedef struct _BURN_ORDERED_PATCHES +{ + BURN_PACKAGE* pPackage; + + BURN_MSPTARGETPRODUCT* pTargetProduct; // only valid in the unelevated engine. +} BURN_ORDERED_PATCHES; + +typedef struct _BURN_EXECUTE_ACTION_CHECKPOINT +{ + DWORD dwId; + BURN_ROLLBACK_BOUNDARY* pActiveRollbackBoundary; +} BURN_EXECUTE_ACTION_CHECKPOINT; + +typedef struct _BURN_EXECUTE_ACTION +{ + BURN_EXECUTE_ACTION_TYPE type; + BOOL fDeleted; // used to skip an action after it was planned since deleting actions out of the plan is too hard. + union + { + BURN_EXECUTE_ACTION_CHECKPOINT checkpoint; + struct + { + HANDLE hEvent; + } syncpoint; + struct + { + BURN_PACKAGE* pPackage; + } uncachePackage; + struct + { + BURN_PACKAGE* pPackage; + BOOL fFireAndForget; + BOOTSTRAPPER_ACTION_STATE action; + LPWSTR sczIgnoreDependencies; + LPWSTR sczAncestors; + } exePackage; + struct + { + BURN_PACKAGE* pPackage; + LPWSTR sczLogPath; + DWORD dwLoggingAttributes; + BURN_MSI_PROPERTY actionMsiProperty; + INSTALLUILEVEL uiLevel; + BOOL fDisableExternalUiHandler; + BOOTSTRAPPER_ACTION_STATE action; + + BOOTSTRAPPER_FEATURE_ACTION* rgFeatures; + } msiPackage; + struct + { + BURN_PACKAGE* pPackage; + LPWSTR sczTargetProductCode; + BURN_PACKAGE* pChainedTargetPackage; + BOOL fSlipstream; + BOOL fPerMachineTarget; + LPWSTR sczLogPath; + BURN_MSI_PROPERTY actionMsiProperty; + INSTALLUILEVEL uiLevel; + BOOL fDisableExternalUiHandler; + BOOTSTRAPPER_ACTION_STATE action; + + BURN_ORDERED_PATCHES* rgOrderedPatches; + DWORD cOrderedPatches; + } mspTarget; + struct + { + BURN_PACKAGE* pPackage; + LPWSTR sczLogPath; + BOOTSTRAPPER_ACTION_STATE action; + } msuPackage; + struct + { + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary; + } rollbackBoundary; + struct + { + BURN_PACKAGE* pPackage; + BURN_DEPENDENCY_ACTION action; + } packageProvider; + struct + { + BURN_PACKAGE* pPackage; + LPWSTR sczBundleProviderKey; + BURN_DEPENDENCY_ACTION action; + } packageDependency; + struct + { + BURN_ROLLBACK_BOUNDARY* pRollbackBoundary; + } msiTransaction; + }; +} BURN_EXECUTE_ACTION; + +typedef struct _BURN_CLEAN_ACTION +{ + BURN_PACKAGE* pPackage; +} BURN_CLEAN_ACTION; + +typedef struct _BURN_PLAN +{ + BOOTSTRAPPER_ACTION action; + BURN_PAYLOADS* pPayloads; // points directly into parent the ENGINE_STATE. + LPWSTR wzBundleId; // points directly into parent the ENGINE_STATE. + LPWSTR wzBundleProviderKey; // points directly into parent the ENGINE_STATE. + BOOL fPerMachine; + BOOL fCanAffectMachineState; + DWORD dwRegistrationOperations; + BOOL fDisallowRemoval; + BOOL fDisableRollback; + BOOL fAffectedMachineState; + BOOL fIgnoreAllDependents; + LPWSTR sczLayoutDirectory; + + DWORD64 qwCacheSizeTotal; + + DWORD64 qwEstimatedSize; + + DWORD cExecutePackagesTotal; + DWORD cOverallProgressTicksTotal; + + BOOL fEnabledForwardCompatibleBundle; + BURN_PACKAGE forwardCompatibleBundle; + + BURN_DEPENDENCY_REGISTRATION_ACTION dependencyRegistrationAction; + + BURN_DEPENDENT_REGISTRATION_ACTION* rgRegistrationActions; + DWORD cRegistrationActions; + + BURN_DEPENDENT_REGISTRATION_ACTION* rgRollbackRegistrationActions; + DWORD cRollbackRegistrationActions; + + BURN_CACHE_ACTION* rgCacheActions; + DWORD cCacheActions; + + BURN_CACHE_ACTION* rgRollbackCacheActions; + DWORD cRollbackCacheActions; + + BURN_EXECUTE_ACTION* rgExecuteActions; + DWORD cExecuteActions; + + BURN_EXECUTE_ACTION* rgRollbackActions; + DWORD cRollbackActions; + + BURN_CLEAN_ACTION* rgCleanActions; + DWORD cCleanActions; + + DEPENDENCY* rgPlannedProviders; + UINT cPlannedProviders; + + BURN_CACHE_CONTAINER_PROGRESS* rgContainerProgress; + DWORD cContainerProgress; + STRINGDICT_HANDLE shContainerProgress; + + BURN_CACHE_PAYLOAD_PROGRESS* rgPayloadProgress; + DWORD cPayloadProgress; + STRINGDICT_HANDLE shPayloadProgress; + + DWORD dwNextCheckpointId; // for plan internal use + BURN_ROLLBACK_BOUNDARY* pActiveRollbackBoundary; // for plan internal use +} BURN_PLAN; + + +// functions + +void PlanReset( + __in BURN_PLAN* pPlan, + __in BURN_CONTAINERS* pContainers, + __in BURN_PACKAGES* pPackages, + __in BURN_PAYLOAD_GROUP* pLayoutPayloads + ); +void PlanUninitializeExecuteAction( + __in BURN_EXECUTE_ACTION* pExecuteAction + ); +HRESULT PlanSetVariables( + __in BOOTSTRAPPER_ACTION action, + __in BURN_VARIABLES* pVariables + ); +HRESULT PlanDefaultPackageRequestState( + __in BURN_PACKAGE_TYPE packageType, + __in BOOTSTRAPPER_PACKAGE_STATE currentState, + __in BOOL fPermanent, + __in BOOTSTRAPPER_ACTION action, + __in BOOTSTRAPPER_PACKAGE_CONDITION_RESULT installCondition, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __out BOOTSTRAPPER_REQUEST_STATE* pRequestState + ); +HRESULT PlanLayoutBundle( + __in BURN_PLAN* pPlan, + __in_z LPCWSTR wzExecutableName, + __in DWORD64 qwBundleSize, + __in BURN_VARIABLES* pVariables, + __in BURN_PAYLOAD_GROUP* pLayoutPayloads + ); +HRESULT PlanForwardCompatibleBundles( + __in BURN_USER_EXPERIENCE* pUX, + __in BOOTSTRAPPER_COMMAND* pCommand, + __in BURN_PLAN* pPlan, + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_ACTION action + ); +HRESULT PlanPackages( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PACKAGES* pPackages, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ); +HRESULT PlanRegistration( + __in BURN_PLAN* pPlan, + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_RESUME_TYPE resumeType, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __inout BOOL* pfContinuePlanning + ); +HRESULT PlanPassThroughBundle( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ); +HRESULT PlanUpdateBundle( + __in BURN_USER_EXPERIENCE* pUX, + __in BURN_PACKAGE* pPackage, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in BOOTSTRAPPER_DISPLAY display, + __in BOOTSTRAPPER_RELATION_TYPE relationType + ); +HRESULT PlanLayoutContainer( + __in BURN_PLAN* pPlan, + __in BURN_CONTAINER* pContainer + ); +HRESULT PlanLayoutPackage( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ); +HRESULT PlanExecutePackage( + __in BOOL fPerMachine, + __in BOOTSTRAPPER_DISPLAY display, + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __inout HANDLE* phSyncpointEvent + ); +HRESULT PlanDefaultRelatedBundleRequestState( + __in BOOTSTRAPPER_RELATION_TYPE commandRelationType, + __in BOOTSTRAPPER_RELATION_TYPE relatedBundleRelationType, + __in BOOTSTRAPPER_ACTION action, + __in VERUTIL_VERSION* pRegistrationVersion, + __in VERUTIL_VERSION* pRelatedBundleVersion, + __inout BOOTSTRAPPER_REQUEST_STATE* pRequestState + ); +HRESULT PlanRelatedBundlesBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BURN_PLAN* pPlan + ); +HRESULT PlanRelatedBundlesComplete( + __in BURN_REGISTRATION* pRegistration, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables, + __in DWORD dwExecuteActionEarlyIndex + ); +HRESULT PlanFinalizeActions( + __in BURN_PLAN* pPlan + ); +HRESULT PlanCleanPackage( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage + ); +HRESULT PlanExecuteCacheSyncAndRollback( + __in BURN_PLAN* pPlan, + __in BURN_PACKAGE* pPackage, + __in HANDLE hCacheEvent + ); +HRESULT PlanExecuteCheckpoint( + __in BURN_PLAN* pPlan + ); +HRESULT PlanInsertExecuteAction( + __in DWORD dwIndex, + __in BURN_PLAN* pPlan, + __out BURN_EXECUTE_ACTION** ppExecuteAction + ); +HRESULT PlanInsertRollbackAction( + __in DWORD dwIndex, + __in BURN_PLAN* pPlan, + __out BURN_EXECUTE_ACTION** ppRollbackAction + ); +HRESULT PlanAppendExecuteAction( + __in BURN_PLAN* pPlan, + __out BURN_EXECUTE_ACTION** ppExecuteAction + ); +HRESULT PlanAppendRollbackAction( + __in BURN_PLAN* pPlan, + __out BURN_EXECUTE_ACTION** ppExecuteAction + ); +HRESULT PlanRollbackBoundaryBegin( + __in BURN_PLAN* pPlan, + __in BURN_ROLLBACK_BOUNDARY* pRollbackBoundary + ); +HRESULT PlanRollbackBoundaryComplete( + __in BURN_PLAN* pPlan + ); +HRESULT PlanSetResumeCommand( + __in BURN_REGISTRATION* pRegistration, + __in BOOTSTRAPPER_ACTION action, + __in BOOTSTRAPPER_COMMAND* pCommand, + __in BURN_LOGGING* pLog + ); +void PlanDump( + __in BURN_PLAN* pPlan + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/platform.cpp b/src/burn/engine/platform.cpp new file mode 100644 index 00000000..9469ff49 --- /dev/null +++ b/src/burn/engine/platform.cpp @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// variables + +PFN_INITIATESYSTEMSHUTDOWNEXW vpfnInitiateSystemShutdownExW; + + +// function definitions + +extern "C" void PlatformInitialize() +{ + vpfnInitiateSystemShutdownExW = ::InitiateSystemShutdownExW; +} diff --git a/src/burn/engine/platform.h b/src/burn/engine/platform.h new file mode 100644 index 00000000..3681f248 --- /dev/null +++ b/src/burn/engine/platform.h @@ -0,0 +1,34 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// typedefs + +typedef BOOL (WINAPI *PFN_INITIATESYSTEMSHUTDOWNEXW)( + __in_opt LPWSTR lpMachineName, + __in_opt LPWSTR lpMessage, + __in DWORD dwTimeout, + __in BOOL bForceAppsClosed, + __in BOOL bRebootAfterShutdown, + __in DWORD dwReason + ); + + +// variable declarations + +extern PFN_INITIATESYSTEMSHUTDOWNEXW vpfnInitiateSystemShutdownExW; + + +// function declarations + +void PlatformInitialize(); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/precomp.cpp b/src/burn/engine/precomp.cpp new file mode 100644 index 00000000..37664a1c --- /dev/null +++ b/src/burn/engine/precomp.cpp @@ -0,0 +1,3 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" diff --git a/src/burn/engine/precomp.h b/src/burn/engine/precomp.h new file mode 100644 index 00000000..11b594da --- /dev/null +++ b/src/burn/engine/precomp.h @@ -0,0 +1,102 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "BootstrapperEngine.h" +#include "BootstrapperApplication.h" +#include "BundleExtensionEngine.h" +#include "BundleExtension.h" + +#include "platform.h" +#include "variant.h" +#include "variable.h" +#include "condition.h" +#include "section.h" +#include "approvedexe.h" +#include "container.h" +#include "payload.h" +#include "cabextract.h" +#include "burnextension.h" +#include "search.h" +#include "userexperience.h" +#include "package.h" +#include "update.h" +#include "pseudobundle.h" +#include "registration.h" +#include "relatedbundle.h" +#include "detect.h" +#include "plan.h" +#include "logging.h" +#include "pipe.h" +#include "core.h" +#include "cache.h" +#include "apply.h" +#include "exeengine.h" +#include "msiengine.h" +#include "mspengine.h" +#include "msuengine.h" +#include "dependency.h" +#include "elevation.h" +#include "embedded.h" +#include "manifest.h" +#include "splashscreen.h" +#include "uithread.h" +#include "netfxchainer.h" + +#include "externalengine.h" +#include "EngineForApplication.h" +#include "EngineForExtension.h" +#include "engine.messages.h" diff --git a/src/burn/engine/pseudobundle.cpp b/src/burn/engine/pseudobundle.cpp new file mode 100644 index 00000000..180cc621 --- /dev/null +++ b/src/burn/engine/pseudobundle.cpp @@ -0,0 +1,241 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +extern "C" HRESULT PseudoBundleInitialize( + __in DWORD64 qwEngineVersion, + __in BURN_PACKAGE* pPackage, + __in BOOL fPerMachine, + __in_z LPCWSTR wzId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BOOTSTRAPPER_PACKAGE_STATE state, + __in BOOL fCached, + __in_z LPCWSTR wzFilePath, + __in_z LPCWSTR wzLocalSource, + __in_z_opt LPCWSTR wzDownloadSource, + __in DWORD64 qwSize, + __in BOOL fVital, + __in_z_opt LPCWSTR wzInstallArguments, + __in_z_opt LPCWSTR wzRepairArguments, + __in_z_opt LPCWSTR wzUninstallArguments, + __in_opt BURN_DEPENDENCY_PROVIDER* pDependencyProvider, + __in_opt const BYTE* pbHash, + __in const DWORD cbHash + ) +{ + HRESULT hr = S_OK; + LPWSTR sczRelationTypeCommandLineSwitch = NULL; + BURN_PAYLOAD* pPayload = NULL; + + LPCWSTR wzRelationTypeCommandLine = CoreRelationTypeToCommandLineString(relationType); + if (wzRelationTypeCommandLine) + { + hr = StrAllocFormatted(&sczRelationTypeCommandLineSwitch, L" -%ls", wzRelationTypeCommandLine); + } + + // Initialize the single payload, and fill out all the necessary fields + pPackage->payloads.rgItems = (BURN_PAYLOAD_GROUP_ITEM*)MemAlloc(sizeof(BURN_PAYLOAD_GROUP_ITEM), TRUE); + ExitOnNull(pPackage->payloads.rgItems, hr, E_OUTOFMEMORY, "Failed to allocate space for burn payload group inside of related bundle struct"); + pPackage->payloads.cItems = 1; + + pPayload = (BURN_PAYLOAD*)MemAlloc(sizeof(BURN_PAYLOAD), TRUE); + ExitOnNull(pPayload, hr, E_OUTOFMEMORY, "Failed to allocate space for burn payload inside of related bundle struct"); + pPackage->payloads.rgItems[0].pPayload = pPayload; + pPayload->packaging = BURN_PAYLOAD_PACKAGING_EXTERNAL; + pPayload->qwFileSize = qwSize; + + hr = StrAllocString(&pPayload->sczKey, wzId, 0); + ExitOnFailure(hr, "Failed to copy key for pseudo bundle payload."); + + hr = StrAllocString(&pPayload->sczFilePath, wzFilePath, 0); + ExitOnFailure(hr, "Failed to copy filename for pseudo bundle."); + + hr = StrAllocString(&pPayload->sczSourcePath, wzLocalSource, 0); + ExitOnFailure(hr, "Failed to copy local source path for pseudo bundle."); + + if (wzDownloadSource && *wzDownloadSource) + { + hr = StrAllocString(&pPayload->downloadSource.sczUrl, wzDownloadSource, 0); + ExitOnFailure(hr, "Failed to copy download source for pseudo bundle."); + } + + if (pbHash) + { + pPayload->pbHash = static_cast(MemAlloc(cbHash, FALSE)); + ExitOnNull(pPayload->pbHash, hr, E_OUTOFMEMORY, "Failed to allocate memory for pseudo bundle payload hash."); + + pPayload->cbHash = cbHash; + memcpy_s(pPayload->pbHash, pPayload->cbHash, pbHash, cbHash); + } + + pPackage->Exe.fPseudoBundle = TRUE; + + pPackage->type = BURN_PACKAGE_TYPE_EXE; + pPackage->fPerMachine = fPerMachine; + pPackage->currentState = state; + pPackage->fCached = fCached; + pPackage->qwInstallSize = qwSize; + pPackage->qwSize = qwSize; + pPackage->fVital = fVital; + + hr = StrAllocString(&pPackage->sczId, wzId, 0); + ExitOnFailure(hr, "Failed to copy key for pseudo bundle."); + + hr = StrAllocString(&pPackage->sczCacheId, wzId, 0); + ExitOnFailure(hr, "Failed to copy cache id for pseudo bundle."); + + // If we are a self updating bundle, we don't have to have Install arguments. + if (wzInstallArguments) + { + hr = StrAllocString(&pPackage->Exe.sczInstallArguments, wzInstallArguments, 0); + ExitOnFailure(hr, "Failed to copy install arguments for related bundle package"); + } + + if (sczRelationTypeCommandLineSwitch) + { + hr = StrAllocConcat(&pPackage->Exe.sczInstallArguments, sczRelationTypeCommandLineSwitch, 0); + ExitOnFailure(hr, "Failed to append relation type to install arguments for related bundle package"); + } + + if (wzRepairArguments) + { + hr = StrAllocString(&pPackage->Exe.sczRepairArguments, wzRepairArguments, 0); + ExitOnFailure(hr, "Failed to copy repair arguments for related bundle package"); + + if (sczRelationTypeCommandLineSwitch) + { + hr = StrAllocConcat(&pPackage->Exe.sczRepairArguments, sczRelationTypeCommandLineSwitch, 0); + ExitOnFailure(hr, "Failed to append relation type to repair arguments for related bundle package"); + } + + pPackage->Exe.fRepairable = TRUE; + } + + if (wzUninstallArguments) + { + hr = StrAllocString(&pPackage->Exe.sczUninstallArguments, wzUninstallArguments, 0); + ExitOnFailure(hr, "Failed to copy uninstall arguments for related bundle package"); + + if (sczRelationTypeCommandLineSwitch) + { + hr = StrAllocConcat(&pPackage->Exe.sczUninstallArguments, sczRelationTypeCommandLineSwitch, 0); + ExitOnFailure(hr, "Failed to append relation type to uninstall arguments for related bundle package"); + } + + pPackage->fUninstallable = TRUE; + } + + // Only support progress from engines that are compatible (aka: version greater than or equal to last protocol breaking change *and* versions that are older or the same as this engine). + pPackage->Exe.protocol = (FILEMAKEVERSION(3, 6, 2221, 0) <= qwEngineVersion && qwEngineVersion <= FILEMAKEVERSION(rmj, rmm, rup, rpr)) ? BURN_EXE_PROTOCOL_TYPE_BURN : BURN_EXE_PROTOCOL_TYPE_NONE; + + // All versions of Burn past v3.9 RTM support suppressing ancestors. + pPackage->Exe.fSupportsAncestors = FILEMAKEVERSION(3, 9, 1006, 0) <= qwEngineVersion; + + if (pDependencyProvider) + { + pPackage->rgDependencyProviders = (BURN_DEPENDENCY_PROVIDER*)MemAlloc(sizeof(BURN_DEPENDENCY_PROVIDER), TRUE); + ExitOnNull(pPackage->rgDependencyProviders, hr, E_OUTOFMEMORY, "Failed to allocate memory for dependency providers."); + pPackage->cDependencyProviders = 1; + + pPackage->rgDependencyProviders[0].fImported = pDependencyProvider->fImported; + + hr = StrAllocString(&pPackage->rgDependencyProviders[0].sczKey, pDependencyProvider->sczKey, 0); + ExitOnFailure(hr, "Failed to copy key for pseudo bundle."); + + hr = StrAllocString(&pPackage->rgDependencyProviders[0].sczVersion, pDependencyProvider->sczVersion, 0); + ExitOnFailure(hr, "Failed to copy version for pseudo bundle."); + + hr = StrAllocString(&pPackage->rgDependencyProviders[0].sczDisplayName, pDependencyProvider->sczDisplayName, 0); + ExitOnFailure(hr, "Failed to copy display name for pseudo bundle."); + } + +LExit: + ReleaseStr(sczRelationTypeCommandLineSwitch); + + return hr; +} + +extern "C" HRESULT PseudoBundleInitializePassthrough( + __in BURN_PACKAGE* pPassthroughPackage, + __in BOOTSTRAPPER_COMMAND* pCommand, + __in_z_opt LPCWSTR wzAppendLogPath, + __in_z_opt LPCWSTR wzActiveParent, + __in_z_opt LPCWSTR wzAncestors, + __in BURN_PACKAGE* pPackage + ) +{ + Assert(BURN_PACKAGE_TYPE_EXE == pPackage->type); + + HRESULT hr = S_OK; + LPWSTR sczArguments = NULL; + + // Initialize the payloads, and copy the necessary fields. + pPassthroughPackage->payloads.rgItems = (BURN_PAYLOAD_GROUP_ITEM*)MemAlloc(sizeof(BURN_PAYLOAD_GROUP_ITEM) * pPackage->payloads.cItems, TRUE); + ExitOnNull(pPassthroughPackage->payloads.rgItems, hr, E_OUTOFMEMORY, "Failed to allocate space for burn package payload inside of passthrough bundle."); + pPassthroughPackage->payloads.cItems = pPackage->payloads.cItems; + + for (DWORD iPayload = 0; iPayload < pPackage->payloads.cItems; ++iPayload) + { + pPassthroughPackage->payloads.rgItems[iPayload].pPayload = pPackage->payloads.rgItems[iPayload].pPayload; + } + + pPassthroughPackage->Exe.fPseudoBundle = TRUE; + + pPassthroughPackage->fPerMachine = FALSE; // passthrough bundles are always launched per-user. + pPassthroughPackage->type = pPackage->type; + pPassthroughPackage->currentState = pPackage->currentState; + pPassthroughPackage->fCached = pPackage->fCached; + pPassthroughPackage->qwInstallSize = pPackage->qwInstallSize; + pPassthroughPackage->qwSize = pPackage->qwSize; + pPassthroughPackage->fVital = pPackage->fVital; + + hr = StrAllocString(&pPassthroughPackage->sczId, pPackage->sczId, 0); + ExitOnFailure(hr, "Failed to copy key for passthrough pseudo bundle."); + + hr = StrAllocString(&pPassthroughPackage->sczCacheId, pPackage->sczCacheId, 0); + ExitOnFailure(hr, "Failed to copy cache id for passthrough pseudo bundle."); + + pPassthroughPackage->Exe.protocol = pPackage->Exe.protocol; + + // No matter the operation, we're passing the same command-line. That's what makes + // this a passthrough bundle. + hr = CoreRecreateCommandLine(&sczArguments, pCommand->action, pCommand->display, pCommand->restart, pCommand->relationType, TRUE, wzActiveParent, wzAncestors, wzAppendLogPath, pCommand->wzCommandLine); + ExitOnFailure(hr, "Failed to recreate command-line arguments."); + + hr = StrAllocString(&pPassthroughPackage->Exe.sczInstallArguments, sczArguments, 0); + ExitOnFailure(hr, "Failed to copy install arguments for passthrough bundle package"); + + hr = StrAllocString(&pPassthroughPackage->Exe.sczRepairArguments, sczArguments, 0); + ExitOnFailure(hr, "Failed to copy related arguments for passthrough bundle package"); + + pPassthroughPackage->Exe.fRepairable = TRUE; + + hr = StrAllocString(&pPassthroughPackage->Exe.sczUninstallArguments, sczArguments, 0); + ExitOnFailure(hr, "Failed to copy uninstall arguments for passthrough bundle package"); + + pPassthroughPackage->fUninstallable = TRUE; + + // TODO: consider bringing this back in the near future. + //if (pDependencyProvider) + //{ + // pPassthroughPackage->rgDependencyProviders = (BURN_DEPENDENCY_PROVIDER*)MemAlloc(sizeof(BURN_DEPENDENCY_PROVIDER), TRUE); + // ExitOnNull(pPassthroughPackage->rgDependencyProviders, hr, E_OUTOFMEMORY, "Failed to allocate memory for dependency providers."); + // pPassthroughPackage->cDependencyProviders = 1; + + // pPassthroughPackage->rgDependencyProviders[0].fImported = pDependencyProvider->fImported; + + // hr = StrAllocString(&pPassthroughPackage->rgDependencyProviders[0].sczKey, pDependencyProvider->sczKey, 0); + // ExitOnFailure(hr, "Failed to copy key for pseudo bundle."); + + // hr = StrAllocString(&pPassthroughPackage->rgDependencyProviders[0].sczVersion, pDependencyProvider->sczVersion, 0); + // ExitOnFailure(hr, "Failed to copy version for pseudo bundle."); + + // hr = StrAllocString(&pPassthroughPackage->rgDependencyProviders[0].sczDisplayName, pDependencyProvider->sczDisplayName, 0); + // ExitOnFailure(hr, "Failed to copy display name for pseudo bundle."); + //} + +LExit: + ReleaseStr(sczArguments); + return hr; +} diff --git a/src/burn/engine/pseudobundle.h b/src/burn/engine/pseudobundle.h new file mode 100644 index 00000000..9fb530aa --- /dev/null +++ b/src/burn/engine/pseudobundle.h @@ -0,0 +1,40 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + +HRESULT PseudoBundleInitialize( + __in DWORD64 qwEngineVersion, + __in BURN_PACKAGE* pPackage, + __in BOOL fPerMachine, + __in_z LPCWSTR wzId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in BOOTSTRAPPER_PACKAGE_STATE state, + __in BOOL fCached, + __in_z LPCWSTR wzFilePath, + __in_z LPCWSTR wzLocalSource, + __in_z_opt LPCWSTR wzDownloadSource, + __in DWORD64 qwSize, + __in BOOL fVital, + __in_z_opt LPCWSTR wzInstallArguments, + __in_z_opt LPCWSTR wzRepairArguments, + __in_z_opt LPCWSTR wzUninstallArguments, + __in_opt BURN_DEPENDENCY_PROVIDER* pDependencyProvider, + __in_opt const BYTE* pbHash, + __in const DWORD cbHash + ); +HRESULT PseudoBundleInitializePassthrough( + __in BURN_PACKAGE* pPassthroughPackage, + __in BOOTSTRAPPER_COMMAND* pCommand, + __in_z_opt LPCWSTR wzAppendLogPath, + __in_z_opt LPCWSTR wzActiveParent, + __in_z_opt LPCWSTR wzAncestors, + __in BURN_PACKAGE* pPackage + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/registration.cpp b/src/burn/engine/registration.cpp new file mode 100644 index 00000000..19da543c --- /dev/null +++ b/src/burn/engine/registration.cpp @@ -0,0 +1,1702 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// constants + +const LPCWSTR REGISTRY_RUN_KEY = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"; +const LPCWSTR REGISTRY_RUN_ONCE_KEY = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"; +const LPCWSTR REGISTRY_REBOOT_PENDING_FORMAT = L"%ls.RebootRequired"; +const LPCWSTR REGISTRY_BUNDLE_INSTALLED = L"Installed"; +const LPCWSTR REGISTRY_BUNDLE_DISPLAY_ICON = L"DisplayIcon"; +const LPCWSTR REGISTRY_BUNDLE_DISPLAY_VERSION = L"DisplayVersion"; +const LPCWSTR REGISTRY_BUNDLE_ESTIMATED_SIZE = L"EstimatedSize"; +const LPCWSTR REGISTRY_BUNDLE_PUBLISHER = L"Publisher"; +const LPCWSTR REGISTRY_BUNDLE_HELP_LINK = L"HelpLink"; +const LPCWSTR REGISTRY_BUNDLE_HELP_TELEPHONE = L"HelpTelephone"; +const LPCWSTR REGISTRY_BUNDLE_URL_INFO_ABOUT = L"URLInfoAbout"; +const LPCWSTR REGISTRY_BUNDLE_URL_UPDATE_INFO = L"URLUpdateInfo"; +const LPCWSTR REGISTRY_BUNDLE_PARENT_DISPLAY_NAME = L"ParentDisplayName"; +const LPCWSTR REGISTRY_BUNDLE_PARENT_KEY_NAME = L"ParentKeyName"; +const LPCWSTR REGISTRY_BUNDLE_COMMENTS = L"Comments"; +const LPCWSTR REGISTRY_BUNDLE_CONTACT = L"Contact"; +const LPCWSTR REGISTRY_BUNDLE_NO_MODIFY = L"NoModify"; +const LPCWSTR REGISTRY_BUNDLE_MODIFY_PATH = L"ModifyPath"; +const LPCWSTR REGISTRY_BUNDLE_NO_ELEVATE_ON_MODIFY = L"NoElevateOnModify"; +const LPCWSTR REGISTRY_BUNDLE_NO_REMOVE = L"NoRemove"; +const LPCWSTR REGISTRY_BUNDLE_SYSTEM_COMPONENT = L"SystemComponent"; +const LPCWSTR REGISTRY_BUNDLE_QUIET_UNINSTALL_STRING = L"QuietUninstallString"; +const LPCWSTR REGISTRY_BUNDLE_UNINSTALL_STRING = L"UninstallString"; +const LPCWSTR REGISTRY_BUNDLE_RESUME_COMMAND_LINE = L"BundleResumeCommandLine"; +const LPCWSTR REGISTRY_BUNDLE_VERSION_MAJOR = L"VersionMajor"; +const LPCWSTR REGISTRY_BUNDLE_VERSION_MINOR = L"VersionMinor"; +const LPCWSTR SWIDTAG_FOLDER = L"swidtag"; + +// internal function declarations + +static HRESULT ParseSoftwareTagsFromXml( + __in IXMLDOMNode* pixnRegistrationNode, + __out BURN_SOFTWARE_TAG** prgSoftwareTags, + __out DWORD* pcSoftwareTags + ); +static HRESULT SetPaths( + __in BURN_REGISTRATION* pRegistration + ); +static HRESULT GetBundleManufacturer( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __out LPWSTR* psczBundleManufacturer + ); +static HRESULT GetBundleName( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __out LPWSTR* psczBundleName + ); +static HRESULT UpdateResumeMode( + __in BURN_REGISTRATION* pRegistration, + __in HKEY hkRegistration, + __in BURN_RESUME_MODE resumeMode, + __in BOOL fRestartInitiated + ); +static HRESULT ParseRelatedCodes( + __in BURN_REGISTRATION* pRegistration, + __in IXMLDOMNode* pixnBundle + ); +static HRESULT FormatUpdateRegistrationKey( + __in BURN_REGISTRATION* pRegistration, + __out_z LPWSTR* psczKey + ); +static HRESULT WriteSoftwareTags( + __in BURN_VARIABLES* pVariables, + __in BURN_SOFTWARE_TAGS* pSoftwareTags + ); +static HRESULT RemoveSoftwareTags( + __in BURN_VARIABLES* pVariables, + __in BURN_SOFTWARE_TAGS* pSoftwareTags + ); +static HRESULT WriteUpdateRegistration( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables + ); +static HRESULT RemoveUpdateRegistration( + __in BURN_REGISTRATION* pRegistration + ); +static HRESULT RegWriteStringVariable( + __in HKEY hkKey, + __in BURN_VARIABLES* pVariables, + __in LPCWSTR wzVariable, + __in LPCWSTR wzName + ); +static HRESULT UpdateBundleNameRegistration( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in HKEY hkRegistration + ); +static BOOL IsWuRebootPending(); +static BOOL IsBundleRebootPending( + __in BURN_REGISTRATION* pRegistration +); +static BOOL IsRegistryRebootPending(); + +// function definitions + +/******************************************************************* + RegistrationParseFromXml - Parses registration information from manifest. + +*******************************************************************/ +extern "C" HRESULT RegistrationParseFromXml( + __in BURN_REGISTRATION* pRegistration, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNode* pixnRegistrationNode = NULL; + IXMLDOMNode* pixnArpNode = NULL; + IXMLDOMNode* pixnUpdateNode = NULL; + LPWSTR scz = NULL; + + // select registration node + hr = XmlSelectSingleNode(pixnBundle, L"Registration", &pixnRegistrationNode); + if (S_FALSE == hr) + { + hr = E_NOTFOUND; + } + ExitOnFailure(hr, "Failed to select registration node."); + + // @Id + hr = XmlGetAttributeEx(pixnRegistrationNode, L"Id", &pRegistration->sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + // @Tag + hr = XmlGetAttributeEx(pixnRegistrationNode, L"Tag", &pRegistration->sczTag); + ExitOnFailure(hr, "Failed to get @Tag."); + + hr = ParseRelatedCodes(pRegistration, pixnBundle); + ExitOnFailure(hr, "Failed to parse related bundles"); + + // @Version + hr = XmlGetAttributeEx(pixnRegistrationNode, L"Version", &scz); + ExitOnFailure(hr, "Failed to get @Version."); + + hr = VerParseVersion(scz, 0, FALSE, &pRegistration->pVersion); + ExitOnFailure(hr, "Failed to parse @Version: %ls", scz); + + if (pRegistration->pVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_MANIFEST_INVALID_VERSION, scz); + } + + // @ProviderKey + hr = XmlGetAttributeEx(pixnRegistrationNode, L"ProviderKey", &pRegistration->sczProviderKey); + ExitOnFailure(hr, "Failed to get @ProviderKey."); + + // @ExecutableName + hr = XmlGetAttributeEx(pixnRegistrationNode, L"ExecutableName", &pRegistration->sczExecutableName); + ExitOnFailure(hr, "Failed to get @ExecutableName."); + + // @PerMachine + hr = XmlGetYesNoAttribute(pixnRegistrationNode, L"PerMachine", &pRegistration->fPerMachine); + ExitOnFailure(hr, "Failed to get @PerMachine."); + + // select ARP node + hr = XmlSelectSingleNode(pixnRegistrationNode, L"Arp", &pixnArpNode); + if (S_FALSE != hr) + { + ExitOnFailure(hr, "Failed to select ARP node."); + + // @Register + hr = XmlGetYesNoAttribute(pixnArpNode, L"Register", &pRegistration->fRegisterArp); + ExitOnFailure(hr, "Failed to get @Register."); + + // @DisplayName + hr = XmlGetAttributeEx(pixnArpNode, L"DisplayName", &pRegistration->sczDisplayName); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @DisplayName."); + } + + // @DisplayVersion + hr = XmlGetAttributeEx(pixnArpNode, L"DisplayVersion", &pRegistration->sczDisplayVersion); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @DisplayVersion."); + } + + // @Publisher + hr = XmlGetAttributeEx(pixnArpNode, L"Publisher", &pRegistration->sczPublisher); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Publisher."); + } + + // @HelpLink + hr = XmlGetAttributeEx(pixnArpNode, L"HelpLink", &pRegistration->sczHelpLink); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @HelpLink."); + } + + // @HelpTelephone + hr = XmlGetAttributeEx(pixnArpNode, L"HelpTelephone", &pRegistration->sczHelpTelephone); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @HelpTelephone."); + } + + // @AboutUrl + hr = XmlGetAttributeEx(pixnArpNode, L"AboutUrl", &pRegistration->sczAboutUrl); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @AboutUrl."); + } + + // @UpdateUrl + hr = XmlGetAttributeEx(pixnArpNode, L"UpdateUrl", &pRegistration->sczUpdateUrl); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @UpdateUrl."); + } + + // @ParentDisplayName + hr = XmlGetAttributeEx(pixnArpNode, L"ParentDisplayName", &pRegistration->sczParentDisplayName); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @ParentDisplayName."); + } + + // @Comments + hr = XmlGetAttributeEx(pixnArpNode, L"Comments", &pRegistration->sczComments); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Comments."); + } + + // @Contact + hr = XmlGetAttributeEx(pixnArpNode, L"Contact", &pRegistration->sczContact); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Contact."); + } + + // @DisableModify + hr = XmlGetAttributeEx(pixnArpNode, L"DisableModify", &scz); + if (SUCCEEDED(hr)) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"button", -1)) + { + pRegistration->modify = BURN_REGISTRATION_MODIFY_DISABLE_BUTTON; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"yes", -1)) + { + pRegistration->modify = BURN_REGISTRATION_MODIFY_DISABLE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"no", -1)) + { + pRegistration->modify = BURN_REGISTRATION_MODIFY_ENABLED; + } + else + { + hr = E_UNEXPECTED; + ExitOnRootFailure(hr, "Invalid modify disabled type: %ls", scz); + } + } + else if (E_NOTFOUND == hr) + { + pRegistration->modify = BURN_REGISTRATION_MODIFY_ENABLED; + hr = S_OK; + } + ExitOnFailure(hr, "Failed to get @DisableModify."); + + // @DisableRemove + hr = XmlGetYesNoAttribute(pixnArpNode, L"DisableRemove", &pRegistration->fNoRemove); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @DisableRemove."); + pRegistration->fNoRemoveDefined = TRUE; + } + } + + hr = ParseSoftwareTagsFromXml(pixnRegistrationNode, &pRegistration->softwareTags.rgSoftwareTags, &pRegistration->softwareTags.cSoftwareTags); + ExitOnFailure(hr, "Failed to parse software tag."); + + // select Update node + hr = XmlSelectSingleNode(pixnRegistrationNode, L"Update", &pixnUpdateNode); + if (S_FALSE != hr) + { + ExitOnFailure(hr, "Failed to select Update node."); + + pRegistration->update.fRegisterUpdate = TRUE; + + // @Manufacturer + hr = XmlGetAttributeEx(pixnUpdateNode, L"Manufacturer", &pRegistration->update.sczManufacturer); + ExitOnFailure(hr, "Failed to get @Manufacturer."); + + // @Department + hr = XmlGetAttributeEx(pixnUpdateNode, L"Department", &pRegistration->update.sczDepartment); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Department."); + } + + // @ProductFamily + hr = XmlGetAttributeEx(pixnUpdateNode, L"ProductFamily", &pRegistration->update.sczProductFamily); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @ProductFamily."); + } + + // @Name + hr = XmlGetAttributeEx(pixnUpdateNode, L"Name", &pRegistration->update.sczName); + ExitOnFailure(hr, "Failed to get @Name."); + + // @Classification + hr = XmlGetAttributeEx(pixnUpdateNode, L"Classification", &pRegistration->update.sczClassification); + ExitOnFailure(hr, "Failed to get @Classification."); + } + + hr = SetPaths(pRegistration); + ExitOnFailure(hr, "Failed to set registration paths."); + +LExit: + ReleaseObject(pixnRegistrationNode); + ReleaseObject(pixnArpNode); + ReleaseObject(pixnUpdateNode); + ReleaseStr(scz); + + return hr; +} + +/******************************************************************* + RegistrationUninitialize - + +*******************************************************************/ +extern "C" void RegistrationUninitialize( + __in BURN_REGISTRATION* pRegistration + ) +{ + ReleaseStr(pRegistration->sczId); + ReleaseStr(pRegistration->sczTag); + + for (DWORD i = 0; i < pRegistration->cDetectCodes; ++i) + { + ReleaseStr(pRegistration->rgsczDetectCodes[i]); + } + ReleaseMem(pRegistration->rgsczDetectCodes); + + for (DWORD i = 0; i < pRegistration->cUpgradeCodes; ++i) + { + ReleaseStr(pRegistration->rgsczUpgradeCodes[i]); + } + ReleaseMem(pRegistration->rgsczUpgradeCodes); + + for (DWORD i = 0; i < pRegistration->cAddonCodes; ++i) + { + ReleaseStr(pRegistration->rgsczAddonCodes[i]); + } + ReleaseMem(pRegistration->rgsczAddonCodes); + + for (DWORD i = 0; i < pRegistration->cPatchCodes; ++i) + { + ReleaseStr(pRegistration->rgsczPatchCodes[i]); + } + ReleaseMem(pRegistration->rgsczPatchCodes); + + ReleaseStr(pRegistration->sczProviderKey); + ReleaseStr(pRegistration->sczActiveParent); + ReleaseStr(pRegistration->sczExecutableName); + + ReleaseStr(pRegistration->sczRegistrationKey); + ReleaseStr(pRegistration->sczCacheExecutablePath); + ReleaseStr(pRegistration->sczResumeCommandLine); + ReleaseStr(pRegistration->sczStateFile); + + ReleaseStr(pRegistration->sczDisplayName); + ReleaseStr(pRegistration->sczDisplayVersion); + ReleaseStr(pRegistration->sczPublisher); + ReleaseStr(pRegistration->sczHelpLink); + ReleaseStr(pRegistration->sczHelpTelephone); + ReleaseStr(pRegistration->sczAboutUrl); + ReleaseStr(pRegistration->sczUpdateUrl); + ReleaseStr(pRegistration->sczParentDisplayName); + ReleaseStr(pRegistration->sczComments); + ReleaseStr(pRegistration->sczContact); + + ReleaseStr(pRegistration->update.sczManufacturer); + ReleaseStr(pRegistration->update.sczDepartment); + ReleaseStr(pRegistration->update.sczProductFamily); + ReleaseStr(pRegistration->update.sczName); + ReleaseStr(pRegistration->update.sczClassification); + + if (pRegistration->softwareTags.rgSoftwareTags) + { + for (DWORD i = 0; i < pRegistration->softwareTags.cSoftwareTags; ++i) + { + ReleaseStr(pRegistration->softwareTags.rgSoftwareTags[i].sczFilename); + ReleaseStr(pRegistration->softwareTags.rgSoftwareTags[i].sczRegid); + ReleaseStr(pRegistration->softwareTags.rgSoftwareTags[i].sczPath); + ReleaseStr(pRegistration->softwareTags.rgSoftwareTags[i].sczTag); + } + + MemFree(pRegistration->softwareTags.rgSoftwareTags); + } + + ReleaseStr(pRegistration->sczDetectedProviderKeyBundleId); + ReleaseStr(pRegistration->sczAncestors); + ReleaseStr(pRegistration->sczBundlePackageAncestors); + RelatedBundlesUninitialize(&pRegistration->relatedBundles); + + // clear struct + memset(pRegistration, 0, sizeof(BURN_REGISTRATION)); +} + +/******************************************************************* + RegistrationSetVariables - Initializes bundle variables that map to + registration entities. + +*******************************************************************/ +extern "C" HRESULT RegistrationSetVariables( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + LPWSTR sczBundleManufacturer = NULL; + LPWSTR sczBundleName = NULL; + + if (pRegistration->fInstalled) + { + hr = VariableSetNumeric(pVariables, BURN_BUNDLE_INSTALLED, 1, TRUE); + ExitOnFailure(hr, "Failed to set the bundle installed built-in variable."); + } + + // Ensure the registration bundle name is updated. + hr = GetBundleName(pRegistration, pVariables, &sczBundleName); + ExitOnFailure(hr, "Failed to initialize bundle name."); + + hr = GetBundleManufacturer(pRegistration, pVariables, &sczBundleName); + ExitOnFailure(hr, "Failed to initialize bundle manufacturer."); + + if (pRegistration->sczActiveParent && *pRegistration->sczActiveParent) + { + hr = VariableSetString(pVariables, BURN_BUNDLE_ACTIVE_PARENT, pRegistration->sczActiveParent, TRUE, FALSE); + ExitOnFailure(hr, "Failed to overwrite the bundle active parent built-in variable."); + } + + hr = VariableSetString(pVariables, BURN_BUNDLE_PROVIDER_KEY, pRegistration->sczProviderKey, TRUE, FALSE); + ExitOnFailure(hr, "Failed to overwrite the bundle provider key built-in variable."); + + hr = VariableSetString(pVariables, BURN_BUNDLE_TAG, pRegistration->sczTag, TRUE, FALSE); + ExitOnFailure(hr, "Failed to overwrite the bundle tag built-in variable."); + + hr = VariableSetVersion(pVariables, BURN_BUNDLE_VERSION, pRegistration->pVersion, TRUE); + ExitOnFailure(hr, "Failed to overwrite the bundle version built-in variable."); + + hr = VariableSetNumeric(pVariables, BURN_REBOOT_PENDING, IsBundleRebootPending(pRegistration) || IsWuRebootPending() || IsRegistryRebootPending(), TRUE); + ExitOnFailure(hr, "Failed to overwrite the bundle reboot-pending built-in variable."); + +LExit: + ReleaseStr(sczBundleManufacturer); + ReleaseStr(sczBundleName); + + return hr; +} + +extern "C" HRESULT RegistrationDetectInstalled( + __in BURN_REGISTRATION* pRegistration + ) +{ + HRESULT hr = S_OK; + HKEY hkRegistration = NULL; + DWORD dwInstalled = 0; + + pRegistration->fCached = FileExistsEx(pRegistration->sczCacheExecutablePath, NULL); + + // open registration key + hr = RegOpen(pRegistration->hkRoot, pRegistration->sczRegistrationKey, KEY_QUERY_VALUE, &hkRegistration); + if (SUCCEEDED(hr)) + { + hr = RegReadNumber(hkRegistration, REGISTRY_BUNDLE_INSTALLED, &dwInstalled); + } + + // Not finding the key or value is okay. + if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + { + hr = S_OK; + } + + pRegistration->fInstalled = (1 == dwInstalled); + + ReleaseRegKey(hkRegistration); + return hr; +} + +/******************************************************************* + RegistrationDetectResumeMode - Detects registration information on the system + to determine if a resume is taking place. + +*******************************************************************/ +extern "C" HRESULT RegistrationDetectResumeType( + __in BURN_REGISTRATION* pRegistration, + __out BOOTSTRAPPER_RESUME_TYPE* pResumeType + ) +{ + HRESULT hr = S_OK; + HKEY hkRegistration = NULL; + DWORD dwResume = 0; + + if (IsBundleRebootPending(pRegistration)) + { + LogId(REPORT_STANDARD, MSG_PENDING_REBOOT_DETECTED, pRegistration->sczRegistrationKey); + + *pResumeType = BOOTSTRAPPER_RESUME_TYPE_REBOOT_PENDING; + ExitFunction1(hr = S_OK); + } + + // open registration key + hr = RegOpen(pRegistration->hkRoot, pRegistration->sczRegistrationKey, KEY_QUERY_VALUE, &hkRegistration); + if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + { + *pResumeType = BOOTSTRAPPER_RESUME_TYPE_NONE; + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to open registration key."); + + // read Resume value + hr = RegReadNumber(hkRegistration, L"Resume", &dwResume); + if (E_FILENOTFOUND == hr) + { + *pResumeType = BOOTSTRAPPER_RESUME_TYPE_INVALID; + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to read Resume value."); + + switch (dwResume) + { + case BURN_RESUME_MODE_ACTIVE: + // a previous run was interrupted + *pResumeType = BOOTSTRAPPER_RESUME_TYPE_INTERRUPTED; + break; + + case BURN_RESUME_MODE_SUSPEND: + *pResumeType = BOOTSTRAPPER_RESUME_TYPE_SUSPEND; + break; + + case BURN_RESUME_MODE_ARP: + *pResumeType = BOOTSTRAPPER_RESUME_TYPE_ARP; + break; + + case BURN_RESUME_MODE_REBOOT_PENDING: + // The volatile pending registry doesn't exist (checked above) which means + // the system was successfully restarted. + *pResumeType = BOOTSTRAPPER_RESUME_TYPE_REBOOT; + break; + + default: + // the value stored in the registry is not valid + *pResumeType = BOOTSTRAPPER_RESUME_TYPE_INVALID; + break; + } + +LExit: + ReleaseRegKey(hkRegistration); + + return hr; +} + +/******************************************************************* + RegistrationDetectRelatedBundles - finds the bundles with same + upgrade/detect/addon/patch codes. + +*******************************************************************/ +extern "C" HRESULT RegistrationDetectRelatedBundles( + __in BURN_REGISTRATION* pRegistration + ) +{ + HRESULT hr = S_OK; + + hr = RelatedBundlesInitializeForScope(TRUE, pRegistration, &pRegistration->relatedBundles); + ExitOnFailure(hr, "Failed to initialize per-machine related bundles."); + + hr = RelatedBundlesInitializeForScope(FALSE, pRegistration, &pRegistration->relatedBundles); + ExitOnFailure(hr, "Failed to initialize per-user related bundles."); + +LExit: + return hr; +} + +/******************************************************************* + RegistrationSessionBegin - Registers a run session on the system. + +*******************************************************************/ +extern "C" HRESULT RegistrationSessionBegin( + __in_z LPCWSTR wzEngineWorkingPath, + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in DWORD dwRegistrationOptions, + __in BURN_DEPENDENCY_REGISTRATION_ACTION dependencyRegistrationAction, + __in DWORD64 qwEstimatedSize + ) +{ + HRESULT hr = S_OK; + DWORD dwSize = 0; + HKEY hkRegistration = NULL; + LPWSTR sczPublisher = NULL; + + LogId(REPORT_VERBOSE, MSG_SESSION_BEGIN, pRegistration->sczRegistrationKey, dwRegistrationOptions, LoggingBoolToString(pRegistration->fDisableResume)); + + // Cache bundle executable. + if (dwRegistrationOptions & BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE) + { + hr = CacheCompleteBundle(pRegistration->fPerMachine, pRegistration->sczExecutableName, pRegistration->sczId, wzEngineWorkingPath +#ifdef DEBUG + , pRegistration->sczCacheExecutablePath +#endif + ); + ExitOnFailure(hr, "Failed to cache bundle from path: %ls", wzEngineWorkingPath); + } + + // create registration key + hr = RegCreate(pRegistration->hkRoot, pRegistration->sczRegistrationKey, KEY_WRITE, &hkRegistration); + ExitOnFailure(hr, "Failed to create registration key."); + + // Write any ARP values and software tags. + if (dwRegistrationOptions & BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION) + { + // Upgrade information + hr = RegWriteString(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_CACHE_PATH, pRegistration->sczCacheExecutablePath); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_CACHE_PATH); + + hr = RegWriteStringArray(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, pRegistration->rgsczUpgradeCodes, pRegistration->cUpgradeCodes); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE); + + hr = RegWriteStringArray(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE, pRegistration->rgsczAddonCodes, pRegistration->cAddonCodes); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE); + + hr = RegWriteStringArray(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE, pRegistration->rgsczDetectCodes, pRegistration->cDetectCodes); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE); + + hr = RegWriteStringArray(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE, pRegistration->rgsczPatchCodes, pRegistration->cPatchCodes); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE); + + hr = RegWriteString(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_VERSION, pRegistration->pVersion->sczVersion); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_VERSION); + + hr = RegWriteNumber(hkRegistration, REGISTRY_BUNDLE_VERSION_MAJOR, pRegistration->pVersion->dwMajor); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_VERSION_MAJOR); + + hr = RegWriteNumber(hkRegistration, REGISTRY_BUNDLE_VERSION_MINOR, pRegistration->pVersion->dwMinor); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_VERSION_MINOR); + + if (pRegistration->sczProviderKey) + { + hr = RegWriteString(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_PROVIDER_KEY, pRegistration->sczProviderKey); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_PROVIDER_KEY); + } + + if (pRegistration->sczTag) + { + hr = RegWriteString(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_TAG, pRegistration->sczTag); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_TAG); + } + + hr = RegWriteStringFormatted(hkRegistration, BURN_REGISTRATION_REGISTRY_ENGINE_VERSION, L"%hs", szVerMajorMinorBuild); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_ENGINE_VERSION); + + // DisplayIcon: [path to exe] and ",0" to refer to the first icon in the executable. + hr = RegWriteStringFormatted(hkRegistration, REGISTRY_BUNDLE_DISPLAY_ICON, L"%s,0", pRegistration->sczCacheExecutablePath); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_DISPLAY_ICON); + + // update display name + hr = UpdateBundleNameRegistration(pRegistration, pVariables, hkRegistration); + ExitOnFailure(hr, "Failed to update name and publisher."); + + // DisplayVersion: provided by UI + if (pRegistration->sczDisplayVersion) + { + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_DISPLAY_VERSION, pRegistration->sczDisplayVersion); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_DISPLAY_VERSION); + } + + // Publisher: provided by UI + hr = GetBundleManufacturer(pRegistration, pVariables, &sczPublisher); + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_PUBLISHER, SUCCEEDED(hr) ? sczPublisher : pRegistration->sczPublisher); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_PUBLISHER); + + // HelpLink: provided by UI + if (pRegistration->sczHelpLink) + { + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_HELP_LINK, pRegistration->sczHelpLink); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_HELP_LINK); + } + + // HelpTelephone: provided by UI + if (pRegistration->sczHelpTelephone) + { + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_HELP_TELEPHONE, pRegistration->sczHelpTelephone); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_HELP_TELEPHONE); + } + + // URLInfoAbout, provided by UI + if (pRegistration->sczAboutUrl) + { + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_URL_INFO_ABOUT, pRegistration->sczAboutUrl); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_URL_INFO_ABOUT); + } + + // URLUpdateInfo, provided by UI + if (pRegistration->sczUpdateUrl) + { + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_URL_UPDATE_INFO, pRegistration->sczUpdateUrl); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_URL_UPDATE_INFO); + } + + // ParentDisplayName + if (pRegistration->sczParentDisplayName) + { + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_PARENT_DISPLAY_NAME, pRegistration->sczParentDisplayName); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_PARENT_DISPLAY_NAME); + + // Need to write the ParentKeyName but can be set to anything. + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_PARENT_KEY_NAME, pRegistration->sczParentDisplayName); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_PARENT_KEY_NAME); + } + + // Comments, provided by UI + if (pRegistration->sczComments) + { + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_COMMENTS, pRegistration->sczComments); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_COMMENTS); + } + + // Contact, provided by UI + if (pRegistration->sczContact) + { + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_CONTACT, pRegistration->sczContact); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_CONTACT); + } + + // InstallLocation: provided by UI + // TODO: need to figure out what "InstallLocation" means in a chainer. + + // NoModify + if (BURN_REGISTRATION_MODIFY_DISABLE == pRegistration->modify) + { + hr = RegWriteNumber(hkRegistration, REGISTRY_BUNDLE_NO_MODIFY, 1); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_NO_MODIFY); + } + else if (BURN_REGISTRATION_MODIFY_DISABLE_BUTTON != pRegistration->modify) // if support modify (aka: did not disable anything) + { + // ModifyPath: [path to exe] /modify + hr = RegWriteStringFormatted(hkRegistration, REGISTRY_BUNDLE_MODIFY_PATH, L"\"%ls\" /modify", pRegistration->sczCacheExecutablePath); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_MODIFY_PATH); + + // NoElevateOnModify: 1 + hr = RegWriteNumber(hkRegistration, REGISTRY_BUNDLE_NO_ELEVATE_ON_MODIFY, 1); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_NO_ELEVATE_ON_MODIFY); + } + + // NoRemove: should this be allowed? + if (pRegistration->fNoRemoveDefined) + { + hr = RegWriteNumber(hkRegistration, REGISTRY_BUNDLE_NO_REMOVE, (DWORD)pRegistration->fNoRemove); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_NO_REMOVE); + } + + // Conditionally hide the ARP entry. + if (!pRegistration->fRegisterArp) + { + hr = RegWriteNumber(hkRegistration, REGISTRY_BUNDLE_SYSTEM_COMPONENT, 1); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_SYSTEM_COMPONENT); + } + + // QuietUninstallString: [path to exe] /uninstall /quiet + hr = RegWriteStringFormatted(hkRegistration, REGISTRY_BUNDLE_QUIET_UNINSTALL_STRING, L"\"%ls\" /uninstall /quiet", pRegistration->sczCacheExecutablePath); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_QUIET_UNINSTALL_STRING); + + // UninstallString, [path to exe] + // If the modify button is to be disabled, we'll add "/modify" to the uninstall string because the button is "Uninstall/Change". Otherwise, + // it's just the "Uninstall" button so we add "/uninstall" to make the program just go away. + LPCWSTR wzUninstallParameters = (BURN_REGISTRATION_MODIFY_DISABLE_BUTTON == pRegistration->modify) ? L"/modify" : L" /uninstall"; + hr = RegWriteStringFormatted(hkRegistration, REGISTRY_BUNDLE_UNINSTALL_STRING, L"\"%ls\" %ls", pRegistration->sczCacheExecutablePath, wzUninstallParameters); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_UNINSTALL_STRING); + + if (pRegistration->softwareTags.cSoftwareTags) + { + hr = WriteSoftwareTags(pVariables, &pRegistration->softwareTags); + ExitOnFailure(hr, "Failed to write software tags."); + } + + // Update registration. + if (pRegistration->update.fRegisterUpdate) + { + hr = WriteUpdateRegistration(pRegistration, pVariables); + ExitOnFailure(hr, "Failed to write update registration."); + } + } + + // Update estimated size. + if (dwRegistrationOptions & BURN_REGISTRATION_ACTION_OPERATIONS_UPDATE_SIZE) + { + qwEstimatedSize /= 1024; // Convert bytes to KB + if (0 < qwEstimatedSize) + { + if (DWORD_MAX < qwEstimatedSize) + { + // ARP doesn't support QWORDs here + dwSize = DWORD_MAX; + } + else + { + dwSize = static_cast(qwEstimatedSize); + } + + hr = RegWriteNumber(hkRegistration, REGISTRY_BUNDLE_ESTIMATED_SIZE, dwSize); + ExitOnFailure(hr, "Failed to write %ls value.", REGISTRY_BUNDLE_ESTIMATED_SIZE); + } + } + + // Register the bundle dependency key. + if (BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER == dependencyRegistrationAction) + { + hr = DependencyRegisterBundle(pRegistration); + ExitOnFailure(hr, "Failed to register the bundle dependency key."); + } + + // update resume mode + hr = UpdateResumeMode(pRegistration, hkRegistration, BURN_RESUME_MODE_ACTIVE, FALSE); + ExitOnFailure(hr, "Failed to update resume mode."); + +LExit: + ReleaseStr(sczPublisher); + ReleaseRegKey(hkRegistration); + + return hr; +} + + +/******************************************************************* + RegistrationSessionResume - Resumes a previous run session. + +*******************************************************************/ +extern "C" HRESULT RegistrationSessionResume( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + HKEY hkRegistration = NULL; + + // open registration key + hr = RegOpen(pRegistration->hkRoot, pRegistration->sczRegistrationKey, KEY_WRITE, &hkRegistration); + ExitOnFailure(hr, "Failed to open registration key."); + + // update resume mode + hr = UpdateResumeMode(pRegistration, hkRegistration, BURN_RESUME_MODE_ACTIVE, FALSE); + ExitOnFailure(hr, "Failed to update resume mode."); + + // update display name + hr = UpdateBundleNameRegistration(pRegistration, pVariables, hkRegistration); + ExitOnFailure(hr, "Failed to update name and publisher."); + +LExit: + ReleaseRegKey(hkRegistration); + + return hr; +} + + +/******************************************************************* + RegistrationSessionEnd - Unregisters a run session from the system. + + *******************************************************************/ +extern "C" HRESULT RegistrationSessionEnd( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in BURN_PACKAGES* pPackages, + __in BURN_RESUME_MODE resumeMode, + __in BOOTSTRAPPER_APPLY_RESTART restart, + __in BURN_DEPENDENCY_REGISTRATION_ACTION dependencyRegistrationAction + ) +{ + HRESULT hr = S_OK; + LPWSTR sczRebootRequiredKey = NULL; + HKEY hkRebootRequired = NULL; + HKEY hkRegistration = NULL; + + LogId(REPORT_STANDARD, MSG_SESSION_END, pRegistration->sczRegistrationKey, LoggingResumeModeToString(resumeMode), LoggingRestartToString(restart), LoggingBoolToString(pRegistration->fDisableResume)); + + // If a restart is required for any reason, write a volatile registry key to track of + // of that fact until the reboot has taken place. + if (BOOTSTRAPPER_APPLY_RESTART_NONE != restart) + { + // We'll write the volatile registry key right next to the bundle ARP registry key + // because that's easy. This is all best effort since the worst case just means in + // the rare case the user launches the same install again before taking the restart + // the BA won't know a restart was still required. + hr = StrAllocFormatted(&sczRebootRequiredKey, REGISTRY_REBOOT_PENDING_FORMAT, pRegistration->sczRegistrationKey); + if (SUCCEEDED(hr)) + { + hr = RegCreateEx(pRegistration->hkRoot, sczRebootRequiredKey, KEY_WRITE, TRUE, NULL, &hkRebootRequired, NULL); + } + + if (FAILED(hr)) + { + ExitTraceSource(DUTIL_SOURCE_DEFAULT, hr, "Failed to write volatile reboot required registry key."); + hr = S_OK; + } + } + + // If no resume mode, then remove the bundle registration. + if (BURN_RESUME_MODE_NONE == resumeMode) + { + // If we just registered the bundle dependency but something went wrong and caused us to not + // keep the bundle registration (like rollback) or we are supposed to unregister the bundle + // dependency when unregistering the bundle, do so. + if (BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER == dependencyRegistrationAction || + BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER == dependencyRegistrationAction) + { + // Remove the bundle dependency key. + DependencyUnregisterBundle(pRegistration, pPackages); + } + + // Delete update registration key. + if (pRegistration->update.fRegisterUpdate) + { + RemoveUpdateRegistration(pRegistration); + } + + RemoveSoftwareTags(pVariables, &pRegistration->softwareTags); + + // Delete registration key. + hr = RegDelete(pRegistration->hkRoot, pRegistration->sczRegistrationKey, REG_KEY_DEFAULT, FALSE); + if (E_FILENOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to delete registration key: %ls", pRegistration->sczRegistrationKey); + } + + CacheRemoveBundle(pRegistration->fPerMachine, pRegistration->sczId); + } + else // the mode needs to be updated so open the registration key. + { + // Open registration key. + hr = RegOpen(pRegistration->hkRoot, pRegistration->sczRegistrationKey, KEY_WRITE, &hkRegistration); + ExitOnFailure(hr, "Failed to open registration key."); + } + + // Update resume mode. + hr = UpdateResumeMode(pRegistration, hkRegistration, resumeMode, BOOTSTRAPPER_APPLY_RESTART_INITIATED == restart); + ExitOnFailure(hr, "Failed to update resume mode."); + +LExit: + ReleaseRegKey(hkRegistration); + ReleaseRegKey(hkRebootRequired); + ReleaseStr(sczRebootRequiredKey); + + return hr; +} + +/******************************************************************* + RegistrationSaveState - Saves an engine state BLOB for retreval after a resume. + +*******************************************************************/ +extern "C" HRESULT RegistrationSaveState( + __in BURN_REGISTRATION* pRegistration, + __in_bcount(cbBuffer) BYTE* pbBuffer, + __in SIZE_T cbBuffer + ) +{ + HRESULT hr = S_OK; + + // write data to file + hr = FileWrite(pRegistration->sczStateFile, FILE_ATTRIBUTE_NORMAL, pbBuffer, cbBuffer, NULL); + if (E_PATHNOTFOUND == hr) + { + // TODO: should we log that the bundle's cache folder was not present so the state file wasn't created either? + hr = S_OK; + } + ExitOnFailure(hr, "Failed to write state to file: %ls", pRegistration->sczStateFile); + +LExit: + return hr; +} + +/******************************************************************* + RegistrationLoadState - Loads a previously stored engine state BLOB. + +*******************************************************************/ +extern "C" HRESULT RegistrationLoadState( + __in BURN_REGISTRATION* pRegistration, + __out_bcount(*pcbBuffer) BYTE** ppbBuffer, + __out SIZE_T* pcbBuffer + ) +{ + // read data from file + HRESULT hr = FileRead(ppbBuffer, pcbBuffer, pRegistration->sczStateFile); + return hr; +} + +/******************************************************************* +RegistrationGetResumeCommandLine - Gets the resume command line from the registry + +*******************************************************************/ +extern "C" HRESULT RegistrationGetResumeCommandLine( + __in const BURN_REGISTRATION* pRegistration, + __deref_out_z LPWSTR* psczResumeCommandLine + ) +{ + HRESULT hr = S_OK; + HKEY hkRegistration = NULL; + + // open registration key + hr = RegOpen(pRegistration->hkRoot, pRegistration->sczRegistrationKey, KEY_QUERY_VALUE, &hkRegistration); + if (SUCCEEDED(hr)) + { + hr = RegReadString(hkRegistration, REGISTRY_BUNDLE_RESUME_COMMAND_LINE, psczResumeCommandLine); + } + + // Not finding the key or value is okay. + if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + { + hr = S_OK; + } + + ReleaseRegKey(hkRegistration); + + return hr; +} + + +// internal helper functions + +static HRESULT ParseSoftwareTagsFromXml( + __in IXMLDOMNode* pixnRegistrationNode, + __out BURN_SOFTWARE_TAG** prgSoftwareTags, + __out DWORD* pcSoftwareTags + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + + BURN_SOFTWARE_TAG* pSoftwareTags = NULL; + BSTR bstrTagXml = NULL; + + // select tag nodes + hr = XmlSelectNodes(pixnRegistrationNode, L"SoftwareTag", &pixnNodes); + ExitOnFailure(hr, "Failed to select software tag nodes."); + + // get tag node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get software tag count."); + + if (cNodes) + { + pSoftwareTags = (BURN_SOFTWARE_TAG*)MemAlloc(sizeof(BURN_SOFTWARE_TAG) * cNodes, TRUE); + ExitOnNull(pSoftwareTags, hr, E_OUTOFMEMORY, "Failed to allocate memory for software tag structs."); + + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_SOFTWARE_TAG* pSoftwareTag = &pSoftwareTags[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + hr = XmlGetAttributeEx(pixnNode, L"Filename", &pSoftwareTag->sczFilename); + ExitOnFailure(hr, "Failed to get @Filename."); + + hr = XmlGetAttributeEx(pixnNode, L"Regid", &pSoftwareTag->sczRegid); + ExitOnFailure(hr, "Failed to get @Regid."); + + hr = XmlGetAttributeEx(pixnNode, L"Path", &pSoftwareTag->sczPath); + ExitOnFailure(hr, "Failed to get @Path."); + + hr = XmlGetText(pixnNode, &bstrTagXml); + ExitOnFailure(hr, "Failed to get SoftwareTag text."); + + hr = StrAnsiAllocString(&pSoftwareTag->sczTag, bstrTagXml, 0, CP_UTF8); + ExitOnFailure(hr, "Failed to convert SoftwareTag text to UTF-8"); + + // prepare next iteration + ReleaseNullBSTR(bstrTagXml); + ReleaseNullObject(pixnNode); + } + } + + *pcSoftwareTags = cNodes; + *prgSoftwareTags = pSoftwareTags; + pSoftwareTags = NULL; + + hr = S_OK; + +LExit: + ReleaseBSTR(bstrTagXml); + ReleaseObject(pixnNode); + ReleaseObject(pixnNodes); + ReleaseMem(pSoftwareTags); + + return hr; +} + +static HRESULT SetPaths( + __in BURN_REGISTRATION* pRegistration + ) +{ + HRESULT hr = S_OK; + LPWSTR sczCacheDirectory = NULL; + + // save registration key root + pRegistration->hkRoot = pRegistration->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + + // build uninstall registry key path + hr = StrAllocFormatted(&pRegistration->sczRegistrationKey, L"%s\\%s", BURN_REGISTRATION_REGISTRY_UNINSTALL_KEY, pRegistration->sczId); + ExitOnFailure(hr, "Failed to build uninstall registry key path."); + + // build cache directory + hr = CacheGetCompletedPath(pRegistration->fPerMachine, pRegistration->sczId, &sczCacheDirectory); + ExitOnFailure(hr, "Failed to build cache directory."); + + // build cached executable path + hr = PathConcat(sczCacheDirectory, pRegistration->sczExecutableName, &pRegistration->sczCacheExecutablePath); + ExitOnFailure(hr, "Failed to build cached executable path."); + + // build state file path + hr = StrAllocFormatted(&pRegistration->sczStateFile, L"%s\\state.rsm", sczCacheDirectory); + ExitOnFailure(hr, "Failed to build state file path."); + +LExit: + ReleaseStr(sczCacheDirectory); + return hr; +} + +static HRESULT GetBundleManufacturer( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __out LPWSTR* psczBundleManufacturer + ) +{ + HRESULT hr = S_OK; + + hr = VariableGetString(pVariables, BURN_BUNDLE_MANUFACTURER, psczBundleManufacturer); + if (E_NOTFOUND == hr) + { + hr = VariableSetString(pVariables, BURN_BUNDLE_MANUFACTURER, pRegistration->sczPublisher, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set bundle manufacturer."); + + hr = StrAllocString(psczBundleManufacturer, pRegistration->sczPublisher, 0); + } + ExitOnFailure(hr, "Failed to get bundle manufacturer."); + +LExit: + return hr; +} + +static HRESULT GetBundleName( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __out LPWSTR* psczBundleName + ) +{ + HRESULT hr = S_OK; + + hr = VariableGetString(pVariables, BURN_BUNDLE_NAME, psczBundleName); + if (E_NOTFOUND == hr) + { + hr = VariableSetString(pVariables, BURN_BUNDLE_NAME, pRegistration->sczDisplayName, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set bundle name."); + + hr = StrAllocString(psczBundleName, pRegistration->sczDisplayName, 0); + } + ExitOnFailure(hr, "Failed to get bundle name."); + +LExit: + return hr; +} + +static HRESULT UpdateResumeMode( + __in BURN_REGISTRATION* pRegistration, + __in HKEY hkRegistration, + __in BURN_RESUME_MODE resumeMode, + __in BOOL fRestartInitiated + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + HKEY hkRebootRequired = NULL; + HKEY hkRun = NULL; + LPWSTR sczResumeCommandLine = NULL; + LPCWSTR sczResumeKey = REGISTRY_RUN_ONCE_KEY; + + LogId(REPORT_STANDARD, MSG_SESSION_UPDATE, pRegistration->sczRegistrationKey, LoggingResumeModeToString(resumeMode), LoggingBoolToString(fRestartInitiated), LoggingBoolToString(pRegistration->fDisableResume)); + + // write resume information + if (hkRegistration) + { + // write Resume value + hr = RegWriteNumber(hkRegistration, L"Resume", (DWORD)resumeMode); + ExitOnFailure(hr, "Failed to write Resume value."); + + // Write the Installed value *only* when the mode is ARP. This will tell us + // that the bundle considers itself "installed" on the machine. Note that we + // never change the value to "0" after that. The bundle will be considered + // "uninstalled" when all of the registration is removed. + if (BURN_RESUME_MODE_ARP == resumeMode) + { + // Write Installed value. + hr = RegWriteNumber(hkRegistration, REGISTRY_BUNDLE_INSTALLED, 1); + ExitOnFailure(hr, "Failed to write Installed value."); + } + } + + // If the engine is active write the run key so we resume if there is an unexpected + // power loss. Also, if a restart was initiated in the middle of the chain then + // ensure the run key exists (it should since going active would have written it). + // Do not write the run key when embedded since the containing bundle + // is expected to detect for and restart the embedded bundle. + if ((BURN_RESUME_MODE_ACTIVE == resumeMode || fRestartInitiated) && !pRegistration->fDisableResume) + { + // append RunOnce switch + hr = StrAllocFormatted(&sczResumeCommandLine, L"\"%ls\" /%ls", pRegistration->sczCacheExecutablePath, BURN_COMMANDLINE_SWITCH_RUNONCE); + ExitOnFailure(hr, "Failed to format resume command line for RunOnce."); + + // write run key + hr = RegCreate(pRegistration->hkRoot, sczResumeKey, KEY_WRITE, &hkRun); + ExitOnFailure(hr, "Failed to create run key."); + + hr = RegWriteString(hkRun, pRegistration->sczId, sczResumeCommandLine); + ExitOnFailure(hr, "Failed to write run key value."); + + hr = RegWriteString(hkRegistration, REGISTRY_BUNDLE_RESUME_COMMAND_LINE, pRegistration->sczResumeCommandLine); + ExitOnFailure(hr, "Failed to write resume command line value."); + } + else // delete run key value + { + hr = RegOpen(pRegistration->hkRoot, sczResumeKey, KEY_WRITE, &hkRun); + if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + { + hr = S_OK; + } + else + { + ExitOnWin32Error(er, hr, "Failed to open run key."); + + er = ::RegDeleteValueW(hkRun, pRegistration->sczId); + if (ERROR_FILE_NOT_FOUND == er) + { + er = ERROR_SUCCESS; + } + ExitOnWin32Error(er, hr, "Failed to delete run key value."); + } + + if (hkRegistration) + { + er = ::RegDeleteValueW(hkRegistration, REGISTRY_BUNDLE_RESUME_COMMAND_LINE); + if (ERROR_FILE_NOT_FOUND == er) + { + er = ERROR_SUCCESS; + } + ExitOnWin32Error(er, hr, "Failed to delete resume command line value."); + } + } + +LExit: + ReleaseStr(sczResumeCommandLine); + ReleaseRegKey(hkRebootRequired); + ReleaseRegKey(hkRun); + + return hr; +} + +static HRESULT ParseRelatedCodes( + __in BURN_REGISTRATION* pRegistration, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnElement = NULL; + LPWSTR sczAction = NULL; + LPWSTR sczId = NULL; + DWORD cElements = 0; + + hr = XmlSelectNodes(pixnBundle, L"RelatedBundle", &pixnNodes); + ExitOnFailure(hr, "Failed to get RelatedBundle nodes"); + + hr = pixnNodes->get_length((long*)&cElements); + ExitOnFailure(hr, "Failed to get RelatedBundle element count."); + + for (DWORD i = 0; i < cElements; ++i) + { + hr = XmlNextElement(pixnNodes, &pixnElement, NULL); + ExitOnFailure(hr, "Failed to get next RelatedBundle element."); + + hr = XmlGetAttributeEx(pixnElement, L"Action", &sczAction); + ExitOnFailure(hr, "Failed to get @Action."); + + hr = XmlGetAttributeEx(pixnElement, L"Id", &sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Detect", -1)) + { + hr = MemEnsureArraySize(reinterpret_cast(&pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes + 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to resize Detect code array in registration"); + + pRegistration->rgsczDetectCodes[pRegistration->cDetectCodes] = sczId; + sczId = NULL; + ++pRegistration->cDetectCodes; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Upgrade", -1)) + { + hr = MemEnsureArraySize(reinterpret_cast(&pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes + 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to resize Upgrade code array in registration"); + + pRegistration->rgsczUpgradeCodes[pRegistration->cUpgradeCodes] = sczId; + sczId = NULL; + ++pRegistration->cUpgradeCodes; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Addon", -1)) + { + hr = MemEnsureArraySize(reinterpret_cast(&pRegistration->rgsczAddonCodes), pRegistration->cAddonCodes + 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to resize Addon code array in registration"); + + pRegistration->rgsczAddonCodes[pRegistration->cAddonCodes] = sczId; + sczId = NULL; + ++pRegistration->cAddonCodes; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczAction, -1, L"Patch", -1)) + { + hr = MemEnsureArraySize(reinterpret_cast(&pRegistration->rgsczPatchCodes), pRegistration->cPatchCodes + 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to resize Patch code array in registration"); + + pRegistration->rgsczPatchCodes[pRegistration->cPatchCodes] = sczId; + sczId = NULL; + ++pRegistration->cPatchCodes; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Action: %ls", sczAction); + } + } + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnElement); + ReleaseStr(sczAction); + ReleaseStr(sczId); + + return hr; +} + +static HRESULT FormatUpdateRegistrationKey( + __in BURN_REGISTRATION* pRegistration, + __out_z LPWSTR* psczKey + ) +{ + HRESULT hr = S_OK; + LPWSTR sczKey = NULL; + + hr = StrAllocFormatted(&sczKey, L"SOFTWARE\\%ls\\Updates\\", pRegistration->update.sczManufacturer); + ExitOnFailure(hr, "Failed to format the key path for update registration."); + + if (pRegistration->update.sczProductFamily) + { + hr = StrAllocFormatted(&sczKey, L"%ls%ls\\", sczKey, pRegistration->update.sczProductFamily); + ExitOnFailure(hr, "Failed to format the key path for update registration."); + } + + hr = StrAllocConcat(&sczKey, pRegistration->update.sczName, 0); + ExitOnFailure(hr, "Failed to format the key path for update registration."); + + *psczKey = sczKey; + sczKey = NULL; + +LExit: + ReleaseStr(sczKey); + + return hr; +} + +static HRESULT WriteSoftwareTags( + __in BURN_VARIABLES* pVariables, + __in BURN_SOFTWARE_TAGS* pSoftwareTags + ) +{ + HRESULT hr = S_OK; + LPWSTR sczRootFolder = NULL; + LPWSTR sczTagFolder = NULL; + LPWSTR sczPath = NULL; + + for (DWORD iTag = 0; iTag < pSoftwareTags->cSoftwareTags; ++iTag) + { + BURN_SOFTWARE_TAG* pSoftwareTag = pSoftwareTags->rgSoftwareTags + iTag; + + hr = VariableFormatString(pVariables, pSoftwareTag->sczPath, &sczRootFolder, NULL); + ExitOnFailure(hr, "Failed to format tag folder path."); + + hr = PathConcat(sczRootFolder, SWIDTAG_FOLDER, &sczTagFolder); + ExitOnFailure(hr, "Failed to allocate regid folder path."); + + hr = PathConcat(sczTagFolder, pSoftwareTag->sczFilename, &sczPath); + ExitOnFailure(hr, "Failed to allocate regid file path."); + + hr = DirEnsureExists(sczTagFolder, NULL); + ExitOnFailure(hr, "Failed to create regid folder: %ls", sczTagFolder); + + hr = FileWrite(sczPath, FILE_ATTRIBUTE_NORMAL, reinterpret_cast(pSoftwareTag->sczTag), lstrlenA(pSoftwareTag->sczTag), NULL); + ExitOnFailure(hr, "Failed to write tag xml to file: %ls", sczPath); + } + +LExit: + ReleaseStr(sczPath); + ReleaseStr(sczTagFolder); + ReleaseStr(sczRootFolder); + + return hr; +} + +static HRESULT RemoveSoftwareTags( + __in BURN_VARIABLES* pVariables, + __in BURN_SOFTWARE_TAGS* pSoftwareTags + ) +{ + HRESULT hr = S_OK; + LPWSTR sczRootFolder = NULL; + LPWSTR sczTagFolder = NULL; + LPWSTR sczPath = NULL; + + for (DWORD iTag = 0; iTag < pSoftwareTags->cSoftwareTags; ++iTag) + { + BURN_SOFTWARE_TAG* pSoftwareTag = pSoftwareTags->rgSoftwareTags + iTag; + + hr = VariableFormatString(pVariables, pSoftwareTag->sczPath, &sczRootFolder, NULL); + ExitOnFailure(hr, "Failed to format tag folder path."); + + hr = PathConcat(sczRootFolder, SWIDTAG_FOLDER, &sczTagFolder); + ExitOnFailure(hr, "Failed to allocate regid folder path."); + + hr = PathConcat(sczTagFolder, pSoftwareTag->sczFilename, &sczPath); + ExitOnFailure(hr, "Failed to allocate regid file path."); + + // Best effort to delete the software tag file and the regid folder. + FileEnsureDelete(sczPath); + + DirDeleteEmptyDirectoriesToRoot(sczTagFolder, 0); + } + +LExit: + ReleaseStr(sczPath); + ReleaseStr(sczTagFolder); + ReleaseStr(sczRootFolder); + + return hr; +} + +static HRESULT WriteUpdateRegistration( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + LPWSTR sczKey = NULL; + HKEY hkKey = NULL; + + hr = FormatUpdateRegistrationKey(pRegistration, &sczKey); + ExitOnFailure(hr, "Failed to get the formatted key path for update registration."); + + hr = RegCreate(pRegistration->hkRoot, sczKey, KEY_WRITE, &hkKey); + ExitOnFailure(hr, "Failed to create the key for update registration."); + + hr = RegWriteString(hkKey, L"ThisVersionInstalled", L"Y"); + ExitOnFailure(hr, "Failed to write %ls value.", L"ThisVersionInstalled"); + + hr = RegWriteString(hkKey, L"PackageName", pRegistration->sczDisplayName); + ExitOnFailure(hr, "Failed to write %ls value.", L"PackageName"); + + hr = RegWriteString(hkKey, L"PackageVersion", pRegistration->sczDisplayVersion); + ExitOnFailure(hr, "Failed to write %ls value.", L"PackageVersion"); + + hr = RegWriteString(hkKey, L"Publisher", pRegistration->sczPublisher); + ExitOnFailure(hr, "Failed to write %ls value.", L"Publisher"); + + if (pRegistration->update.sczDepartment) + { + hr = RegWriteString(hkKey, L"PublishingGroup", pRegistration->update.sczDepartment); + ExitOnFailure(hr, "Failed to write %ls value.", L"PublishingGroup"); + } + + hr = RegWriteString(hkKey, L"ReleaseType", pRegistration->update.sczClassification); + ExitOnFailure(hr, "Failed to write %ls value.", L"ReleaseType"); + + hr = RegWriteStringVariable(hkKey, pVariables, VARIABLE_LOGONUSER, L"InstalledBy"); + ExitOnFailure(hr, "Failed to write %ls value.", L"InstalledBy"); + + hr = RegWriteStringVariable(hkKey, pVariables, VARIABLE_DATE, L"InstalledDate"); + ExitOnFailure(hr, "Failed to write %ls value.", L"InstalledDate"); + + hr = RegWriteStringVariable(hkKey, pVariables, VARIABLE_INSTALLERNAME, L"InstallerName"); + ExitOnFailure(hr, "Failed to write %ls value.", L"InstallerName"); + + hr = RegWriteStringVariable(hkKey, pVariables, VARIABLE_INSTALLERVERSION, L"InstallerVersion"); + ExitOnFailure(hr, "Failed to write %ls value.", L"InstallerVersion"); + +LExit: + ReleaseRegKey(hkKey); + ReleaseStr(sczKey); + + return hr; +} + +static HRESULT RemoveUpdateRegistration( + __in BURN_REGISTRATION* pRegistration + ) +{ + HRESULT hr = S_OK; + LPWSTR sczKey = NULL; + LPWSTR sczPackageVersion = NULL; + HKEY hkKey = NULL; + BOOL fDeleteRegKey = TRUE; + + hr = FormatUpdateRegistrationKey(pRegistration, &sczKey); + ExitOnFailure(hr, "Failed to format key for update registration."); + + // Only delete if the uninstalling bundle's PackageVersion is the same as the + // PackageVersion in the update registration key. + // This is to support build to build upgrades + hr = RegOpen(pRegistration->hkRoot, sczKey, KEY_QUERY_VALUE, &hkKey); + if (SUCCEEDED(hr)) + { + hr = RegReadString(hkKey, L"PackageVersion", &sczPackageVersion); + if (SUCCEEDED(hr)) + { + if (CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, sczPackageVersion, -1, pRegistration->sczDisplayVersion, -1)) + { + fDeleteRegKey = FALSE; + } + } + ReleaseRegKey(hkKey); + } + + // Unable to open the key or read the value is okay. + hr = S_OK; + + if (fDeleteRegKey) + { + hr = RegDelete(pRegistration->hkRoot, sczKey, REG_KEY_DEFAULT, FALSE); + if (E_FILENOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to remove update registration key: %ls", sczKey); + } + } + +LExit: + ReleaseStr(sczPackageVersion); + ReleaseStr(sczKey); + + return hr; +} + +static HRESULT RegWriteStringVariable( + __in HKEY hk, + __in BURN_VARIABLES* pVariables, + __in LPCWSTR wzVariable, + __in LPCWSTR wzName + ) +{ + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + + hr = VariableGetString(pVariables, wzVariable, &sczValue); + ExitOnFailure(hr, "Failed to get the %ls variable.", wzVariable); + + hr = RegWriteString(hk, wzName, sczValue); + ExitOnFailure(hr, "Failed to write %ls value.", wzName); + +LExit: + StrSecureZeroFreeString(sczValue); + + return hr; +} + +static HRESULT UpdateBundleNameRegistration( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in HKEY hkRegistration + ) +{ + HRESULT hr = S_OK; + LPWSTR sczDisplayName = NULL; + + // DisplayName: provided by UI + hr = GetBundleName(pRegistration, pVariables, &sczDisplayName); + hr = RegWriteString(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_DISPLAY_NAME, SUCCEEDED(hr) ? sczDisplayName : pRegistration->sczDisplayName); + ExitOnFailure(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_DISPLAY_NAME); + +LExit: + ReleaseStr(sczDisplayName); + + return hr; +} + +static BOOL IsWuRebootPending() +{ + HRESULT hr = S_OK; + BOOL fRebootPending = FALSE; + + // Do a best effort to ask WU if a reboot is required. If anything goes + // wrong then let's pretend a reboot is not required. + hr = ::CoInitialize(NULL); + if (SUCCEEDED(hr) || RPC_E_CHANGED_MODE == hr) + { + hr = WuaRestartRequired(&fRebootPending); + if (FAILED(hr)) + { + fRebootPending = FALSE; + } + + ::CoUninitialize(); + } + + return fRebootPending; +} + +static BOOL IsBundleRebootPending(BURN_REGISTRATION* pRegistration) +{ + HRESULT hr = S_OK; + LPWSTR sczRebootRequiredKey = NULL; + HKEY hkRebootRequired = NULL; + BOOL fBundleRebootPending = FALSE; + + // Check to see if a restart is pending for this bundle. + hr = StrAllocFormatted(&sczRebootRequiredKey, REGISTRY_REBOOT_PENDING_FORMAT, pRegistration->sczRegistrationKey); + ExitOnFailure(hr, "Failed to format pending restart registry key to read."); + + hr = RegOpen(pRegistration->hkRoot, sczRebootRequiredKey, KEY_QUERY_VALUE, &hkRebootRequired); + fBundleRebootPending = SUCCEEDED(hr); + +LExit: + ReleaseStr(sczRebootRequiredKey); + ReleaseRegKey(hkRebootRequired); + + return fBundleRebootPending; +} + +static BOOL IsRegistryRebootPending() +{ + HRESULT hr = S_OK; + DWORD dwValue; + HKEY hk = NULL; + BOOL fRebootPending = FALSE; + + hr = RegKeyReadNumber(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\ServerManager", L"CurrentRebootAttempts", TRUE, &dwValue); + fRebootPending = SUCCEEDED(hr) && 0 < dwValue; + + if (!fRebootPending) + { + hr = RegKeyReadNumber(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Updates", L"UpdateExeVolatile", TRUE, &dwValue); + fRebootPending = SUCCEEDED(hr) && 0 < dwValue; + + if (!fRebootPending) + { + fRebootPending = RegValueExists(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending", NULL, TRUE); + + if (!fRebootPending) + { + fRebootPending = RegValueExists(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootInProgress", NULL, TRUE); + + if (!fRebootPending) + { + hr = RegKeyReadNumber(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update", L"AUState", TRUE, &dwValue); + fRebootPending = SUCCEEDED(hr) && 8 == dwValue; + + if (!fRebootPending) + { + fRebootPending = RegValueExists(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager", L"PendingFileRenameOperations", TRUE); + + if (!fRebootPending) + { + fRebootPending = RegValueExists(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager", L"PendingFileRenameOperations2", TRUE); + + if (!fRebootPending) + { + hr = RegOpen(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\FileRenameOperations", KEY_READ | KEY_WOW64_64KEY, &hk); + if (SUCCEEDED(hr)) + { + DWORD cSubKeys = 0; + DWORD cValues = 0; + hr = RegQueryKey(hk, &cSubKeys, &cValues); + fRebootPending = SUCCEEDED(hr) && (0 < cSubKeys || 0 < cValues); + } + } + } + } + } + } + } + } + + ReleaseRegKey(hk); + + return fRebootPending; +} diff --git a/src/burn/engine/registration.h b/src/burn/engine/registration.h new file mode 100644 index 00000000..6d8a6d2a --- /dev/null +++ b/src/burn/engine/registration.h @@ -0,0 +1,225 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +enum BURN_MODE; +enum BURN_DEPENDENCY_REGISTRATION_ACTION; +struct _BURN_LOGGING; +typedef _BURN_LOGGING BURN_LOGGING; + +// constants + +const LPCWSTR BURN_REGISTRATION_REGISTRY_UNINSTALL_KEY = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_BUNDLE_CACHE_PATH = L"BundleCachePath"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE = L"BundleAddonCode"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE = L"BundleDetectCode"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE = L"BundlePatchCode"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE = L"BundleUpgradeCode"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_BUNDLE_DISPLAY_NAME = L"DisplayName"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_BUNDLE_VERSION = L"BundleVersion"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_ENGINE_VERSION = L"EngineVersion"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_BUNDLE_PROVIDER_KEY = L"BundleProviderKey"; +const LPCWSTR BURN_REGISTRATION_REGISTRY_BUNDLE_TAG = L"BundleTag"; + +enum BURN_RESUME_MODE +{ + BURN_RESUME_MODE_NONE, + BURN_RESUME_MODE_ACTIVE, + BURN_RESUME_MODE_SUSPEND, + BURN_RESUME_MODE_ARP, + BURN_RESUME_MODE_REBOOT_PENDING, +}; + +enum BURN_REGISTRATION_MODIFY_TYPE +{ + BURN_REGISTRATION_MODIFY_ENABLED, + BURN_REGISTRATION_MODIFY_DISABLE, + BURN_REGISTRATION_MODIFY_DISABLE_BUTTON, +}; + + +// structs + +typedef struct _BURN_UPDATE_REGISTRATION +{ + BOOL fRegisterUpdate; + LPWSTR sczManufacturer; + LPWSTR sczDepartment; + LPWSTR sczProductFamily; + LPWSTR sczName; + LPWSTR sczClassification; +} BURN_UPDATE_REGISTRATION; + +typedef struct _BURN_RELATED_BUNDLE +{ + BOOTSTRAPPER_RELATION_TYPE relationType; + BOOL fForwardCompatible; + + VERUTIL_VERSION* pVersion; + LPWSTR sczTag; + BOOL fPlannable; + + BURN_PACKAGE package; +} BURN_RELATED_BUNDLE; + +typedef struct _BURN_RELATED_BUNDLES +{ + BURN_RELATED_BUNDLE* rgRelatedBundles; + DWORD cRelatedBundles; +} BURN_RELATED_BUNDLES; + +typedef struct _BURN_SOFTWARE_TAG +{ + LPWSTR sczFilename; + LPWSTR sczRegid; + LPWSTR sczPath; + LPSTR sczTag; +} BURN_SOFTWARE_TAG; + +typedef struct _BURN_SOFTWARE_TAGS +{ + BURN_SOFTWARE_TAG* rgSoftwareTags; + DWORD cSoftwareTags; +} BURN_SOFTWARE_TAGS; + +typedef struct _BURN_REGISTRATION +{ + BOOL fPerMachine; + BOOL fRegisterArp; + BOOL fDisableResume; + BOOL fCached; + BOOL fInstalled; + LPWSTR sczId; + LPWSTR sczTag; + + LPWSTR *rgsczDetectCodes; + DWORD cDetectCodes; + + LPWSTR *rgsczUpgradeCodes; + DWORD cUpgradeCodes; + + LPWSTR *rgsczAddonCodes; + DWORD cAddonCodes; + + LPWSTR *rgsczPatchCodes; + DWORD cPatchCodes; + + VERUTIL_VERSION* pVersion; + LPWSTR sczActiveParent; + LPWSTR sczProviderKey; + LPWSTR sczExecutableName; + + // paths + HKEY hkRoot; + LPWSTR sczRegistrationKey; + LPWSTR sczCacheExecutablePath; + LPWSTR sczResumeCommandLine; + LPWSTR sczStateFile; + + // ARP registration + LPWSTR sczDisplayName; + LPWSTR sczDisplayVersion; + LPWSTR sczPublisher; + LPWSTR sczHelpLink; + LPWSTR sczHelpTelephone; + LPWSTR sczAboutUrl; + LPWSTR sczUpdateUrl; + LPWSTR sczParentDisplayName; + LPWSTR sczComments; + //LPWSTR sczReadme; // TODO: this would be a file path + LPWSTR sczContact; + //DWORD64 qwEstimatedSize; // TODO: size should come from disk cost calculation + BURN_REGISTRATION_MODIFY_TYPE modify; + BOOL fNoRemoveDefined; + BOOL fNoRemove; + + BURN_SOFTWARE_TAGS softwareTags; + + // Update registration + BURN_UPDATE_REGISTRATION update; + + BURN_RELATED_BUNDLES relatedBundles; // Only valid after detect. + DEPENDENCY* rgIgnoredDependencies; // Only valid after detect. + UINT cIgnoredDependencies; // Only valid after detect. + DEPENDENCY* rgDependents; // Only valid after detect. + UINT cDependents; // Only valid after detect. + BOOL fIgnoreAllDependents; // Only valid after detect. + LPCWSTR wzSelfDependent; // Only valid after detect. + BOOL fSelfRegisteredAsDependent; // Only valid after detect. + BOOL fParentRegisteredAsDependent; // Only valid after detect. + BOOL fForwardCompatibleBundleExists; // Only valid after detect. + BOOL fEligibleForCleanup; // Only valid after detect. + + LPWSTR sczDetectedProviderKeyBundleId; + LPWSTR sczAncestors; + LPWSTR sczBundlePackageAncestors; +} BURN_REGISTRATION; + + +// functions + +HRESULT RegistrationParseFromXml( + __in BURN_REGISTRATION* pRegistration, + __in IXMLDOMNode* pixnBundle + ); +void RegistrationUninitialize( + __in BURN_REGISTRATION* pRegistration + ); +HRESULT RegistrationSetVariables( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables + ); +HRESULT RegistrationDetectInstalled( + __in BURN_REGISTRATION* pRegistration + ); +HRESULT RegistrationDetectResumeType( + __in BURN_REGISTRATION* pRegistration, + __out BOOTSTRAPPER_RESUME_TYPE* pResumeType + ); +HRESULT RegistrationDetectRelatedBundles( + __in BURN_REGISTRATION* pRegistration + ); +HRESULT RegistrationSessionBegin( + __in_z LPCWSTR wzEngineWorkingPath, + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in DWORD dwRegistrationOptions, + __in BURN_DEPENDENCY_REGISTRATION_ACTION dependencyRegistrationAction, + __in DWORD64 qwEstimatedSize + ); +HRESULT RegistrationSessionResume( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables + ); +HRESULT RegistrationSessionEnd( + __in BURN_REGISTRATION* pRegistration, + __in BURN_VARIABLES* pVariables, + __in BURN_PACKAGES* pPackages, + __in BURN_RESUME_MODE resumeMode, + __in BOOTSTRAPPER_APPLY_RESTART restart, + __in BURN_DEPENDENCY_REGISTRATION_ACTION dependencyRegistrationAction + ); +HRESULT RegistrationSaveState( + __in BURN_REGISTRATION* pRegistration, + __in_bcount_opt(cbBuffer) BYTE* pbBuffer, + __in_opt SIZE_T cbBuffer + ); +HRESULT RegistrationLoadState( + __in BURN_REGISTRATION* pRegistration, + __out_bcount(*pcbBuffer) BYTE** ppbBuffer, + __out SIZE_T* pcbBuffer + ); +HRESULT RegistrationGetResumeCommandLine( + __in const BURN_REGISTRATION* pRegistration, + __deref_out_z LPWSTR* psczResumeCommandLine + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/relatedbundle.cpp b/src/burn/engine/relatedbundle.cpp new file mode 100644 index 00000000..d3c856a6 --- /dev/null +++ b/src/burn/engine/relatedbundle.cpp @@ -0,0 +1,483 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// internal function declarations + +static HRESULT LoadIfRelatedBundle( + __in BOOL fPerMachine, + __in HKEY hkUninstallKey, + __in_z LPCWSTR sczRelatedBundleId, + __in BURN_REGISTRATION* pRegistration, + __in BURN_RELATED_BUNDLES* pRelatedBundles + ); +static HRESULT DetermineRelationType( + __in HKEY hkBundleId, + __in BURN_REGISTRATION* pRegistration, + __out BOOTSTRAPPER_RELATION_TYPE* pRelationType + ); +static HRESULT LoadRelatedBundleFromKey( + __in_z LPCWSTR wzRelatedBundleId, + __in HKEY hkBundleId, + __in BOOL fPerMachine, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __inout BURN_RELATED_BUNDLE *pRelatedBundle + ); + + +// function definitions + +extern "C" HRESULT RelatedBundlesInitializeForScope( + __in BOOL fPerMachine, + __in BURN_REGISTRATION* pRegistration, + __in BURN_RELATED_BUNDLES* pRelatedBundles + ) +{ + HRESULT hr = S_OK; + HKEY hkRoot = fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + HKEY hkUninstallKey = NULL; + LPWSTR sczRelatedBundleId = NULL; + + hr = RegOpen(hkRoot, BURN_REGISTRATION_REGISTRY_UNINSTALL_KEY, KEY_READ, &hkUninstallKey); + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to open uninstall registry key."); + + for (DWORD dwIndex = 0; /* exit via break below */; ++dwIndex) + { + hr = RegKeyEnum(hkUninstallKey, dwIndex, &sczRelatedBundleId); + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + break; + } + ExitOnFailure(hr, "Failed to enumerate uninstall key for related bundles."); + + // If we did not find our bundle id, try to load the subkey as a related bundle. + if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczRelatedBundleId, -1, pRegistration->sczId, -1)) + { + // Ignore failures here since we'll often find products that aren't actually + // related bundles (or even bundles at all). + HRESULT hrRelatedBundle = LoadIfRelatedBundle(fPerMachine, hkUninstallKey, sczRelatedBundleId, pRegistration, pRelatedBundles); + UNREFERENCED_PARAMETER(hrRelatedBundle); + } + } + +LExit: + ReleaseStr(sczRelatedBundleId); + ReleaseRegKey(hkUninstallKey); + + return hr; +} + +extern "C" void RelatedBundlesUninitialize( + __in BURN_RELATED_BUNDLES* pRelatedBundles + ) +{ + if (pRelatedBundles->rgRelatedBundles) + { + for (DWORD i = 0; i < pRelatedBundles->cRelatedBundles; ++i) + { + BURN_PACKAGE* pPackage = &pRelatedBundles->rgRelatedBundles[i].package; + + for (DWORD j = 0; j < pPackage->payloads.cItems; ++j) + { + PayloadUninitialize(pPackage->payloads.rgItems[j].pPayload); + } + + PackageUninitialize(pPackage); + ReleaseStr(pRelatedBundles->rgRelatedBundles[i].sczTag); + } + + MemFree(pRelatedBundles->rgRelatedBundles); + } + + memset(pRelatedBundles, 0, sizeof(BURN_RELATED_BUNDLES)); +} + + +// internal helper functions + +static HRESULT LoadIfRelatedBundle( + __in BOOL fPerMachine, + __in HKEY hkUninstallKey, + __in_z LPCWSTR sczRelatedBundleId, + __in BURN_REGISTRATION* pRegistration, + __in BURN_RELATED_BUNDLES* pRelatedBundles + ) +{ + HRESULT hr = S_OK; + HKEY hkBundleId = NULL; + BOOTSTRAPPER_RELATION_TYPE relationType = BOOTSTRAPPER_RELATION_NONE; + + hr = RegOpen(hkUninstallKey, sczRelatedBundleId, KEY_READ, &hkBundleId); + ExitOnFailure(hr, "Failed to open uninstall key for potential related bundle: %ls", sczRelatedBundleId); + + hr = DetermineRelationType(hkBundleId, pRegistration, &relationType); + if (FAILED(hr) || BOOTSTRAPPER_RELATION_NONE == relationType) + { + // Must not be a related bundle. + hr = E_NOTFOUND; + } + else // load the related bundle. + { + hr = MemEnsureArraySize(reinterpret_cast(&pRelatedBundles->rgRelatedBundles), pRelatedBundles->cRelatedBundles + 1, sizeof(BURN_RELATED_BUNDLE), 5); + ExitOnFailure(hr, "Failed to ensure there is space for related bundles."); + + BURN_RELATED_BUNDLE* pRelatedBundle = pRelatedBundles->rgRelatedBundles + pRelatedBundles->cRelatedBundles; + + hr = LoadRelatedBundleFromKey(sczRelatedBundleId, hkBundleId, fPerMachine, relationType, pRelatedBundle); + ExitOnFailure(hr, "Failed to initialize package from related bundle id: %ls", sczRelatedBundleId); + + ++pRelatedBundles->cRelatedBundles; + } + +LExit: + ReleaseRegKey(hkBundleId); + + return hr; +} + +static HRESULT DetermineRelationType( + __in HKEY hkBundleId, + __in BURN_REGISTRATION* pRegistration, + __out BOOTSTRAPPER_RELATION_TYPE* pRelationType + ) +{ + HRESULT hr = S_OK; + LPWSTR* rgsczUpgradeCodes = NULL; + DWORD cUpgradeCodes = 0; + STRINGDICT_HANDLE sdUpgradeCodes = NULL; + LPWSTR* rgsczAddonCodes = NULL; + DWORD cAddonCodes = 0; + STRINGDICT_HANDLE sdAddonCodes = NULL; + LPWSTR* rgsczDetectCodes = NULL; + DWORD cDetectCodes = 0; + STRINGDICT_HANDLE sdDetectCodes = NULL; + LPWSTR* rgsczPatchCodes = NULL; + DWORD cPatchCodes = 0; + STRINGDICT_HANDLE sdPatchCodes = NULL; + + *pRelationType = BOOTSTRAPPER_RELATION_NONE; + + // All remaining operations should treat all related bundles as non-vital. + hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes, &cUpgradeCodes); + if (HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE) == hr) + { + TraceError(hr, "Failed to read upgrade codes as REG_MULTI_SZ. Trying again as REG_SZ in case of older bundles."); + + rgsczUpgradeCodes = reinterpret_cast(MemAlloc(sizeof(LPWSTR), TRUE)); + ExitOnNull(rgsczUpgradeCodes, hr, E_OUTOFMEMORY, "Failed to allocate list for a single upgrade code from older bundle."); + + hr = RegReadString(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes[0]); + if (SUCCEEDED(hr)) + { + cUpgradeCodes = 1; + } + } + + // Compare upgrade codes. + if (SUCCEEDED(hr)) + { + hr = DictCreateStringListFromArray(&sdUpgradeCodes, rgsczUpgradeCodes, cUpgradeCodes, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "upgrade codes"); + + // Upgrade relationship: when their upgrade codes match our upgrade codes. + hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for upgrade code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_UPGRADE; + ExitFunction(); + } + + // Detect relationship: when their upgrade codes match our detect codes. + hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for detect code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_DETECT; + ExitFunction(); + } + + // Dependent relationship: when their upgrade codes match our addon codes. + hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pRegistration->rgsczAddonCodes), pRegistration->cAddonCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT; + ExitFunction(); + } + + // Dependent relationship: when their upgrade codes match our patch codes. + hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pRegistration->rgsczPatchCodes), pRegistration->cPatchCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT; + ExitFunction(); + } + + ReleaseNullDict(sdUpgradeCodes); + ReleaseNullStrArray(rgsczUpgradeCodes, cUpgradeCodes); + } + + // Compare addon codes. + hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE, &rgsczAddonCodes, &cAddonCodes); + if (SUCCEEDED(hr)) + { + hr = DictCreateStringListFromArray(&sdAddonCodes, rgsczAddonCodes, cAddonCodes, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "addon codes"); + + // Addon relationship: when their addon codes match our detect codes. + hr = DictCompareStringListToArray(sdAddonCodes, const_cast(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_ADDON; + ExitFunction(); + } + + // Addon relationship: when their addon codes match our upgrade codes. + hr = DictCompareStringListToArray(sdAddonCodes, const_cast(pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_ADDON; + ExitFunction(); + } + + ReleaseNullDict(sdAddonCodes); + ReleaseNullStrArray(rgsczAddonCodes, cAddonCodes); + } + + // Compare patch codes. + hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE, &rgsczPatchCodes, &cPatchCodes); + if (SUCCEEDED(hr)) + { + hr = DictCreateStringListFromArray(&sdPatchCodes, rgsczPatchCodes, cPatchCodes, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "patch codes"); + + // Patch relationship: when their patch codes match our detect codes. + hr = DictCompareStringListToArray(sdPatchCodes, const_cast(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for patch code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_PATCH; + ExitFunction(); + } + + // Patch relationship: when their patch codes match our upgrade codes. + hr = DictCompareStringListToArray(sdPatchCodes, const_cast(pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for patch code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_PATCH; + ExitFunction(); + } + + ReleaseNullDict(sdPatchCodes); + ReleaseNullStrArray(rgsczPatchCodes, cPatchCodes); + } + + // Compare detect codes. + hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE, &rgsczDetectCodes, &cDetectCodes); + if (SUCCEEDED(hr)) + { + hr = DictCreateStringListFromArray(&sdDetectCodes, rgsczDetectCodes, cDetectCodes, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "detect codes"); + + // Detect relationship: when their detect codes match our detect codes. + hr = DictCompareStringListToArray(sdDetectCodes, const_cast(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for detect code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_DETECT; + ExitFunction(); + } + + // Dependent relationship: when their detect codes match our addon codes. + hr = DictCompareStringListToArray(sdDetectCodes, const_cast(pRegistration->rgsczAddonCodes), pRegistration->cAddonCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT; + ExitFunction(); + } + + // Dependent relationship: when their detect codes match our patch codes. + hr = DictCompareStringListToArray(sdDetectCodes, const_cast(pRegistration->rgsczPatchCodes), pRegistration->cPatchCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT; + ExitFunction(); + } + + ReleaseNullDict(sdDetectCodes); + ReleaseNullStrArray(rgsczDetectCodes, cDetectCodes); + } + +LExit: + if (SUCCEEDED(hr) && BOOTSTRAPPER_RELATION_NONE == *pRelationType) + { + hr = E_NOTFOUND; + } + + ReleaseDict(sdUpgradeCodes); + ReleaseStrArray(rgsczUpgradeCodes, cUpgradeCodes); + ReleaseDict(sdAddonCodes); + ReleaseStrArray(rgsczAddonCodes, cAddonCodes); + ReleaseDict(sdDetectCodes); + ReleaseStrArray(rgsczDetectCodes, cDetectCodes); + ReleaseDict(sdPatchCodes); + ReleaseStrArray(rgsczPatchCodes, cPatchCodes); + + return hr; +} + +static HRESULT LoadRelatedBundleFromKey( + __in_z LPCWSTR wzRelatedBundleId, + __in HKEY hkBundleId, + __in BOOL fPerMachine, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __inout BURN_RELATED_BUNDLE* pRelatedBundle + ) +{ + HRESULT hr = S_OK; + DWORD64 qwEngineVersion = 0; + LPWSTR sczBundleVersion = NULL; + LPWSTR sczCachePath = NULL; + BOOL fCached = FALSE; + DWORD64 qwFileSize = 0; + BURN_DEPENDENCY_PROVIDER dependencyProvider = { }; + + hr = RegReadVersion(hkBundleId, BURN_REGISTRATION_REGISTRY_ENGINE_VERSION, &qwEngineVersion); + if (FAILED(hr)) + { + qwEngineVersion = 0; + hr = S_OK; + } + + hr = RegReadString(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_VERSION, &sczBundleVersion); + ExitOnFailure(hr, "Failed to read version from registry for bundle: %ls", wzRelatedBundleId); + + hr = VerParseVersion(sczBundleVersion, 0, FALSE, &pRelatedBundle->pVersion); + ExitOnFailure(hr, "Failed to parse pseudo bundle version: %ls", sczBundleVersion); + + if (pRelatedBundle->pVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_RELATED_PACKAGE_INVALID_VERSION, wzRelatedBundleId, sczBundleVersion); + } + + hr = RegReadString(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_CACHE_PATH, &sczCachePath); + ExitOnFailure(hr, "Failed to read cache path from registry for bundle: %ls", wzRelatedBundleId); + + if (FileExistsEx(sczCachePath, NULL)) + { + fCached = TRUE; + } + else + { + LogId(REPORT_STANDARD, MSG_DETECT_RELATED_BUNDLE_NOT_CACHED, wzRelatedBundleId, sczCachePath); + } + + pRelatedBundle->fPlannable = fCached; + + hr = RegReadString(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_PROVIDER_KEY, &dependencyProvider.sczKey); + if (E_FILENOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to read provider key from registry for bundle: %ls", wzRelatedBundleId); + + dependencyProvider.fImported = TRUE; + + hr = StrAllocString(&dependencyProvider.sczVersion, pRelatedBundle->pVersion->sczVersion, 0); + ExitOnFailure(hr, "Failed to copy version for bundle: %ls", wzRelatedBundleId); + + hr = RegReadString(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_DISPLAY_NAME, &dependencyProvider.sczDisplayName); + if (E_FILENOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to copy display name for bundle: %ls", wzRelatedBundleId); + } + } + + hr = RegReadString(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_TAG, &pRelatedBundle->sczTag); + if (E_FILENOTFOUND == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to read tag from registry for bundle: %ls", wzRelatedBundleId); + + pRelatedBundle->relationType = relationType; + + hr = PseudoBundleInitialize(qwEngineVersion, &pRelatedBundle->package, fPerMachine, wzRelatedBundleId, pRelatedBundle->relationType, + BOOTSTRAPPER_PACKAGE_STATE_PRESENT, fCached, sczCachePath, sczCachePath, NULL, qwFileSize, FALSE, + L"-quiet", L"-repair -quiet", L"-uninstall -quiet", + (dependencyProvider.sczKey && *dependencyProvider.sczKey) ? &dependencyProvider : NULL, + NULL, 0); + ExitOnFailure(hr, "Failed to initialize related bundle to represent bundle: %ls", wzRelatedBundleId); + +LExit: + DependencyUninitializeProvider(&dependencyProvider); + ReleaseStr(sczCachePath); + ReleaseStr(sczBundleVersion); + + return hr; +} diff --git a/src/burn/engine/relatedbundle.h b/src/burn/engine/relatedbundle.h new file mode 100644 index 00000000..01691c25 --- /dev/null +++ b/src/burn/engine/relatedbundle.h @@ -0,0 +1,20 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + +HRESULT RelatedBundlesInitializeForScope( + __in BOOL fPerMachine, + __in BURN_REGISTRATION* pRegistration, + __in BURN_RELATED_BUNDLES* pRelatedBundles + ); +void RelatedBundlesUninitialize( + __in BURN_RELATED_BUNDLES* pRelatedBundles + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/search.cpp b/src/burn/engine/search.cpp new file mode 100644 index 00000000..6d5f8d49 --- /dev/null +++ b/src/burn/engine/search.cpp @@ -0,0 +1,1303 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// internal function declarations + +static HRESULT DirectorySearchExists( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT DirectorySearchPath( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT FileSearchExists( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT FileSearchVersion( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT FileSearchPath( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT RegistrySearchExists( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT RegistrySearchValue( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT MsiComponentSearch( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT MsiProductSearch( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT MsiFeatureSearch( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ); +static HRESULT PerformExtensionSearch( + __in BURN_SEARCH* pSearch + ); +static HRESULT PerformSetVariable( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables +); + + +// function definitions + +extern "C" HRESULT SearchesParseFromXml( + __in BURN_SEARCHES* pSearches, + __in BURN_EXTENSIONS* pBurnExtensions, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + BSTR bstrNodeName = NULL; + LPWSTR scz = NULL; + BURN_VARIANT_TYPE valueType = BURN_VARIANT_TYPE_NONE; + + // select search nodes + hr = XmlSelectNodes(pixnBundle, L"DirectorySearch|FileSearch|RegistrySearch|MsiComponentSearch|MsiProductSearch|MsiFeatureSearch|ExtensionSearch|SetVariable", &pixnNodes); + ExitOnFailure(hr, "Failed to select search nodes."); + + // get search node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get search node count."); + + if (!cNodes) + { + ExitFunction(); + } + + // allocate memory for searches + pSearches->rgSearches = (BURN_SEARCH*)MemAlloc(sizeof(BURN_SEARCH) * cNodes, TRUE); + ExitOnNull(pSearches->rgSearches, hr, E_OUTOFMEMORY, "Failed to allocate memory for search structs."); + + pSearches->cSearches = cNodes; + + // parse search elements + for (DWORD i = 0; i < cNodes; ++i) + { + BURN_SEARCH* pSearch = &pSearches->rgSearches[i]; + + hr = XmlNextElement(pixnNodes, &pixnNode, &bstrNodeName); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &pSearch->sczKey); + ExitOnFailure(hr, "Failed to get @Id."); + + // @Variable + hr = XmlGetAttributeEx(pixnNode, L"Variable", &pSearch->sczVariable); + ExitOnFailure(hr, "Failed to get @Variable."); + + // @Condition + hr = XmlGetAttributeEx(pixnNode, L"Condition", &pSearch->sczCondition); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Condition."); + } + + // read type specific attributes + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"DirectorySearch", -1)) + { + pSearch->Type = BURN_SEARCH_TYPE_DIRECTORY; + + // @Path + hr = XmlGetAttributeEx(pixnNode, L"Path", &pSearch->DirectorySearch.sczPath); + ExitOnFailure(hr, "Failed to get @Path."); + + // @Type + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnFailure(hr, "Failed to get @Type."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"exists", -1)) + { + pSearch->DirectorySearch.Type = BURN_DIRECTORY_SEARCH_TYPE_EXISTS; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"path", -1)) + { + pSearch->DirectorySearch.Type = BURN_DIRECTORY_SEARCH_TYPE_PATH; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Type: %ls", scz); + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"FileSearch", -1)) + { + pSearch->Type = BURN_SEARCH_TYPE_FILE; + + // @Path + hr = XmlGetAttributeEx(pixnNode, L"Path", &pSearch->FileSearch.sczPath); + ExitOnFailure(hr, "Failed to get @Path."); + + // @Type + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnFailure(hr, "Failed to get @Type."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"exists", -1)) + { + pSearch->FileSearch.Type = BURN_FILE_SEARCH_TYPE_EXISTS; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"version", -1)) + { + pSearch->FileSearch.Type = BURN_FILE_SEARCH_TYPE_VERSION; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"path", -1)) + { + pSearch->FileSearch.Type = BURN_FILE_SEARCH_TYPE_PATH; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Type: %ls", scz); + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"RegistrySearch", -1)) + { + pSearch->Type = BURN_SEARCH_TYPE_REGISTRY; + + // @Root + hr = XmlGetAttributeEx(pixnNode, L"Root", &scz); + ExitOnFailure(hr, "Failed to get @Root."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"HKCR", -1)) + { + pSearch->RegistrySearch.hRoot = HKEY_CLASSES_ROOT; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"HKCU", -1)) + { + pSearch->RegistrySearch.hRoot = HKEY_CURRENT_USER; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"HKLM", -1)) + { + pSearch->RegistrySearch.hRoot = HKEY_LOCAL_MACHINE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"HKU", -1)) + { + pSearch->RegistrySearch.hRoot = HKEY_USERS; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Root: %ls", scz); + } + + // @Key + hr = XmlGetAttributeEx(pixnNode, L"Key", &pSearch->RegistrySearch.sczKey); + ExitOnFailure(hr, "Failed to get Key attribute."); + + // @Value + hr = XmlGetAttributeEx(pixnNode, L"Value", &pSearch->RegistrySearch.sczValue); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get Value attribute."); + } + + // @Type + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnFailure(hr, "Failed to get @Type."); + + hr = XmlGetYesNoAttribute(pixnNode, L"Win64", &pSearch->RegistrySearch.fWin64); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get Win64 attribute."); + } + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"exists", -1)) + { + pSearch->RegistrySearch.Type = BURN_REGISTRY_SEARCH_TYPE_EXISTS; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"value", -1)) + { + pSearch->RegistrySearch.Type = BURN_REGISTRY_SEARCH_TYPE_VALUE; + + // @ExpandEnvironment + hr = XmlGetYesNoAttribute(pixnNode, L"ExpandEnvironment", &pSearch->RegistrySearch.fExpandEnvironment); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @ExpandEnvironment."); + } + + // @VariableType + hr = XmlGetAttributeEx(pixnNode, L"VariableType", &scz); + ExitOnFailure(hr, "Failed to get @VariableType."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"formatted", -1)) + { + pSearch->RegistrySearch.VariableType = BURN_VARIANT_TYPE_FORMATTED; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"numeric", -1)) + { + pSearch->RegistrySearch.VariableType = BURN_VARIANT_TYPE_NUMERIC; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"string", -1)) + { + pSearch->RegistrySearch.VariableType = BURN_VARIANT_TYPE_STRING; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"version", -1)) + { + pSearch->RegistrySearch.VariableType = BURN_VARIANT_TYPE_VERSION; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @VariableType: %ls", scz); + } + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Type: %ls", scz); + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"MsiComponentSearch", -1)) + { + pSearch->Type = BURN_SEARCH_TYPE_MSI_COMPONENT; + + // @ProductCode + hr = XmlGetAttributeEx(pixnNode, L"ProductCode", &pSearch->MsiComponentSearch.sczProductCode); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @ProductCode."); + } + + // @ComponentId + hr = XmlGetAttributeEx(pixnNode, L"ComponentId", &pSearch->MsiComponentSearch.sczComponentId); + ExitOnFailure(hr, "Failed to get @ComponentId."); + + // @Type + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnFailure(hr, "Failed to get @Type."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"keyPath", -1)) + { + pSearch->MsiComponentSearch.Type = BURN_MSI_COMPONENT_SEARCH_TYPE_KEYPATH; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"state", -1)) + { + pSearch->MsiComponentSearch.Type = BURN_MSI_COMPONENT_SEARCH_TYPE_STATE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"directory", -1)) + { + pSearch->MsiComponentSearch.Type = BURN_MSI_COMPONENT_SEARCH_TYPE_DIRECTORY; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Type: %ls", scz); + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"MsiProductSearch", -1)) + { + pSearch->Type = BURN_SEARCH_TYPE_MSI_PRODUCT; + pSearch->MsiProductSearch.GuidType = BURN_MSI_PRODUCT_SEARCH_GUID_TYPE_NONE; + + // @ProductCode (if we don't find a product code then look for an upgrade code) + hr = XmlGetAttributeEx(pixnNode, L"ProductCode", &pSearch->MsiProductSearch.sczGuid); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @ProductCode."); + pSearch->MsiProductSearch.GuidType = BURN_MSI_PRODUCT_SEARCH_GUID_TYPE_PRODUCTCODE; + } + else + { + // @UpgradeCode + hr = XmlGetAttributeEx(pixnNode, L"UpgradeCode", &pSearch->MsiProductSearch.sczGuid); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @UpgradeCode."); + pSearch->MsiProductSearch.GuidType = BURN_MSI_PRODUCT_SEARCH_GUID_TYPE_UPGRADECODE; + } + } + + // make sure we found either a product or upgrade code + if (BURN_MSI_PRODUCT_SEARCH_GUID_TYPE_NONE == pSearch->MsiProductSearch.GuidType) + { + hr = E_NOTFOUND; + ExitOnFailure(hr, "Failed to get @ProductCode or @UpgradeCode."); + } + + // @Type + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnFailure(hr, "Failed to get @Type."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"version", -1)) + { + pSearch->MsiProductSearch.Type = BURN_MSI_PRODUCT_SEARCH_TYPE_VERSION; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"language", -1)) + { + pSearch->MsiProductSearch.Type = BURN_MSI_PRODUCT_SEARCH_TYPE_LANGUAGE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"state", -1)) + { + pSearch->MsiProductSearch.Type = BURN_MSI_PRODUCT_SEARCH_TYPE_STATE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"assignment", -1)) + { + pSearch->MsiProductSearch.Type = BURN_MSI_PRODUCT_SEARCH_TYPE_ASSIGNMENT; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Type: %ls", scz); + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"MsiFeatureSearch", -1)) + { + pSearch->Type = BURN_SEARCH_TYPE_MSI_FEATURE; + + // @ProductCode + hr = XmlGetAttributeEx(pixnNode, L"ProductCode", &pSearch->MsiFeatureSearch.sczProductCode); + ExitOnFailure(hr, "Failed to get @ProductCode."); + + // @FeatureId + hr = XmlGetAttributeEx(pixnNode, L"FeatureId", &pSearch->MsiFeatureSearch.sczFeatureId); + ExitOnFailure(hr, "Failed to get @FeatureId."); + + // @Type + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnFailure(hr, "Failed to get @Type."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"state", -1)) + { + pSearch->MsiFeatureSearch.Type = BURN_MSI_FEATURE_SEARCH_TYPE_STATE; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Type: %ls", scz); + } + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"ExtensionSearch", -1)) + { + pSearch->Type = BURN_SEARCH_TYPE_EXTENSION; + + // @ExtensionId + hr = XmlGetAttributeEx(pixnNode, L"ExtensionId", &scz); + ExitOnFailure(hr, "Failed to get @ExtensionId."); + + hr = BurnExtensionFindById(pBurnExtensions, scz, &pSearch->ExtensionSearch.pExtension); + ExitOnFailure(hr, "Failed to find extension '%ls' for search '%ls'", scz, pSearch->sczKey); + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, bstrNodeName, -1, L"SetVariable", -1)) + { + pSearch->Type = BURN_SEARCH_TYPE_SET_VARIABLE; + + // @Value + hr = XmlGetAttributeEx(pixnNode, L"Value", &scz); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Value."); + + hr = BVariantSetString(&pSearch->SetVariable.value, scz, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + + // @Type + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnFailure(hr, "Failed to get @Type."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"formatted", -1)) + { + valueType = BURN_VARIANT_TYPE_FORMATTED; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"numeric", -1)) + { + valueType = BURN_VARIANT_TYPE_NUMERIC; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"string", -1)) + { + valueType = BURN_VARIANT_TYPE_STRING; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"version", -1)) + { + valueType = BURN_VARIANT_TYPE_VERSION; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Type: %ls", scz); + } + } + else + { + valueType = BURN_VARIANT_TYPE_NONE; + } + + // change value variant to correct type + hr = BVariantChangeType(&pSearch->SetVariable.value, valueType); + ExitOnFailure(hr, "Failed to change variant type."); + } + else + { + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Unexpected element name: %ls", bstrNodeName); + } + + // prepare next iteration + ReleaseNullObject(pixnNode); + ReleaseNullBSTR(bstrNodeName); + } + + hr = S_OK; + +LExit: + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseBSTR(bstrNodeName); + ReleaseStr(scz); + return hr; +} + +extern "C" HRESULT SearchesExecute( + __in BURN_SEARCHES* pSearches, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + BOOL f = FALSE; + + for (DWORD i = 0; i < pSearches->cSearches; ++i) + { + BURN_SEARCH* pSearch = &pSearches->rgSearches[i]; + + // evaluate condition + if (pSearch->sczCondition && *pSearch->sczCondition) + { + hr = ConditionEvaluate(pVariables, pSearch->sczCondition, &f); + if (E_INVALIDDATA == hr) + { + TraceError(hr, "Failed to parse search condition. Id = '%ls', Condition = '%ls'", pSearch->sczKey, pSearch->sczCondition); + hr = S_OK; + continue; + } + ExitOnFailure(hr, "Failed to evaluate search condition. Id = '%ls', Condition = '%ls'", pSearch->sczKey, pSearch->sczCondition); + + if (!f) + { + continue; // condition evaluated to false, skip + } + } + + switch (pSearch->Type) + { + case BURN_SEARCH_TYPE_DIRECTORY: + switch (pSearch->DirectorySearch.Type) + { + case BURN_DIRECTORY_SEARCH_TYPE_EXISTS: + hr = DirectorySearchExists(pSearch, pVariables); + break; + case BURN_DIRECTORY_SEARCH_TYPE_PATH: + hr = DirectorySearchPath(pSearch, pVariables); + break; + default: + hr = E_UNEXPECTED; + } + break; + case BURN_SEARCH_TYPE_FILE: + switch (pSearch->FileSearch.Type) + { + case BURN_FILE_SEARCH_TYPE_EXISTS: + hr = FileSearchExists(pSearch, pVariables); + break; + case BURN_FILE_SEARCH_TYPE_VERSION: + hr = FileSearchVersion(pSearch, pVariables); + break; + case BURN_FILE_SEARCH_TYPE_PATH: + hr = FileSearchPath(pSearch, pVariables); + break; + default: + hr = E_UNEXPECTED; + } + break; + case BURN_SEARCH_TYPE_REGISTRY: + switch (pSearch->RegistrySearch.Type) + { + case BURN_REGISTRY_SEARCH_TYPE_EXISTS: + hr = RegistrySearchExists(pSearch, pVariables); + break; + case BURN_REGISTRY_SEARCH_TYPE_VALUE: + hr = RegistrySearchValue(pSearch, pVariables); + break; + default: + hr = E_UNEXPECTED; + } + break; + case BURN_SEARCH_TYPE_MSI_COMPONENT: + hr = MsiComponentSearch(pSearch, pVariables); + break; + case BURN_SEARCH_TYPE_MSI_PRODUCT: + hr = MsiProductSearch(pSearch, pVariables); + break; + case BURN_SEARCH_TYPE_MSI_FEATURE: + hr = MsiFeatureSearch(pSearch, pVariables); + break; + case BURN_SEARCH_TYPE_EXTENSION: + hr = PerformExtensionSearch(pSearch); + break; + case BURN_SEARCH_TYPE_SET_VARIABLE: + hr = PerformSetVariable(pSearch, pVariables); + break; + default: + hr = E_UNEXPECTED; + } + + if (FAILED(hr)) + { + TraceError(hr, "Search failed. Id = '%ls'", pSearch->sczKey); + continue; + } + } + + hr = S_OK; + +LExit: + return hr; +} + +extern "C" void SearchesUninitialize( + __in BURN_SEARCHES* pSearches + ) +{ + if (pSearches->rgSearches) + { + for (DWORD i = 0; i < pSearches->cSearches; ++i) + { + BURN_SEARCH* pSearch = &pSearches->rgSearches[i]; + + ReleaseStr(pSearch->sczKey); + ReleaseStr(pSearch->sczVariable); + ReleaseStr(pSearch->sczCondition); + + switch (pSearch->Type) + { + case BURN_SEARCH_TYPE_DIRECTORY: + ReleaseStr(pSearch->DirectorySearch.sczPath); + break; + case BURN_SEARCH_TYPE_FILE: + ReleaseStr(pSearch->FileSearch.sczPath); + break; + case BURN_SEARCH_TYPE_REGISTRY: + ReleaseStr(pSearch->RegistrySearch.sczKey); + ReleaseStr(pSearch->RegistrySearch.sczValue); + break; + case BURN_SEARCH_TYPE_MSI_COMPONENT: + ReleaseStr(pSearch->MsiComponentSearch.sczProductCode); + ReleaseStr(pSearch->MsiComponentSearch.sczComponentId); + break; + case BURN_SEARCH_TYPE_MSI_PRODUCT: + ReleaseStr(pSearch->MsiProductSearch.sczGuid); + break; + case BURN_SEARCH_TYPE_MSI_FEATURE: + ReleaseStr(pSearch->MsiFeatureSearch.sczProductCode); + ReleaseStr(pSearch->MsiFeatureSearch.sczFeatureId); + break; + case BURN_SEARCH_TYPE_SET_VARIABLE: + BVariantUninitialize(&pSearch->SetVariable.value); + break; + } + } + MemFree(pSearches->rgSearches); + } +} + + +// internal function definitions + +static HRESULT DirectorySearchExists( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + BOOL fExists = FALSE; + + // format path + hr = VariableFormatString(pVariables, pSearch->DirectorySearch.sczPath, &sczPath, NULL); + ExitOnFailure(hr, "Failed to format variable string."); + + DWORD dwAttributes = ::GetFileAttributesW(sczPath); + if (INVALID_FILE_ATTRIBUTES == dwAttributes) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + { + hr = S_OK; // didn't find file, fExists still is false. + } + } + else if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + fExists = TRUE; + } + + // else must have found a file. + // What if there is a hidden variable in sczPath? + ExitOnFailure(hr, "Failed while searching directory search: %ls, for path: %ls", pSearch->sczKey, sczPath); + + // set variable + hr = VariableSetNumeric(pVariables, pSearch->sczVariable, fExists, FALSE); + ExitOnFailure(hr, "Failed to set variable."); + +LExit: + StrSecureZeroFreeString(sczPath); + + return hr; +} + +static HRESULT DirectorySearchPath( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + + // format path + hr = VariableFormatString(pVariables, pSearch->DirectorySearch.sczPath, &sczPath, NULL); + ExitOnFailure(hr, "Failed to format variable string."); + + DWORD dwAttributes = ::GetFileAttributesW(sczPath); + if (INVALID_FILE_ATTRIBUTES == dwAttributes) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + } + else if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + hr = VariableSetString(pVariables, pSearch->sczVariable, sczPath, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set directory search path variable."); + } + else // must have found a file. + { + hr = E_PATHNOTFOUND; + } + + // What if there is a hidden variable in sczPath? + if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + { + LogStringLine(REPORT_STANDARD, "Directory search: %ls, did not find path: %ls, reason: 0x%x", pSearch->sczKey, sczPath, hr); + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed while searching directory search: %ls, for path: %ls", pSearch->sczKey, sczPath); + +LExit: + StrSecureZeroFreeString(sczPath); + + return hr; +} + +static HRESULT FileSearchExists( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + LPWSTR sczPath = NULL; + BOOL fExists = FALSE; + + // format path + hr = VariableFormatString(pVariables, pSearch->FileSearch.sczPath, &sczPath, NULL); + ExitOnFailure(hr, "Failed to format variable string."); + + // find file + DWORD dwAttributes = ::GetFileAttributesW(sczPath); + if (INVALID_FILE_ATTRIBUTES == dwAttributes) + { + er = ::GetLastError(); + if (ERROR_FILE_NOT_FOUND == er || ERROR_PATH_NOT_FOUND == er) + { + // What if there is a hidden variable in sczPath? + LogStringLine(REPORT_STANDARD, "File search: %ls, did not find path: %ls", pSearch->sczKey, sczPath); + } + else + { + ExitOnWin32Error(er, hr, "Failed get to file attributes. '%ls'", pSearch->DirectorySearch.sczPath); + } + } + else if (FILE_ATTRIBUTE_DIRECTORY != (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + fExists = TRUE; + } + + // set variable + hr = VariableSetNumeric(pVariables, pSearch->sczVariable, fExists, FALSE); + ExitOnFailure(hr, "Failed to set variable."); + +LExit: + StrSecureZeroFreeString(sczPath); + return hr; +} + +static HRESULT FileSearchVersion( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + ULARGE_INTEGER uliVersion = { }; + LPWSTR sczPath = NULL; + VERUTIL_VERSION* pVersion = NULL; + + // format path + hr = VariableFormatString(pVariables, pSearch->FileSearch.sczPath, &sczPath, NULL); + ExitOnFailure(hr, "Failed to format path string."); + + // get file version + hr = FileVersion(sczPath, &uliVersion.HighPart, &uliVersion.LowPart); + if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + { + // What if there is a hidden variable in sczPath? + LogStringLine(REPORT_STANDARD, "File search: %ls, did not find path: %ls", pSearch->sczKey, sczPath); + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to get file version."); + + hr = VerVersionFromQword(uliVersion.QuadPart, &pVersion); + ExitOnFailure(hr, "Failed to create version from file version."); + + // set variable + hr = VariableSetVersion(pVariables, pSearch->sczVariable, pVersion, FALSE); + ExitOnFailure(hr, "Failed to set variable."); + +LExit: + StrSecureZeroFreeString(sczPath); + ReleaseVerutilVersion(pVersion); + return hr; +} + +static HRESULT FileSearchPath( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + + // format path + hr = VariableFormatString(pVariables, pSearch->FileSearch.sczPath, &sczPath, NULL); + ExitOnFailure(hr, "Failed to format variable string."); + + DWORD dwAttributes = ::GetFileAttributesW(sczPath); + if (INVALID_FILE_ATTRIBUTES == dwAttributes) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + } + else if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) // found a directory. + { + hr = E_FILENOTFOUND; + } + else // found our file. + { + hr = VariableSetString(pVariables, pSearch->sczVariable, sczPath, FALSE, FALSE); + ExitOnFailure(hr, "Failed to set variable to file search path."); + } + + // What if there is a hidden variable in sczPath? + if (E_FILENOTFOUND == hr || E_PATHNOTFOUND == hr) + { + LogStringLine(REPORT_STANDARD, "File search: %ls, did not find path: %ls", pSearch->sczKey, sczPath); + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed while searching file search: %ls, for path: %ls", pSearch->sczKey, sczPath); + +LExit: + StrSecureZeroFreeString(sczPath); + + return hr; +} + +static HRESULT RegistrySearchExists( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + LPWSTR sczKey = NULL; + LPWSTR sczValue = NULL; + HKEY hKey = NULL; + DWORD dwType = 0; + BOOL fExists = FALSE; + REGSAM samDesired = KEY_QUERY_VALUE; + + if (pSearch->RegistrySearch.fWin64) + { + samDesired = samDesired | KEY_WOW64_64KEY; + } + + // format key string + hr = VariableFormatString(pVariables, pSearch->RegistrySearch.sczKey, &sczKey, NULL); + ExitOnFailure(hr, "Failed to format key string."); + + // open key + hr = RegOpen(pSearch->RegistrySearch.hRoot, sczKey, samDesired, &hKey); + if (SUCCEEDED(hr)) + { + fExists = TRUE; + } + else if (E_FILENOTFOUND == hr) + { + // What if there is a hidden variable in sczKey? + LogStringLine(REPORT_STANDARD, "Registry key not found. Key = '%ls'", sczKey); + fExists = FALSE; + hr = S_OK; + } + else + { + // What if there is a hidden variable in sczKey? + ExitOnFailure(hr, "Failed to open registry key. Key = '%ls'", sczKey); + } + + if (fExists && pSearch->RegistrySearch.sczValue) + { + // format value string + hr = VariableFormatString(pVariables, pSearch->RegistrySearch.sczValue, &sczValue, NULL); + ExitOnFailure(hr, "Failed to format value string."); + + // query value + er = ::RegQueryValueExW(hKey, sczValue, NULL, &dwType, NULL, NULL); + switch (er) + { + case ERROR_SUCCESS: + fExists = TRUE; + break; + case ERROR_FILE_NOT_FOUND: + // What if there is a hidden variable in sczKey or sczValue? + LogStringLine(REPORT_STANDARD, "Registry value not found. Key = '%ls', Value = '%ls'", sczKey, sczValue); + fExists = FALSE; + break; + default: + ExitOnWin32Error(er, hr, "Failed to query registry key value."); + } + } + + // set variable + hr = VariableSetNumeric(pVariables, pSearch->sczVariable, fExists, FALSE); + ExitOnFailure(hr, "Failed to set variable."); + +LExit: + if (FAILED(hr)) + { + // What if there is a hidden variable in sczKey? + LogStringLine(REPORT_STANDARD, "RegistrySearchExists failed: ID '%ls', HRESULT 0x%x", sczKey, hr); + } + + StrSecureZeroFreeString(sczKey); + StrSecureZeroFreeString(sczValue); + ReleaseRegKey(hKey); + + return hr; +} + +static HRESULT RegistrySearchValue( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + LPWSTR sczKey = NULL; + LPWSTR sczValue = NULL; + HKEY hKey = NULL; + DWORD dwType = 0; + DWORD cbData = 0; + LPBYTE pData = NULL; + DWORD cch = 0; + BURN_VARIANT value = { }; + REGSAM samDesired = KEY_QUERY_VALUE; + + if (pSearch->RegistrySearch.fWin64) + { + samDesired = samDesired | KEY_WOW64_64KEY; + } + + // format key string + hr = VariableFormatString(pVariables, pSearch->RegistrySearch.sczKey, &sczKey, NULL); + ExitOnFailure(hr, "Failed to format key string."); + + // format value string + if (pSearch->RegistrySearch.sczValue) + { + hr = VariableFormatString(pVariables, pSearch->RegistrySearch.sczValue, &sczValue, NULL); + ExitOnFailure(hr, "Failed to format value string."); + } + + // open key + hr = RegOpen(pSearch->RegistrySearch.hRoot, sczKey, samDesired, &hKey); + if (E_FILENOTFOUND == hr) + { + // What if there is a hidden variable in sczKey? + LogStringLine(REPORT_STANDARD, "Registry key not found. Key = '%ls'", sczKey); + + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to open registry key."); + + // get value + er = ::RegQueryValueExW(hKey, sczValue, NULL, &dwType, NULL, &cbData); + if (ERROR_FILE_NOT_FOUND == er) + { + // What if there is a hidden variable in sczKey or sczValue? + LogStringLine(REPORT_STANDARD, "Registry value not found. Key = '%ls', Value = '%ls'", sczKey, sczValue); + + ExitFunction1(hr = S_OK); + } + ExitOnWin32Error(er, hr, "Failed to query registry key value size."); + + pData = (LPBYTE)MemAlloc(cbData + sizeof(WCHAR), TRUE); // + sizeof(WCHAR) here to ensure that we always have a null terminator for REG_SZ + ExitOnNull(pData, hr, E_OUTOFMEMORY, "Failed to allocate memory registry value."); + + er = ::RegQueryValueExW(hKey, sczValue, NULL, &dwType, pData, &cbData); + ExitOnWin32Error(er, hr, "Failed to query registry key value."); + + switch (dwType) + { + case REG_DWORD: + if (sizeof(LONG) != cbData) + { + ExitFunction1(hr = E_UNEXPECTED); + } + hr = BVariantSetNumeric(&value, *((LONG*)pData)); + break; + case REG_QWORD: + if (sizeof(LONGLONG) != cbData) + { + ExitFunction1(hr = E_UNEXPECTED); + } + hr = BVariantSetNumeric(&value, *((LONGLONG*)pData)); + break; + case REG_EXPAND_SZ: + if (pSearch->RegistrySearch.fExpandEnvironment) + { + hr = StrAlloc(&value.sczValue, cbData); + ExitOnFailure(hr, "Failed to allocate string buffer."); + value.Type = BURN_VARIANT_TYPE_STRING; + + cch = ::ExpandEnvironmentStringsW((LPCWSTR)pData, value.sczValue, cbData); + if (cch > cbData) + { + hr = StrAlloc(&value.sczValue, cch); + ExitOnFailure(hr, "Failed to allocate string buffer."); + + if (cch != ::ExpandEnvironmentStringsW((LPCWSTR)pData, value.sczValue, cch)) + { + ExitWithLastError(hr, "Failed to get expand environment string."); + } + } + break; + } + __fallthrough; + case REG_SZ: + hr = BVariantSetString(&value, (LPCWSTR)pData, 0, FALSE); + break; + default: + ExitOnFailure(hr = E_NOTIMPL, "Unsupported registry key value type. Type = '%u'", dwType); + } + ExitOnFailure(hr, "Failed to read registry value."); + + // change value to requested type + hr = BVariantChangeType(&value, pSearch->RegistrySearch.VariableType); + ExitOnFailure(hr, "Failed to change value type."); + + // Set variable. + hr = VariableSetVariant(pVariables, pSearch->sczVariable, &value); + ExitOnFailure(hr, "Failed to set variable."); + +LExit: + if (FAILED(hr)) + { + // What if there is a hidden variable in sczKey? + LogStringLine(REPORT_STANDARD, "RegistrySearchValue failed: ID '%ls', HRESULT 0x%x", sczKey, hr); + } + + StrSecureZeroFreeString(sczKey); + StrSecureZeroFreeString(sczValue); + ReleaseRegKey(hKey); + ReleaseMem(pData); + BVariantUninitialize(&value); + + return hr; +} + +static HRESULT MsiComponentSearch( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + INSTALLSTATE is = INSTALLSTATE_BROKEN; + LPWSTR sczComponentId = NULL; + LPWSTR sczProductCode = NULL; + LPWSTR sczPath = NULL; + + // format component id string + hr = VariableFormatString(pVariables, pSearch->MsiComponentSearch.sczComponentId, &sczComponentId, NULL); + ExitOnFailure(hr, "Failed to format component id string."); + + if (pSearch->MsiComponentSearch.sczProductCode) + { + // format product code string + hr = VariableFormatString(pVariables, pSearch->MsiComponentSearch.sczProductCode, &sczProductCode, NULL); + ExitOnFailure(hr, "Failed to format product code string."); + } + + if (sczProductCode) + { + hr = WiuGetComponentPath(sczProductCode, sczComponentId, &is, &sczPath); + } + else + { + hr = WiuLocateComponent(sczComponentId, &is, &sczPath); + } + + if (INSTALLSTATE_SOURCEABSENT == is) + { + is = INSTALLSTATE_SOURCE; + } + else if (INSTALLSTATE_UNKNOWN == is || INSTALLSTATE_NOTUSED == is) + { + is = INSTALLSTATE_ABSENT; + } + else if (INSTALLSTATE_ABSENT != is && INSTALLSTATE_LOCAL != is && INSTALLSTATE_SOURCE != is) + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Failed to get component path: %d", is); + } + + // set variable + switch (pSearch->MsiComponentSearch.Type) + { + case BURN_MSI_COMPONENT_SEARCH_TYPE_KEYPATH: + if (INSTALLSTATE_ABSENT == is || INSTALLSTATE_LOCAL == is || INSTALLSTATE_SOURCE == is) + { + hr = VariableSetString(pVariables, pSearch->sczVariable, sczPath, FALSE, FALSE); + } + break; + case BURN_MSI_COMPONENT_SEARCH_TYPE_STATE: + hr = VariableSetNumeric(pVariables, pSearch->sczVariable, is, FALSE); + break; + case BURN_MSI_COMPONENT_SEARCH_TYPE_DIRECTORY: + if (INSTALLSTATE_ABSENT == is || INSTALLSTATE_LOCAL == is || INSTALLSTATE_SOURCE == is) + { + // remove file part from path, if any + LPWSTR wz = wcsrchr(sczPath, L'\\'); + if (wz) + { + wz[1] = L'\0'; + } + + hr = VariableSetString(pVariables, pSearch->sczVariable, sczPath, FALSE, FALSE); + } + break; + } + ExitOnFailure(hr, "Failed to set variable."); + +LExit: + if (FAILED(hr)) + { + LogStringLine(REPORT_STANDARD, "MsiComponentSearch failed: ID '%ls', HRESULT 0x%x", pSearch->sczKey, hr); + } + + StrSecureZeroFreeString(sczComponentId); + StrSecureZeroFreeString(sczProductCode); + ReleaseStr(sczPath); + return hr; +} + +static HRESULT MsiProductSearch( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + LPWSTR sczGuid = NULL; + LPCWSTR wzProperty = NULL; + LPWSTR *rgsczRelatedProductCodes = NULL; + DWORD dwRelatedProducts = 0; + BURN_VARIANT_TYPE type = BURN_VARIANT_TYPE_NONE; + BURN_VARIANT value = { }; + + switch (pSearch->MsiProductSearch.Type) + { + case BURN_MSI_PRODUCT_SEARCH_TYPE_VERSION: + wzProperty = INSTALLPROPERTY_VERSIONSTRING; + break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_LANGUAGE: + wzProperty = INSTALLPROPERTY_LANGUAGE; + break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_STATE: + wzProperty = INSTALLPROPERTY_PRODUCTSTATE; + break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_ASSIGNMENT: + wzProperty = INSTALLPROPERTY_ASSIGNMENTTYPE; + break; + default: + ExitOnFailure(hr = E_NOTIMPL, "Unsupported product search type: %u", pSearch->MsiProductSearch.Type); + } + + // format guid string + hr = VariableFormatString(pVariables, pSearch->MsiProductSearch.sczGuid, &sczGuid, NULL); + ExitOnFailure(hr, "Failed to format GUID string."); + + // get product info + value.Type = BURN_VARIANT_TYPE_STRING; + + // if this is an upgrade code then get the product code of the highest versioned related product + if (BURN_MSI_PRODUCT_SEARCH_GUID_TYPE_UPGRADECODE == pSearch->MsiProductSearch.GuidType) + { + // WiuEnumRelatedProductCodes will log sczGuid on errors, what if there's a hidden variable in there? + hr = WiuEnumRelatedProductCodes(sczGuid, &rgsczRelatedProductCodes, &dwRelatedProducts, TRUE); + ExitOnFailure(hr, "Failed to enumerate related products for upgrade code."); + + // if we actually found a related product then use its upgrade code for the rest of the search + if (1 == dwRelatedProducts) + { + hr = StrAllocStringSecure(&sczGuid, rgsczRelatedProductCodes[0], 0); + ExitOnFailure(hr, "Failed to copy upgrade code."); + } + else + { + // set this here so we have a way of knowing that we don't need to bother + // querying for the product information below + hr = HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT); + } + } + + if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) != hr) + { + hr = WiuGetProductInfo(sczGuid, wzProperty, &value.sczValue); + if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY) == hr) + { + // product state is available only through MsiGetProductInfoEx + // What if there is a hidden variable in sczGuid? + LogStringLine(REPORT_VERBOSE, "Trying per-machine extended info for property '%ls' for product: %ls", wzProperty, sczGuid); + hr = WiuGetProductInfoEx(sczGuid, NULL, MSIINSTALLCONTEXT_MACHINE, wzProperty, &value.sczValue); + + // if not in per-machine context, try per-user (unmanaged) + if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr) + { + // What if there is a hidden variable in sczGuid? + LogStringLine(REPORT_STANDARD, "Trying per-user extended info for property '%ls' for product: %ls", wzProperty, sczGuid); + hr = WiuGetProductInfoEx(sczGuid, NULL, MSIINSTALLCONTEXT_USERUNMANAGED, wzProperty, &value.sczValue); + } + } + } + + if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr) + { + // What if there is a hidden variable in sczGuid? + LogStringLine(REPORT_STANDARD, "Product or related product not found: %ls", sczGuid); + + // set value to indicate absent + switch (pSearch->MsiProductSearch.Type) + { + case BURN_MSI_PRODUCT_SEARCH_TYPE_ASSIGNMENT: __fallthrough; + case BURN_MSI_PRODUCT_SEARCH_TYPE_VERSION: + value.Type = BURN_VARIANT_TYPE_NUMERIC; + value.llValue = 0; + break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_LANGUAGE: + // is supposed to remain empty + break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_STATE: + value.Type = BURN_VARIANT_TYPE_NUMERIC; + value.llValue = INSTALLSTATE_ABSENT; + break; + } + + hr = S_OK; + } + ExitOnFailure(hr, "Failed to get product info."); + + // change value type + switch (pSearch->MsiProductSearch.Type) + { + case BURN_MSI_PRODUCT_SEARCH_TYPE_VERSION: + type = BURN_VARIANT_TYPE_VERSION; + break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_LANGUAGE: + type = BURN_VARIANT_TYPE_STRING; + break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_STATE: __fallthrough; + case BURN_MSI_PRODUCT_SEARCH_TYPE_ASSIGNMENT: + type = BURN_VARIANT_TYPE_NUMERIC; + break; + } + hr = BVariantChangeType(&value, type); + ExitOnFailure(hr, "Failed to change value type."); + + // Set variable. + hr = VariableSetVariant(pVariables, pSearch->sczVariable, &value); + ExitOnFailure(hr, "Failed to set variable."); + +LExit: + if (FAILED(hr)) + { + LogStringLine(REPORT_STANDARD, "MsiProductSearch failed: ID '%ls', HRESULT 0x%x", pSearch->sczKey, hr); + } + + StrSecureZeroFreeString(sczGuid); + ReleaseStrArray(rgsczRelatedProductCodes, dwRelatedProducts); + BVariantUninitialize(&value); + + return hr; +} + +static HRESULT MsiFeatureSearch( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* /*pVariables*/ + ) +{ + HRESULT hr = E_NOTIMPL; + +//LExit: + if (FAILED(hr)) + { + LogStringLine(REPORT_STANDARD, "MsiFeatureSearch failed: ID '%ls', HRESULT 0x%x", pSearch->sczKey, hr); + } + + return hr; +} + +static HRESULT PerformExtensionSearch( + __in BURN_SEARCH* pSearch + ) +{ + HRESULT hr = S_OK; + + hr = BurnExtensionPerformSearch(pSearch->ExtensionSearch.pExtension, pSearch->sczKey, pSearch->sczVariable); + + return hr; +} + +static HRESULT PerformSetVariable( + __in BURN_SEARCH* pSearch, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + + hr = VariableSetVariant(pVariables, pSearch->sczVariable, &pSearch->SetVariable.value); + ExitOnFailure(hr, "Failed to set variable: %ls", pSearch->sczVariable); + +LExit: + return hr; +} diff --git a/src/burn/engine/search.h b/src/burn/engine/search.h new file mode 100644 index 00000000..c699c97c --- /dev/null +++ b/src/burn/engine/search.h @@ -0,0 +1,163 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + +enum BURN_SEARCH_TYPE +{ + BURN_SEARCH_TYPE_NONE, + BURN_SEARCH_TYPE_DIRECTORY, + BURN_SEARCH_TYPE_FILE, + BURN_SEARCH_TYPE_REGISTRY, + BURN_SEARCH_TYPE_MSI_COMPONENT, + BURN_SEARCH_TYPE_MSI_PRODUCT, + BURN_SEARCH_TYPE_MSI_FEATURE, + BURN_SEARCH_TYPE_EXTENSION, + BURN_SEARCH_TYPE_SET_VARIABLE, +}; + +enum BURN_DIRECTORY_SEARCH_TYPE +{ + BURN_DIRECTORY_SEARCH_TYPE_NONE, + BURN_DIRECTORY_SEARCH_TYPE_EXISTS, + BURN_DIRECTORY_SEARCH_TYPE_PATH, +}; + +enum BURN_FILE_SEARCH_TYPE +{ + BURN_FILE_SEARCH_TYPE_NONE, + BURN_FILE_SEARCH_TYPE_EXISTS, + BURN_FILE_SEARCH_TYPE_VERSION, + BURN_FILE_SEARCH_TYPE_PATH, +}; + +enum BURN_REGISTRY_SEARCH_TYPE +{ + BURN_REGISTRY_SEARCH_TYPE_NONE, + BURN_REGISTRY_SEARCH_TYPE_EXISTS, + BURN_REGISTRY_SEARCH_TYPE_VALUE, +}; + +enum BURN_MSI_COMPONENT_SEARCH_TYPE +{ + BURN_MSI_COMPONENT_SEARCH_TYPE_NONE, + BURN_MSI_COMPONENT_SEARCH_TYPE_KEYPATH, + BURN_MSI_COMPONENT_SEARCH_TYPE_STATE, + BURN_MSI_COMPONENT_SEARCH_TYPE_DIRECTORY, +}; + +enum BURN_MSI_PRODUCT_SEARCH_TYPE +{ + BURN_MSI_PRODUCT_SEARCH_TYPE_NONE, + BURN_MSI_PRODUCT_SEARCH_TYPE_VERSION, + BURN_MSI_PRODUCT_SEARCH_TYPE_LANGUAGE, + BURN_MSI_PRODUCT_SEARCH_TYPE_STATE, + BURN_MSI_PRODUCT_SEARCH_TYPE_ASSIGNMENT, +}; + +enum BURN_MSI_PRODUCT_SEARCH_GUID_TYPE +{ + BURN_MSI_PRODUCT_SEARCH_GUID_TYPE_NONE, + BURN_MSI_PRODUCT_SEARCH_GUID_TYPE_PRODUCTCODE, + BURN_MSI_PRODUCT_SEARCH_GUID_TYPE_UPGRADECODE +}; + +enum BURN_MSI_FEATURE_SEARCH_TYPE +{ + BURN_MSI_FEATURE_SEARCH_TYPE_NONE, + BURN_MSI_FEATURE_SEARCH_TYPE_STATE, +}; + + +// structs + +typedef struct _BURN_SEARCH +{ + LPWSTR sczKey; + LPWSTR sczVariable; + LPWSTR sczCondition; + + BURN_SEARCH_TYPE Type; + union + { + struct + { + BURN_DIRECTORY_SEARCH_TYPE Type; + LPWSTR sczPath; + } DirectorySearch; + struct + { + BURN_FILE_SEARCH_TYPE Type; + LPWSTR sczPath; + } FileSearch; + struct + { + BURN_REGISTRY_SEARCH_TYPE Type; + BURN_VARIANT_TYPE VariableType; + HKEY hRoot; + LPWSTR sczKey; + LPWSTR sczValue; + BOOL fWin64; + BOOL fExpandEnvironment; + } RegistrySearch; + struct + { + BURN_MSI_COMPONENT_SEARCH_TYPE Type; + LPWSTR sczProductCode; + LPWSTR sczComponentId; + } MsiComponentSearch; + struct + { + BURN_MSI_PRODUCT_SEARCH_TYPE Type; + BURN_MSI_PRODUCT_SEARCH_GUID_TYPE GuidType; + LPWSTR sczGuid; + } MsiProductSearch; + struct + { + BURN_MSI_FEATURE_SEARCH_TYPE Type; + LPWSTR sczProductCode; + LPWSTR sczFeatureId; + } MsiFeatureSearch; + struct + { + BURN_EXTENSION* pExtension; + } ExtensionSearch; + struct + { + BURN_VARIANT value; + } SetVariable; + }; +} BURN_SEARCH; + +typedef struct _BURN_SEARCHES +{ + BURN_SEARCH* rgSearches; + DWORD cSearches; +} BURN_SEARCHES; + + +// function declarations + +HRESULT SearchesParseFromXml( + __in BURN_SEARCHES* pSearches, + __in BURN_EXTENSIONS* pBurnExtensions, + __in IXMLDOMNode* pixnBundle + ); +HRESULT SearchesExecute( + __in BURN_SEARCHES* pSearches, + __in BURN_VARIABLES* pVariables + ); +void SearchesUninitialize( + __in BURN_SEARCHES* pSearches + ); + + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/section.cpp b/src/burn/engine/section.cpp new file mode 100644 index 00000000..3720155c --- /dev/null +++ b/src/burn/engine/section.cpp @@ -0,0 +1,399 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// constants + +// If these defaults ever change, be sure to update constants in burn\stub\StubSection.cpp as well. +#define BURN_SECTION_NAME ".wixburn" +#define BURN_SECTION_MAGIC 0x00f14300 +#define BURN_SECTION_VERSION 0x00000002 +#define MANIFEST_CABINET_TOKEN L"0" + +// structs +typedef struct _BURN_SECTION_HEADER +{ + DWORD dwMagic; + DWORD dwVersion; + + GUID guidBundleId; + + DWORD dwStubSize; + DWORD dwOriginalChecksum; + DWORD dwOriginalSignatureOffset; + DWORD dwOriginalSignatureSize; + + DWORD dwFormat; + DWORD cContainers; + DWORD rgcbContainers[1]; +} BURN_SECTION_HEADER; + +static HRESULT VerifySectionMatchesMemoryPEHeader( + __in REFGUID pSection + ); + + +extern "C" HRESULT SectionInitialize( + __in BURN_SECTION* pSection, + __in HANDLE hEngineFile, + __in HANDLE hSourceEngineFile + ) +{ + HRESULT hr = S_OK; + DWORD cbRead = 0; + LARGE_INTEGER li = { }; + LONGLONG llSize = 0; + IMAGE_DOS_HEADER dosHeader = { }; + IMAGE_NT_HEADERS ntHeader = { }; + DWORD dwChecksumOffset = 0; + DWORD dwCertificateTableOffset = 0; + DWORD dwSignatureOffset = 0; + DWORD cbSignature = 0; + IMAGE_SECTION_HEADER sectionHeader = { }; + DWORD_PTR dwOriginalChecksumAndSignatureOffset = 0; + BURN_SECTION_HEADER* pBurnSectionHeader = NULL; + + pSection->hEngineFile = hEngineFile; + ExitOnInvalidHandleWithLastError(pSection->hEngineFile, hr, "Failed to open handle to engine process path."); + + pSection->hSourceEngineFile = INVALID_HANDLE_VALUE == hSourceEngineFile ? hEngineFile : hSourceEngineFile; + + // + // First, make sure we have a valid DOS signature. + // + if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to seek to start of file."); + } + + // read DOS header + if (!::ReadFile(pSection->hEngineFile, &dosHeader, sizeof(IMAGE_DOS_HEADER), &cbRead, NULL)) + { + ExitWithLastError(hr, "Failed to read DOS header."); + } + else if (sizeof(IMAGE_DOS_HEADER) > cbRead || IMAGE_DOS_SIGNATURE != dosHeader.e_magic) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to find valid DOS image header in buffer."); + } + + // + // Now, make sure we have a valid NT signature. + // + + // seek to new header + li.QuadPart = dosHeader.e_lfanew; + if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to seek to NT header."); + } + + // read NT header + if (!::ReadFile(pSection->hEngineFile, &ntHeader, sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER), &cbRead, NULL)) + { + ExitWithLastError(hr, "Failed to read NT header."); + } + else if ((sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER)) > cbRead || IMAGE_NT_SIGNATURE != ntHeader.Signature) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to find valid NT image header in buffer."); + } + + // Get the table offsets. + dwChecksumOffset = dosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER) + (sizeof(DWORD) * 16); + dwCertificateTableOffset = dosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS) - (sizeof(IMAGE_DATA_DIRECTORY) * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY)); + + // Seek into the certificate table to get the signature size. + li.QuadPart = dwCertificateTableOffset; + if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to seek to section info."); + } + + if (!::ReadFile(pSection->hEngineFile, &dwSignatureOffset, sizeof(dwSignatureOffset), &cbRead, NULL)) + { + ExitWithLastError(hr, "Failed to read signature offset."); + } + + if (!::ReadFile(pSection->hEngineFile, &cbSignature, sizeof(cbSignature), &cbRead, NULL)) + { + ExitWithLastError(hr, "Failed to read signature size."); + } + + // + // Finally, get into the section table and look for the Burn section info. + // + + // seek past optional headers + li.QuadPart = dosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER) + ntHeader.FileHeader.SizeOfOptionalHeader; + if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to seek past optional headers."); + } + + // read sections one by one until we find our section + for (DWORD i = 0; ; ++i) + { + // read section + if (!::ReadFile(pSection->hEngineFile, §ionHeader, sizeof(IMAGE_SECTION_HEADER), &cbRead, NULL)) + { + ExitWithLastError(hr, "Failed to read image section header, index: %u", i); + } + if (sizeof(IMAGE_SECTION_HEADER) > cbRead) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to read complete image section header, index: %u", i); + } + + // compare header name + C_ASSERT(sizeof(sectionHeader.Name) == sizeof(BURN_SECTION_NAME) - 1); + if (0 == memcmp(sectionHeader.Name, BURN_SECTION_NAME, sizeof(sectionHeader.Name))) + { + break; + } + + // fail if we hit the end + if (i + 1 >= ntHeader.FileHeader.NumberOfSections) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to find Burn section."); + } + } + + // + // We've arrived at the section info. + // + + // check size of section + if (sizeof(BURN_SECTION_HEADER) > sectionHeader.SizeOfRawData) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to read section info, data to short: %u", sectionHeader.SizeOfRawData); + } + + // allocate buffer for section info + pBurnSectionHeader = (BURN_SECTION_HEADER*)MemAlloc(sectionHeader.SizeOfRawData, TRUE); + ExitOnNull(pBurnSectionHeader, hr, E_OUTOFMEMORY, "Failed to allocate buffer for section info."); + + // seek to section info + li.QuadPart = sectionHeader.PointerToRawData; + if (!::SetFilePointerEx(pSection->hEngineFile, li, NULL, FILE_BEGIN)) + { + ExitWithLastError(hr, "Failed to seek to section info."); + } + + // Note the location of original checksum and signature information in the burn section header. + dwOriginalChecksumAndSignatureOffset = sectionHeader.PointerToRawData + (reinterpret_cast(&pBurnSectionHeader->dwOriginalChecksum) - reinterpret_cast(pBurnSectionHeader)); + + // read section info + if (!::ReadFile(pSection->hEngineFile, pBurnSectionHeader, sectionHeader.SizeOfRawData, &cbRead, NULL)) + { + ExitWithLastError(hr, "Failed to read section info."); + } + else if (sectionHeader.SizeOfRawData > cbRead) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to read complete section info."); + } + + // validate version of section info + if (BURN_SECTION_VERSION != pBurnSectionHeader->dwVersion) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to read section info, unsupported version: %08x", pBurnSectionHeader->dwVersion); + } + + hr = FileSizeByHandle(pSection->hSourceEngineFile, &llSize); + ExitOnFailure(hr, "Failed to get total size of bundle."); + + pSection->cbStub = pBurnSectionHeader->dwStubSize; + + // If there is an original signature use that to determine the engine size. + if (pBurnSectionHeader->dwOriginalSignatureOffset) + { + pSection->cbEngineSize = pBurnSectionHeader->dwOriginalSignatureOffset + pBurnSectionHeader->dwOriginalSignatureSize; + } + else if (dwSignatureOffset) // if there is a signature, use it. + { + pSection->cbEngineSize = dwSignatureOffset + cbSignature; + } + else // just use the stub and UX container as the size of the engine. + { + pSection->cbEngineSize = pSection->cbStub + pBurnSectionHeader->rgcbContainers[0]; + } + + pSection->qwBundleSize = static_cast(llSize); + + pSection->dwChecksumOffset = dwChecksumOffset; + pSection->dwCertificateTableOffset = dwCertificateTableOffset; + pSection->dwOriginalChecksumAndSignatureOffset = dwOriginalChecksumAndSignatureOffset; + + pSection->dwOriginalChecksum = pBurnSectionHeader->dwOriginalChecksum; + pSection->dwOriginalSignatureOffset = pBurnSectionHeader->dwOriginalSignatureOffset; + pSection->dwOriginalSignatureSize = pBurnSectionHeader->dwOriginalSignatureSize; + + pSection->dwFormat = pBurnSectionHeader->dwFormat; + pSection->cContainers = pBurnSectionHeader->cContainers; + pSection->rgcbContainers = (DWORD*)MemAlloc(sizeof(DWORD) * pSection->cContainers, TRUE); + ExitOnNull(pSection->rgcbContainers, hr, E_OUTOFMEMORY, "Failed to allocate memory for container sizes."); + + memcpy(pSection->rgcbContainers, pBurnSectionHeader->rgcbContainers, sizeof(DWORD) * pSection->cContainers); + + // TODO: verify more than just the GUID. + hr = VerifySectionMatchesMemoryPEHeader(pBurnSectionHeader->guidBundleId); + ExitOnRootFailure(hr, "PE Header from file didn't match PE Header in memory."); + +LExit: + ReleaseMem(pBurnSectionHeader); + + return hr; +} + +extern "C" void SectionUninitialize( + __out BURN_SECTION* pSection + ) +{ + ReleaseMem(pSection->rgcbContainers); + memset(pSection, 0, sizeof(BURN_SECTION)); +} + +extern "C" HRESULT SectionGetAttachedContainerInfo( + __in BURN_SECTION* pSection, + __in DWORD iContainerIndex, + __in DWORD dwExpectedType, + __out DWORD64* pqwOffset, + __out DWORD64* pqwSize, + __out BOOL* pfPresent + ) +{ + HRESULT hr = S_OK; + + // validate container info + if (iContainerIndex >= pSection->cContainers) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to find container info, too few elements: %u", pSection->cContainers); + } + else if (dwExpectedType != pSection->dwFormat) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Unexpected container format."); + } + + // If we are asking for the UX container, find it right after the stub. + if (0 == iContainerIndex) + { + *pqwOffset = pSection->cbStub; + } + else // attached containers start after the whole engine. + { + *pqwOffset = pSection->cbEngineSize; + for (DWORD i = 1; i < iContainerIndex; ++i) + { + *pqwOffset += pSection->rgcbContainers[i]; + } + } + + *pqwSize = pSection->rgcbContainers[iContainerIndex]; + *pfPresent = (*pqwOffset + *pqwSize) <= pSection->qwBundleSize; + + AssertSz(*pfPresent || pSection->qwBundleSize <= *pqwOffset, "An attached container should either be present or completely absent from the bundle. Found a case where the attached container is partially present which is wrong."); + +LExit: + return hr; +} + +HRESULT VerifySectionMatchesMemoryPEHeader( + __in REFGUID pBundleId + ) +{ + HRESULT hr = S_OK; + BYTE* pbPEHeader = NULL; + PIMAGE_DOS_HEADER pDosHeader = NULL; + PIMAGE_NT_HEADERS pNtHeader = NULL; + PIMAGE_SECTION_HEADER pSections = NULL; + PIMAGE_SECTION_HEADER pSectionHeader = NULL; + BURN_SECTION_HEADER* pBurnSectionHeader = NULL; + + pbPEHeader = reinterpret_cast(::GetModuleHandleW(NULL)); + ExitOnNullWithLastError(pbPEHeader, hr, "Failed to get module handle to process."); + + // + // First, make sure we have a valid DOS signature. + // + + pDosHeader = reinterpret_cast(pbPEHeader); + if (IMAGE_DOS_SIGNATURE != pDosHeader->e_magic) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to find valid DOS image header in buffer."); + } + + // + // Now, make sure we have a valid NT signature. + // + + pNtHeader = reinterpret_cast(pbPEHeader + pDosHeader->e_lfanew); + if (IMAGE_NT_SIGNATURE != pNtHeader->Signature) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to find valid NT image header in buffer."); + } + + // + // Finally, get into the section table and look for the Burn section info. + // + + pSections = reinterpret_cast(pbPEHeader + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER) + pNtHeader->FileHeader.SizeOfOptionalHeader); + + // Read sections one by one until we find our section. + for (DWORD i = 0; ; ++i) + { + pSectionHeader = pSections + i; + + // Compare header name. + C_ASSERT(sizeof(pSectionHeader->Name) == sizeof(BURN_SECTION_NAME) - 1); + if (0 == memcmp(pSectionHeader->Name, BURN_SECTION_NAME, sizeof(pSectionHeader->Name))) + { + break; + } + + // Fail if we hit the end. + if (i + 1 >= pNtHeader->FileHeader.NumberOfSections) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to find Burn section."); + } + } + + // + // We've arrived at the section info. + // + + // Check size of section. + if (sizeof(BURN_SECTION_HEADER) > pSectionHeader->SizeOfRawData) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to read section info, data to short: %u", pSectionHeader->SizeOfRawData); + } + + // Get Burn section info. + pBurnSectionHeader = reinterpret_cast(pbPEHeader + pSectionHeader->VirtualAddress); + + // Validate version of section info. + if (BURN_SECTION_VERSION != pBurnSectionHeader->dwVersion) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Failed to read section info, unsupported version: %08x", pBurnSectionHeader->dwVersion); + } + + if (!::IsEqualGUID(pBundleId, pBurnSectionHeader->guidBundleId)) + { + hr = E_INVALIDDATA; + ExitOnRootFailure(hr, "Bundle guid didn't match the guid in the PE Header in memory."); + } + +LExit: + return hr; +} diff --git a/src/burn/engine/section.h b/src/burn/engine/section.h new file mode 100644 index 00000000..6c62ba44 --- /dev/null +++ b/src/burn/engine/section.h @@ -0,0 +1,54 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// structs + +typedef struct _BURN_SECTION +{ + HANDLE hEngineFile; + HANDLE hSourceEngineFile; + + DWORD cbStub; + DWORD cbEngineSize; // stub + UX container + original certficiate + DWORD64 qwBundleSize; // stub + UX container + original certificate [+ attached containers* + final certificate] + + DWORD dwChecksumOffset; + DWORD dwCertificateTableOffset; + DWORD_PTR dwOriginalChecksumAndSignatureOffset; + + DWORD dwOriginalChecksum; + DWORD dwOriginalSignatureOffset; + DWORD dwOriginalSignatureSize; + + DWORD dwFormat; + DWORD cContainers; + DWORD* rgcbContainers; +} BURN_SECTION; + + +HRESULT SectionInitialize( + __in BURN_SECTION* pSection, + __in HANDLE hEngineFile, + __in HANDLE hSourceEngineFile + ); +void SectionUninitialize( + __in BURN_SECTION* pSection + ); +HRESULT SectionGetAttachedContainerInfo( + __in BURN_SECTION* pSection, + __in DWORD iContainerIndex, + __in DWORD dwExpectedType, + __out DWORD64* pqwOffset, + __out DWORD64* pqwSize, + __out BOOL* pfPresent + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/splashscreen.cpp b/src/burn/engine/splashscreen.cpp new file mode 100644 index 00000000..90bd5203 --- /dev/null +++ b/src/burn/engine/splashscreen.cpp @@ -0,0 +1,355 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +#define BURN_SPLASHSCREEN_CLASS_WINDOW L"WixBurnSplashScreen" +#define IDB_SPLASHSCREEN 1 + +// struct + +struct SPLASHSCREEN_INFO +{ + HBITMAP hBitmap; + SIZE defaultDpiSize; + SIZE size; + UINT nDpi; + HWND hWnd; +}; + +struct SPLASHSCREEN_CONTEXT +{ + HANDLE hInitializedEvent; + HINSTANCE hInstance; + LPCWSTR wzCaption; + + HWND* pHwnd; +}; + +// internal function definitions + +static DWORD WINAPI ThreadProc( + __in LPVOID pvContext + ); +static LRESULT CALLBACK WndProc( + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ); +static HRESULT LoadSplashScreen( + __in SPLASHSCREEN_CONTEXT* pContext, + __in SPLASHSCREEN_INFO* pSplashScreen + ); +static BOOL OnDpiChanged( + __in SPLASHSCREEN_INFO* pSplashScreen, + __in WPARAM wParam, + __in LPARAM lParam + ); +static void OnEraseBkgnd( + __in SPLASHSCREEN_INFO* pSplashScreen, + __in WPARAM wParam + ); +static void OnNcCreate( + __in HWND hWnd, + __in LPARAM lParam + ); +static void ScaleSplashScreen( + __in SPLASHSCREEN_INFO* pSplashScreen, + __in UINT nDpi, + __in int x, + __in int y + ); + + +// function definitions + +extern "C" void SplashScreenCreate( + __in HINSTANCE hInstance, + __in_z_opt LPCWSTR wzCaption, + __out HWND* pHwnd + ) +{ + HRESULT hr = S_OK; + SPLASHSCREEN_CONTEXT context = { }; + HANDLE rgSplashScreenEvents[2] = { }; + DWORD dwSplashScreenThreadId = 0; + + rgSplashScreenEvents[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL); + ExitOnNullWithLastError(rgSplashScreenEvents[0], hr, "Failed to create modal event."); + + // create splash screen thread. + context.hInitializedEvent = rgSplashScreenEvents[0]; + context.hInstance = hInstance; + context.wzCaption = wzCaption; + context.pHwnd = pHwnd; + + rgSplashScreenEvents[1] = ::CreateThread(NULL, 0, ThreadProc, &context, 0, &dwSplashScreenThreadId); + ExitOnNullWithLastError(rgSplashScreenEvents[1], hr, "Failed to create UI thread."); + + // It doesn't really matter if the thread gets initialized (WAIT_OBJECT_0) or fails and exits + // prematurely (WAIT_OBJECT_0 + 1), we just want to wait long enough for one of those two + // events to happen. + ::WaitForMultipleObjects(countof(rgSplashScreenEvents), rgSplashScreenEvents, FALSE, INFINITE); + +LExit: + ReleaseHandle(rgSplashScreenEvents[1]); + ReleaseHandle(rgSplashScreenEvents[0]); +} + +extern "C" HRESULT SplashScreenDisplayError( + __in BOOTSTRAPPER_DISPLAY display, + __in_z LPCWSTR wzBundleName, + __in HRESULT hrError + ) +{ + HRESULT hr = S_OK; + LPWSTR sczDisplayString = NULL; + + hr = StrAllocFromError(&sczDisplayString, hrError, NULL); + ExitOnFailure(hr, "Failed to allocate string to display error message"); + + Trace(REPORT_STANDARD, "Error message displayed because: %ls", sczDisplayString); + + if (BOOTSTRAPPER_DISPLAY_NONE == display || BOOTSTRAPPER_DISPLAY_PASSIVE == display || BOOTSTRAPPER_DISPLAY_EMBEDDED == display) + { + // Don't display the error dialog in these modes + ExitFunction1(hr = S_OK); + } + + ::MessageBoxW(NULL, sczDisplayString, wzBundleName, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL); + +LExit: + ReleaseStr(sczDisplayString); + + return hr; +} + + +static DWORD WINAPI ThreadProc( + __in LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + SPLASHSCREEN_CONTEXT* pContext = static_cast(pvContext); + + SPLASHSCREEN_INFO splashScreenInfo = { }; + + WNDCLASSW wc = { }; + BOOL fRegistered = TRUE; + + BOOL fRet = FALSE; + MSG msg = { }; + + // Register the window class. + wc.lpfnWndProc = WndProc; + wc.hInstance = pContext->hInstance; + wc.hCursor = ::LoadCursorW(NULL, (LPCWSTR)IDC_ARROW); + wc.lpszClassName = BURN_SPLASHSCREEN_CLASS_WINDOW; + if (!::RegisterClassW(&wc)) + { + ExitWithLastError(hr, "Failed to register window."); + } + + fRegistered = TRUE; + + hr = LoadSplashScreen(pContext, &splashScreenInfo); + ExitOnFailure(hr, "Failed to load splash screen."); + + // Return the splash screen window and free the main thread waiting for us to be initialized. + *pContext->pHwnd = splashScreenInfo.hWnd; + ::SetEvent(pContext->hInitializedEvent); + + // Pump messages until the bootstrapper application destroys the window. + while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) + { + if (-1 == fRet) + { + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Unexpected return value from message pump."); + } + else if (!::IsDialogMessageW(splashScreenInfo.hWnd, &msg)) + { + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + } + +LExit: + if (fRegistered) + { + ::UnregisterClassW(BURN_SPLASHSCREEN_CLASS_WINDOW, pContext->hInstance); + } + + if (splashScreenInfo.hBitmap) + { + ::DeleteObject(splashScreenInfo.hBitmap); + } + + return hr; +} + +static LRESULT CALLBACK WndProc( + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + LRESULT lres = 0; + SPLASHSCREEN_INFO* pSplashScreen = reinterpret_cast(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); + + switch (uMsg) + { + case WM_NCCREATE: + OnNcCreate(hWnd, lParam); + break; + + case WM_NCDESTROY: + lres = ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); + ::PostQuitMessage(0); + return lres; + + case WM_NCHITTEST: + return HTCAPTION; // allow window to be moved by grabbing any pixel. + + case WM_DPICHANGED: + if (OnDpiChanged(pSplashScreen, wParam, lParam)) + { + return 0; + } + break; + + case WM_ERASEBKGND: + OnEraseBkgnd(pSplashScreen, wParam); + return 1; + } + + return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +static HRESULT LoadSplashScreen( + __in SPLASHSCREEN_CONTEXT* pContext, + __in SPLASHSCREEN_INFO* pSplashScreen + ) +{ + HRESULT hr = S_OK; + BITMAP bmp = { }; + POINT pt = { }; + int x = 0; + int y = 0; + DPIU_MONITOR_CONTEXT* pMonitorContext = NULL; + RECT* pMonitorRect = NULL; + + pSplashScreen->nDpi = USER_DEFAULT_SCREEN_DPI; + pSplashScreen->hBitmap = ::LoadBitmapW(pContext->hInstance, MAKEINTRESOURCEW(IDB_SPLASHSCREEN)); + ExitOnNullWithLastError(pSplashScreen->hBitmap, hr, "Failed to load splash screen bitmap."); + + ::GetObject(pSplashScreen->hBitmap, sizeof(bmp), static_cast(&bmp)); + pSplashScreen->defaultDpiSize.cx = pSplashScreen->size.cx = bmp.bmWidth; + pSplashScreen->defaultDpiSize.cy = pSplashScreen->size.cy = bmp.bmHeight; + + // Try to default to the monitor with the mouse, otherwise default to the primary monitor. + if (!::GetCursorPos(&pt)) + { + pt.x = 0; + pt.y = 0; + } + + // Try to center the window on the chosen monitor. + hr = DpiuGetMonitorContextFromPoint(&pt, &pMonitorContext); + if (SUCCEEDED(hr)) + { + pMonitorRect = &pMonitorContext->mi.rcWork; + if (pMonitorContext->nDpi != pSplashScreen->nDpi) + { + ScaleSplashScreen(pSplashScreen, pMonitorContext->nDpi, pMonitorRect->left, pMonitorRect->top); + } + + x = pMonitorRect->left + (pMonitorRect->right - pMonitorRect->left - pSplashScreen->size.cx) / 2; + y = pMonitorRect->top + (pMonitorRect->bottom - pMonitorRect->top - pSplashScreen->size.cy) / 2; + } + else + { + hr = S_OK; + x = CW_USEDEFAULT; + y = CW_USEDEFAULT; + } + + pSplashScreen->hWnd = ::CreateWindowExW(WS_EX_TOOLWINDOW, BURN_SPLASHSCREEN_CLASS_WINDOW, pContext->wzCaption, WS_POPUP | WS_VISIBLE, x, y, pSplashScreen->size.cx, pSplashScreen->size.cy, HWND_DESKTOP, NULL, pContext->hInstance, pSplashScreen); + ExitOnNullWithLastError(pSplashScreen->hWnd, hr, "Failed to create window."); + +LExit: + MemFree(pMonitorContext); + + return hr; +} + +static BOOL OnDpiChanged( + __in SPLASHSCREEN_INFO* pSplashScreen, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + UINT nDpi = HIWORD(wParam); + RECT* pRect = reinterpret_cast(lParam); + BOOL fDpiChanged = pSplashScreen->nDpi != nDpi; + + if (fDpiChanged) + { + ScaleSplashScreen(pSplashScreen, nDpi, pRect->left, pRect->top); + } + + return fDpiChanged; +} + +static void OnEraseBkgnd( + __in SPLASHSCREEN_INFO* pSplashScreen, + __in WPARAM wParam + ) +{ + HDC hdc = reinterpret_cast(wParam); + HDC hdcMem = ::CreateCompatibleDC(hdc); + HBITMAP hDefaultBitmap = static_cast(::SelectObject(hdcMem, pSplashScreen->hBitmap)); + ::StretchBlt(hdc, 0, 0, pSplashScreen->size.cx, pSplashScreen->size.cy, hdcMem, 0, 0, pSplashScreen->defaultDpiSize.cx, pSplashScreen->defaultDpiSize.cy, SRCCOPY); + ::SelectObject(hdcMem, hDefaultBitmap); + ::DeleteDC(hdcMem); +} + +static void OnNcCreate( + __in HWND hWnd, + __in LPARAM lParam + ) +{ + DPIU_WINDOW_CONTEXT windowContext = { }; + CREATESTRUCTW* pCreateStruct = reinterpret_cast(lParam); + SPLASHSCREEN_INFO* pSplashScreen = reinterpret_cast(pCreateStruct->lpCreateParams); + + ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(pSplashScreen)); + pSplashScreen->hWnd = hWnd; + + DpiuGetWindowContext(pSplashScreen->hWnd, &windowContext); + + if (windowContext.nDpi != pSplashScreen->nDpi) + { + ScaleSplashScreen(pSplashScreen, windowContext.nDpi, pCreateStruct->x, pCreateStruct->y); + } +} + +static void ScaleSplashScreen( + __in SPLASHSCREEN_INFO* pSplashScreen, + __in UINT nDpi, + __in int x, + __in int y + ) +{ + pSplashScreen->nDpi = nDpi; + + pSplashScreen->size.cx = DpiuScaleValue(pSplashScreen->defaultDpiSize.cx, pSplashScreen->nDpi); + pSplashScreen->size.cy = DpiuScaleValue(pSplashScreen->defaultDpiSize.cy, pSplashScreen->nDpi); + + if (pSplashScreen->hWnd) + { + ::SetWindowPos(pSplashScreen->hWnd, NULL, x, y, pSplashScreen->size.cx, pSplashScreen->size.cy, SWP_NOACTIVATE | SWP_NOZORDER); + } +} diff --git a/src/burn/engine/splashscreen.h b/src/burn/engine/splashscreen.h new file mode 100644 index 00000000..8f8817c7 --- /dev/null +++ b/src/burn/engine/splashscreen.h @@ -0,0 +1,31 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + + +// structs + + +// functions + +void SplashScreenCreate( + __in HINSTANCE hInstance, + __in_z_opt LPCWSTR wzCaption, + __out HWND* pHwnd + ); +HRESULT SplashScreenDisplayError( + __in BOOTSTRAPPER_DISPLAY display, + __in_z LPCWSTR wzBundleName, + __in HRESULT hrError + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/uithread.cpp b/src/burn/engine/uithread.cpp new file mode 100644 index 00000000..433cb171 --- /dev/null +++ b/src/burn/engine/uithread.cpp @@ -0,0 +1,222 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +#define BURN_UITHREAD_CLASS_WINDOW L"WixBurnMessageWindow" + + +// structs + +struct UITHREAD_CONTEXT +{ + HANDLE hInitializedEvent; + HINSTANCE hInstance; + BURN_ENGINE_STATE* pEngineState; +}; + +struct UITHREAD_INFO +{ + BOOL fElevated; + BURN_USER_EXPERIENCE* pUserExperience; +}; + + +// internal function declarations + +static DWORD WINAPI ThreadProc( + __in LPVOID pvContext + ); + +static LRESULT CALLBACK WndProc( + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ); + + +// function definitions + +HRESULT UiCreateMessageWindow( + __in HINSTANCE hInstance, + __in BURN_ENGINE_STATE* pEngineState + ) +{ + HRESULT hr = S_OK; + HANDLE rgWaitHandles[2] = { }; + UITHREAD_CONTEXT context = { }; + + // Create event to signal after the UI thread / window is initialized. + rgWaitHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL); + ExitOnNullWithLastError(rgWaitHandles[0], hr, "Failed to create initialization event."); + + // Pass necessary information to create the window. + context.hInitializedEvent = rgWaitHandles[0]; + context.hInstance = hInstance; + context.pEngineState = pEngineState; + + // Create our separate UI thread. + rgWaitHandles[1] = ::CreateThread(NULL, 0, ThreadProc, &context, 0, NULL); + ExitOnNullWithLastError(rgWaitHandles[1], hr, "Failed to create the UI thread."); + + // Wait for either the thread to be initialized or the window to exit / fail prematurely. + ::WaitForMultipleObjects(countof(rgWaitHandles), rgWaitHandles, FALSE, INFINITE); + + pEngineState->hMessageWindowThread = rgWaitHandles[1]; + rgWaitHandles[1] = NULL; + +LExit: + ReleaseHandle(rgWaitHandles[1]); + ReleaseHandle(rgWaitHandles[0]); + + return hr; +} + +void UiCloseMessageWindow( + __in BURN_ENGINE_STATE* pEngineState + ) +{ + if (::IsWindow(pEngineState->hMessageWindow)) + { + ::PostMessageW(pEngineState->hMessageWindow, WM_CLOSE, 0, 0); + + // Give the window 15 seconds to close because if it stays open it can prevent + // the engine from starting a reboot (should a reboot actually be necessary). + ::WaitForSingleObject(pEngineState->hMessageWindowThread, 15 * 1000); + } +} + + +// internal function definitions + +static DWORD WINAPI ThreadProc( + __in LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + UITHREAD_CONTEXT* pContext = static_cast(pvContext); + UITHREAD_INFO info = { }; + + WNDCLASSW wc = { }; + BOOL fRegistered = TRUE; + HWND hWnd = NULL; + + BOOL fRet = FALSE; + MSG msg = { }; + + BURN_ENGINE_STATE* pEngineState = pContext->pEngineState; + BOOL fElevated = BURN_MODE_ELEVATED == pContext->pEngineState->mode; + + // If elevated, set up the thread local storage to store the correct pipe to communicate logging. + if (fElevated) + { + Assert(TLS_OUT_OF_INDEXES != pEngineState->dwElevatedLoggingTlsId); + + if (!::TlsSetValue(pEngineState->dwElevatedLoggingTlsId, pEngineState->companionConnection.hPipe)) + { + // If the function failed we cannot write to the pipe so just terminate. + ExitFunction1(hr = E_INVALIDSTATE); + } + } + + wc.lpfnWndProc = WndProc; + wc.hInstance = pContext->hInstance; + wc.lpszClassName = BURN_UITHREAD_CLASS_WINDOW; + + if (!::RegisterClassW(&wc)) + { + ExitWithLastError(hr, "Failed to register window."); + } + + fRegistered = TRUE; + + info.fElevated = fElevated; + info.pUserExperience = &pEngineState->userExperience; + + // Create the window to handle reboots without activating it. + hWnd = ::CreateWindowExW(WS_EX_NOACTIVATE, wc.lpszClassName, NULL, WS_POPUP, 0, 0, 0, 0, HWND_DESKTOP, NULL, pContext->hInstance, &info); + ExitOnNullWithLastError(hWnd, hr, "Failed to create window."); + + ::ShowWindow(hWnd, SW_SHOWNA); + + // Persist the window handle and let the caller know we've initialized. + pEngineState->hMessageWindow = hWnd; + ::SetEvent(pContext->hInitializedEvent); + + // Pump messages until the window is closed. + while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) + { + if (-1 == fRet) + { + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Unexpected return value from message pump."); + } + else if (!::IsDialogMessageW(msg.hwnd, &msg)) + { + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + } + +LExit: + if (fRegistered) + { + ::UnregisterClassW(BURN_UITHREAD_CLASS_WINDOW, pContext->hInstance); + } + + return hr; +} + +static LRESULT CALLBACK WndProc( + __in HWND hWnd, + __in UINT uMsg, + __in WPARAM wParam, + __in LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_NCCREATE: + { + LPCREATESTRUCTW lpcs = reinterpret_cast(lParam); + ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(lpcs->lpCreateParams)); + break; + } + + case WM_NCDESTROY: + { + LRESULT lRes = ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0); + return lRes; + } + + case WM_QUERYENDSESSION: + { + DWORD dwEndSession = static_cast(lParam); + BOOL fCritical = ENDSESSION_CRITICAL & dwEndSession; + BOOL fCancel = TRUE; + BOOL fRet = FALSE; + + // Always block shutdown in the elevated process, but ask the BA in the non-elevated. + UITHREAD_INFO* pInfo = reinterpret_cast(::GetWindowLongPtrW(hWnd, GWLP_USERDATA)); + if (!pInfo->fElevated) + { + // TODO: instead of recommending canceling all non-critical shutdowns, maybe we should only recommend cancel + // when the engine is doing work? + fCancel = !fCritical; + // TODO: There's a race condition here where the BA may not have been loaded, or already was unloaded. + UserExperienceOnSystemShutdown(pInfo->pUserExperience, dwEndSession, &fCancel); + } + + fRet = !fCancel; + LogId(REPORT_STANDARD, MSG_SYSTEM_SHUTDOWN, LoggingBoolToString(fCritical), LoggingBoolToString(pInfo->fElevated), LoggingBoolToString(fRet)); + return fRet; + } + + case WM_DESTROY: + ::PostQuitMessage(0); + return 0; + } + + return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); +} diff --git a/src/burn/engine/uithread.h b/src/burn/engine/uithread.h new file mode 100644 index 00000000..41168d52 --- /dev/null +++ b/src/burn/engine/uithread.h @@ -0,0 +1,23 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// functions + +HRESULT UiCreateMessageWindow( + __in HINSTANCE hInstance, + __in BURN_ENGINE_STATE* pEngineState + ); + +void UiCloseMessageWindow( + __in BURN_ENGINE_STATE* pEngineState + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/update.cpp b/src/burn/engine/update.cpp new file mode 100644 index 00000000..b04fa9a4 --- /dev/null +++ b/src/burn/engine/update.cpp @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// internal function declarations + + +// function definitions + +extern "C" HRESULT UpdateParseFromXml( + __in BURN_UPDATE* pUpdate, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNode* pixnUpdateNode = NULL; + + hr = XmlSelectSingleNode(pixnBundle, L"Update", &pixnUpdateNode); + if (S_FALSE == hr) + { + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to select Bundle/Update node."); + + // @Location + hr = XmlGetAttributeEx(pixnUpdateNode, L"Location", &pUpdate->sczUpdateSource); + ExitOnFailure(hr, "Failed to get Update@Location."); + +LExit: + ReleaseObject(pixnUpdateNode); + + return hr; +} + +extern "C" void UpdateUninitialize( + __in BURN_UPDATE* pUpdate + ) +{ + PackageUninitialize(&pUpdate->package); + + ReleaseStr(pUpdate->sczUpdateSource); + memset(pUpdate, 0, sizeof(BURN_UPDATE)); +} diff --git a/src/burn/engine/update.h b/src/burn/engine/update.h new file mode 100644 index 00000000..67d40481 --- /dev/null +++ b/src/burn/engine/update.h @@ -0,0 +1,33 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// structs + +typedef struct _BURN_UPDATE +{ + BOOL fUpdateAvailable; + LPWSTR sczUpdateSource; + + BURN_PACKAGE package; +} BURN_UPDATE; + + +// function declarations + +HRESULT UpdateParseFromXml( + __in BURN_UPDATE* pUpdate, + __in IXMLDOMNode* pixnBundle + ); +void UpdateUninitialize( + __in BURN_UPDATE* pUpdate + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/userexperience.cpp b/src/burn/engine/userexperience.cpp new file mode 100644 index 00000000..2215a070 --- /dev/null +++ b/src/burn/engine/userexperience.cpp @@ -0,0 +1,2653 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// internal function declarations + +static int FilterResult( + __in DWORD dwAllowedResults, + __in int nResult + ); + +static HRESULT FilterExecuteResult( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __in BOOL fRollback, + __in BOOL fCancel, + __in LPCWSTR sczEventName + ); + +static HRESULT SendBAMessage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_APPLICATION_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ); + +static HRESULT SendBAMessageFromInactiveEngine( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_APPLICATION_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ); + + +// function definitions + +/******************************************************************* + UserExperienceParseFromXml - + +*******************************************************************/ +extern "C" HRESULT UserExperienceParseFromXml( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNode* pixnUserExperienceNode = NULL; + + // select UX node + hr = XmlSelectSingleNode(pixnBundle, L"UX", &pixnUserExperienceNode); + if (S_FALSE == hr) + { + hr = E_NOTFOUND; + } + ExitOnFailure(hr, "Failed to select user experience node."); + + // parse splash screen + hr = XmlGetYesNoAttribute(pixnUserExperienceNode, L"SplashScreen", &pUserExperience->fSplashScreen); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to to get UX/@SplashScreen"); + } + + // parse payloads + hr = PayloadsParseFromXml(&pUserExperience->payloads, NULL, NULL, pixnUserExperienceNode); + ExitOnFailure(hr, "Failed to parse user experience payloads."); + + // make sure we have at least one payload + if (0 == pUserExperience->payloads.cPayloads) + { + hr = E_UNEXPECTED; + ExitOnFailure(hr, "Too few UX payloads."); + } + +LExit: + ReleaseObject(pixnUserExperienceNode); + + return hr; +} + +/******************************************************************* + UserExperienceUninitialize - + +*******************************************************************/ +extern "C" void UserExperienceUninitialize( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + ReleaseStr(pUserExperience->sczTempDirectory); + PayloadsUninitialize(&pUserExperience->payloads); + + // clear struct + memset(pUserExperience, 0, sizeof(BURN_USER_EXPERIENCE)); +} + +/******************************************************************* + UserExperienceLoad - + +*******************************************************************/ +extern "C" HRESULT UserExperienceLoad( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_ENGINE_CONTEXT* pEngineContext, + __in BOOTSTRAPPER_COMMAND* pCommand + ) +{ + HRESULT hr = S_OK; + BOOTSTRAPPER_CREATE_ARGS args = { }; + BOOTSTRAPPER_CREATE_RESULTS results = { }; + + args.cbSize = sizeof(BOOTSTRAPPER_CREATE_ARGS); + args.pCommand = pCommand; + args.pfnBootstrapperEngineProc = EngineForApplicationProc; + args.pvBootstrapperEngineProcContext = pEngineContext; + args.qwEngineAPIVersion = MAKEQWORDVERSION(2021, 4, 27, 0); + + results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS); + + // Load BA DLL. + pUserExperience->hUXModule = ::LoadLibraryExW(pUserExperience->payloads.rgPayloads[0].sczLocalFilePath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + ExitOnNullWithLastError(pUserExperience->hUXModule, hr, "Failed to load BA DLL."); + + // Get BootstrapperApplicationCreate entry-point. + PFN_BOOTSTRAPPER_APPLICATION_CREATE pfnCreate = (PFN_BOOTSTRAPPER_APPLICATION_CREATE)::GetProcAddress(pUserExperience->hUXModule, "BootstrapperApplicationCreate"); + ExitOnNullWithLastError(pfnCreate, hr, "Failed to get BootstrapperApplicationCreate entry-point"); + + // Create BA. + hr = pfnCreate(&args, &results); + ExitOnFailure(hr, "Failed to create BA."); + + pUserExperience->pfnBAProc = results.pfnBootstrapperApplicationProc; + pUserExperience->pvBAProcContext = results.pvBootstrapperApplicationProcContext; + pUserExperience->fDisableUnloading = results.fDisableUnloading; + +LExit: + return hr; +} + +/******************************************************************* + UserExperienceUnload - + +*******************************************************************/ +extern "C" HRESULT UserExperienceUnload( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + + if (pUserExperience->hUXModule) + { + // Get BootstrapperApplicationDestroy entry-point and call it if it exists. + PFN_BOOTSTRAPPER_APPLICATION_DESTROY pfnDestroy = (PFN_BOOTSTRAPPER_APPLICATION_DESTROY)::GetProcAddress(pUserExperience->hUXModule, "BootstrapperApplicationDestroy"); + if (pfnDestroy) + { + pfnDestroy(); + } + + // Free BA DLL if it supports it. + if (!pUserExperience->fDisableUnloading && !::FreeLibrary(pUserExperience->hUXModule)) + { + hr = HRESULT_FROM_WIN32(::GetLastError()); + TraceError(hr, "Failed to unload BA DLL."); + } + pUserExperience->hUXModule = NULL; + } + +//LExit: + return hr; +} + +extern "C" HRESULT UserExperienceEnsureWorkingFolder( + __in LPCWSTR wzBundleId, + __deref_out_z LPWSTR* psczUserExperienceWorkingFolder + ) +{ + HRESULT hr = S_OK; + LPWSTR sczWorkingFolder = NULL; + + hr = CacheEnsureWorkingFolder(wzBundleId, &sczWorkingFolder); + ExitOnFailure(hr, "Failed to create working folder."); + + hr = StrAllocFormatted(psczUserExperienceWorkingFolder, L"%ls%ls\\", sczWorkingFolder, L".ba"); + ExitOnFailure(hr, "Failed to calculate the bootstrapper application working path."); + + hr = DirEnsureExists(*psczUserExperienceWorkingFolder, NULL); + ExitOnFailure(hr, "Failed create bootstrapper application working folder."); + +LExit: + ReleaseStr(sczWorkingFolder); + + return hr; +} + + +extern "C" HRESULT UserExperienceRemove( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + + // Remove temporary UX directory + if (pUserExperience->sczTempDirectory) + { + hr = DirEnsureDeleteEx(pUserExperience->sczTempDirectory, DIR_DELETE_FILES | DIR_DELETE_RECURSE | DIR_DELETE_SCHEDULE); + TraceError(hr, "Could not delete bootstrapper application folder. Some files will be left in the temp folder."); + } + +//LExit: + return hr; +} + +extern "C" int UserExperienceSendError( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_ERROR_TYPE errorType, + __in_z_opt LPCWSTR wzPackageId, + __in HRESULT hrCode, + __in_z_opt LPCWSTR wzError, + __in DWORD uiFlags, + __in int nRecommendation + ) +{ + int nResult = nRecommendation; + DWORD dwCode = HRESULT_CODE(hrCode); + LPWSTR sczError = NULL; + + // If no error string was provided, try to get the error string from the HRESULT. + if (!wzError) + { + if (SUCCEEDED(StrAllocFromError(&sczError, hrCode, NULL))) + { + wzError = sczError; + } + } + + UserExperienceOnError(pUserExperience, errorType, wzPackageId, dwCode, wzError, uiFlags, 0, NULL, &nResult); // ignore return value. + + ReleaseStr(sczError); + return nResult; +} + +extern "C" void UserExperienceActivateEngine( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + ::EnterCriticalSection(&pUserExperience->csEngineActive); + AssertSz(!pUserExperience->fEngineActive, "Engine should have been deactivated before activating it."); + pUserExperience->fEngineActive = TRUE; + ::LeaveCriticalSection(&pUserExperience->csEngineActive); +} + +extern "C" void UserExperienceDeactivateEngine( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + ::EnterCriticalSection(&pUserExperience->csEngineActive); + AssertSz(pUserExperience->fEngineActive, "Engine should have been active before deactivating it."); + pUserExperience->fEngineActive = FALSE; + ::LeaveCriticalSection(&pUserExperience->csEngineActive); +} + +extern "C" HRESULT UserExperienceEnsureEngineInactive( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + // Make a slight optimization here by ignoring the critical section, because all callers should have needed to enter it for their operation anyway. + HRESULT hr = pUserExperience->fEngineActive ? HRESULT_FROM_WIN32(ERROR_BUSY) : S_OK; + ExitOnRootFailure(hr, "Engine is active, cannot proceed."); + +LExit: + return hr; +} + +extern "C" void UserExperienceExecuteReset( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + pUserExperience->hrApplyError = S_OK; + pUserExperience->hwndApply = NULL; +} + +extern "C" void UserExperienceExecutePhaseComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrResult + ) +{ + if (FAILED(hrResult)) + { + pUserExperience->hrApplyError = hrResult; + } +} + +EXTERN_C BAAPI UserExperienceOnApplyBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in DWORD dwPhaseCount + ) +{ + HRESULT hr = S_OK; + BA_ONAPPLYBEGIN_ARGS args = { }; + BA_ONAPPLYBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.dwPhaseCount = dwPhaseCount; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnApplyBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnApplyComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __in BOOTSTRAPPER_APPLY_RESTART restart, + __inout BOOTSTRAPPER_APPLYCOMPLETE_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + BA_ONAPPLYCOMPLETE_ARGS args = { }; + BA_ONAPPLYCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + args.restart = restart; + args.recommendation = *pAction; + + results.cbSize = sizeof(results); + results.action = *pAction; + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnApplyComplete failed."); + + *pAction = results.action; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnBeginMsiTransactionBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId + ) +{ + HRESULT hr = S_OK; + BA_ONBEGINMSITRANSACTIONBEGIN_ARGS args = { }; + BA_ONBEGINMSITRANSACTIONBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzTransactionId = wzTransactionId; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONBEGINMSITRANSACTIONBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnBeginMsiTransactionBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnBeginMsiTransactionComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONBEGINMSITRANSACTIONCOMPLETE_ARGS args = { }; + BA_ONBEGINMSITRANSACTIONCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzTransactionId = wzTransactionId; + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONBEGINMSITRANSACTIONCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnBeginMsiTransactionComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheAcquireBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in_z LPWSTR* pwzSource, + __in_z LPWSTR* pwzDownloadUrl, + __in_z_opt LPCWSTR wzPayloadContainerId, + __out BOOTSTRAPPER_CACHE_OPERATION* pCacheOperation + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEACQUIREBEGIN_ARGS args = { }; + BA_ONCACHEACQUIREBEGIN_RESULTS results = { }; + *pCacheOperation = BOOTSTRAPPER_CACHE_OPERATION_NONE; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + args.wzSource = *pwzSource; + args.wzDownloadUrl = *pwzDownloadUrl; + args.wzPayloadContainerId = wzPayloadContainerId; + args.recommendation = *pCacheOperation; + + results.cbSize = sizeof(results); + results.action = *pCacheOperation; + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEACQUIREBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnCacheAcquireBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + else + { + // Verify the BA requested an action that is possible. + if (BOOTSTRAPPER_CACHE_OPERATION_DOWNLOAD == results.action && *pwzDownloadUrl && **pwzDownloadUrl || + BOOTSTRAPPER_CACHE_OPERATION_EXTRACT == results.action && wzPayloadContainerId || + BOOTSTRAPPER_CACHE_OPERATION_COPY == results.action || + BOOTSTRAPPER_CACHE_OPERATION_NONE == results.action) + { + *pCacheOperation = results.action; + } + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheAcquireComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in HRESULT hrStatus, + __inout BOOL* pfRetry + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEACQUIRECOMPLETE_ARGS args = { }; + BA_ONCACHEACQUIRECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + args.hrStatus = hrStatus; + args.recommendation = *pfRetry ? BOOTSTRAPPER_CACHEACQUIRECOMPLETE_ACTION_RETRY : BOOTSTRAPPER_CACHEACQUIRECOMPLETE_ACTION_NONE; + + results.cbSize = sizeof(results); + results.action = args.recommendation; + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEACQUIRECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnCacheAcquireComplete failed."); + + if (FAILED(hrStatus)) + { + *pfRetry = BOOTSTRAPPER_CACHEACQUIRECOMPLETE_ACTION_RETRY == results.action; + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheAcquireProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in DWORD dwOverallPercentage + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEACQUIREPROGRESS_ARGS args = { }; + BA_ONCACHEACQUIREPROGRESS_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + args.dw64Progress = dw64Progress; + args.dw64Total = dw64Total; + args.dwOverallPercentage = dwOverallPercentage; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEACQUIREPROGRESS, &args, &results); + ExitOnFailure(hr, "BA OnCacheAcquireProgress failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheAcquireResolving( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in_z LPWSTR* rgSearchPaths, + __in DWORD cSearchPaths, + __in BOOL fFoundLocal, + __in DWORD* pdwChosenSearchPath, + __in_z_opt LPWSTR* pwzDownloadUrl, + __in_z_opt LPCWSTR wzPayloadContainerId, + __inout BOOTSTRAPPER_CACHE_RESOLVE_OPERATION* pCacheOperation + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEACQUIRERESOLVING_ARGS args = { }; + BA_ONCACHEACQUIRERESOLVING_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + args.rgSearchPaths = const_cast(rgSearchPaths); + args.cSearchPaths = cSearchPaths; + args.fFoundLocal = fFoundLocal; + args.dwRecommendedSearchPath = *pdwChosenSearchPath; + args.wzDownloadUrl = *pwzDownloadUrl; + args.recommendation = *pCacheOperation; + + results.cbSize = sizeof(results); + results.dwChosenSearchPath = *pdwChosenSearchPath; + results.action = *pCacheOperation; + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEACQUIRERESOLVING, &args, &results); + ExitOnFailure(hr, "BA OnCacheAcquireResolving failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + else + { + // Verify the BA requested an action that is possible. + if (BOOTSTRAPPER_CACHE_RESOLVE_DOWNLOAD == results.action && *pwzDownloadUrl && **pwzDownloadUrl || + BOOTSTRAPPER_CACHE_RESOLVE_CONTAINER == results.action && wzPayloadContainerId || + BOOTSTRAPPER_CACHE_RESOLVE_RETRY == results.action || + BOOTSTRAPPER_CACHE_RESOLVE_NONE == results.action) + { + *pCacheOperation = results.action; + } + else if (BOOTSTRAPPER_CACHE_RESOLVE_LOCAL == results.action && results.dwChosenSearchPath < cSearchPaths) + { + *pdwChosenSearchPath = results.dwChosenSearchPath; + *pCacheOperation = results.action; + } + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEBEGIN_ARGS args = { }; + BA_ONCACHEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnCacheBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONCACHECOMPLETE_ARGS args = { }; + BA_ONCACHECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnCacheComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheContainerOrPayloadVerifyBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId + ) +{ + HRESULT hr = S_OK; + BA_ONCACHECONTAINERORPAYLOADVERIFYBEGIN_ARGS args = { }; + BA_ONCACHECONTAINERORPAYLOADVERIFYBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHECONTAINERORPAYLOADVERIFYBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnCacheContainerOrPayloadVerifyBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheContainerOrPayloadVerifyComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONCACHECONTAINERORPAYLOADVERIFYCOMPLETE_ARGS args = { }; + BA_ONCACHECONTAINERORPAYLOADVERIFYCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHECONTAINERORPAYLOADVERIFYCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnCacheContainerOrPayloadVerifyComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheContainerOrPayloadVerifyProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in DWORD dwOverallPercentage + ) +{ + HRESULT hr = S_OK; + BA_ONCACHECONTAINERORPAYLOADVERIFYPROGRESS_ARGS args = { }; + BA_ONCACHECONTAINERORPAYLOADVERIFYPROGRESS_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + args.dw64Progress = dw64Progress; + args.dw64Total = dw64Total; + args.dwOverallPercentage = dwOverallPercentage; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHECONTAINERORPAYLOADVERIFYPROGRESS, &args, &results); + ExitOnFailure(hr, "BA OnCacheContainerOrPayloadVerifyProgress failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCachePackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in DWORD cCachePayloads, + __in DWORD64 dw64PackageCacheSize + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEPACKAGEBEGIN_ARGS args = { }; + BA_ONCACHEPACKAGEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.cCachePayloads = cCachePayloads; + args.dw64PackageCacheSize = dw64PackageCacheSize; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEPACKAGEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnCachePackageBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCachePackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in HRESULT hrStatus, + __inout BOOTSTRAPPER_CACHEPACKAGECOMPLETE_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEPACKAGECOMPLETE_ARGS args = { }; + BA_ONCACHEPACKAGECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.hrStatus = hrStatus; + args.recommendation = *pAction; + + results.cbSize = sizeof(results); + results.action = *pAction; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEPACKAGECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnCachePackageComplete failed."); + + if (FAILED(hrStatus)) + { + *pAction = results.action; + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCachePayloadExtractBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzContainerId, + __in_z_opt LPCWSTR wzPayloadId + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEPAYLOADEXTRACTBEGIN_ARGS args = { }; + BA_ONCACHEPAYLOADEXTRACTBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzContainerId = wzContainerId; + args.wzPayloadId = wzPayloadId; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEPAYLOADEXTRACTBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnCachePayloadExtractBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCachePayloadExtractComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEPAYLOADEXTRACTCOMPLETE_ARGS args = { }; + BA_ONCACHEPAYLOADEXTRACTCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzContainerId = wzContainerId; + args.wzPayloadId = wzPayloadId; + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEPAYLOADEXTRACTCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnCachePayloadExtractComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCachePayloadExtractProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in DWORD dwOverallPercentage + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEPAYLOADEXTRACTPROGRESS_ARGS args = { }; + BA_ONCACHEPAYLOADEXTRACTPROGRESS_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzContainerId = wzContainerId; + args.wzPayloadId = wzPayloadId; + args.dw64Progress = dw64Progress; + args.dw64Total = dw64Total; + args.dwOverallPercentage = dwOverallPercentage; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEPAYLOADEXTRACTPROGRESS, &args, &results); + ExitOnFailure(hr, "BA OnCachePayloadExtractProgress failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheVerifyBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEVERIFYBEGIN_ARGS args = { }; + BA_ONCACHEVERIFYBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEVERIFYBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnCacheVerifyBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheVerifyComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in HRESULT hrStatus, + __inout BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEVERIFYCOMPLETE_ARGS args = { }; + BA_ONCACHEVERIFYCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + args.hrStatus = hrStatus; + args.recommendation = *pAction; + + results.cbSize = sizeof(results); + results.action = *pAction; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEVERIFYCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnCacheVerifyComplete failed."); + + if (FAILED(hrStatus)) + { + *pAction = results.action; + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCacheVerifyProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in DWORD dwOverallPercentage, + __in BOOTSTRAPPER_CACHE_VERIFY_STEP verifyStep + ) +{ + HRESULT hr = S_OK; + BA_ONCACHEVERIFYPROGRESS_ARGS args = { }; + BA_ONCACHEVERIFYPROGRESS_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageOrContainerId = wzPackageOrContainerId; + args.wzPayloadId = wzPayloadId; + args.dw64Progress = dw64Progress; + args.dw64Total = dw64Total; + args.dwOverallPercentage = dwOverallPercentage; + args.verifyStep = verifyStep; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCACHEVERIFYPROGRESS, &args, &results); + ExitOnFailure(hr, "BA OnCacheVerifyProgress failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCommitMsiTransactionBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId + ) +{ + HRESULT hr = S_OK; + BA_ONCOMMITMSITRANSACTIONBEGIN_ARGS args = { }; + BA_ONCOMMITMSITRANSACTIONBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzTransactionId = wzTransactionId; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCOMMITMSITRANSACTIONBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnCommitMsiTransactionBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnCommitMsiTransactionComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONCOMMITMSITRANSACTIONCOMPLETE_ARGS args = { }; + BA_ONCOMMITMSITRANSACTIONCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzTransactionId = wzTransactionId; + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONCOMMITMSITRANSACTIONCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnCommitMsiTransactionComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fCached, + __in BOOL fInstalled, + __in DWORD cPackages + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTBEGIN_ARGS args = { }; + BA_ONDETECTBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.cPackages = cPackages; + args.fInstalled = fInstalled; + args.fCached = fCached; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnDetectBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __in BOOL fEligibleForCleanup + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTCOMPLETE_ARGS args = { }; + BA_ONDETECTCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + args.fEligibleForCleanup = fEligibleForCleanup; + + results.cbSize = sizeof(results); + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnDetectComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectForwardCompatibleBundle( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzBundleId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in_z LPCWSTR wzBundleTag, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion, + __in BOOL fMissingFromCache + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTFORWARDCOMPATIBLEBUNDLE_ARGS args = { }; + BA_ONDETECTFORWARDCOMPATIBLEBUNDLE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzBundleId = wzBundleId; + args.relationType = relationType; + args.wzBundleTag = wzBundleTag; + args.fPerMachine = fPerMachine; + args.wzVersion = pVersion->sczVersion; + args.fMissingFromCache = fMissingFromCache; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTFORWARDCOMPATIBLEBUNDLE, &args, &results); + ExitOnFailure(hr, "BA OnDetectForwardCompatibleBundle failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectMsiFeature( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzFeatureId, + __in BOOTSTRAPPER_FEATURE_STATE state + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTMSIFEATURE_ARGS args = { }; + BA_ONDETECTMSIFEATURE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzFeatureId = wzFeatureId; + args.state = state; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTMSIFEATURE, &args, &results); + ExitOnFailure(hr, "BA OnDetectMsiFeature failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectPackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTPACKAGEBEGIN_ARGS args = { }; + BA_ONDETECTPACKAGEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTPACKAGEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnDetectPackageBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectPackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in HRESULT hrStatus, + __in BOOTSTRAPPER_PACKAGE_STATE state, + __in BOOL fCached + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTPACKAGECOMPLETE_ARGS args = { }; + BA_ONDETECTPACKAGECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.hrStatus = hrStatus; + args.state = state; + args.fCached = fCached; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTPACKAGECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnDetectPackageComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectRelatedBundle( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzBundleId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in_z LPCWSTR wzBundleTag, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion, + __in BOOTSTRAPPER_RELATED_OPERATION operation, + __in BOOL fMissingFromCache + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTRELATEDBUNDLE_ARGS args = { }; + BA_ONDETECTRELATEDBUNDLE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzBundleId = wzBundleId; + args.relationType = relationType; + args.wzBundleTag = wzBundleTag; + args.fPerMachine = fPerMachine; + args.wzVersion = pVersion->sczVersion; + args.operation = operation; + args.fMissingFromCache = fMissingFromCache; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTRELATEDBUNDLE, &args, &results); + ExitOnFailure(hr, "BA OnDetectRelatedBundle failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectRelatedMsiPackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzUpgradeCode, + __in_z LPCWSTR wzProductCode, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion, + __in BOOTSTRAPPER_RELATED_OPERATION operation + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTRELATEDMSIPACKAGE_ARGS args = { }; + BA_ONDETECTRELATEDMSIPACKAGE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzUpgradeCode = wzUpgradeCode; + args.wzProductCode = wzProductCode; + args.fPerMachine = fPerMachine; + args.wzVersion = pVersion->sczVersion; + args.operation = operation; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTRELATEDMSIPACKAGE, &args, &results); + ExitOnFailure(hr, "BA OnDetectRelatedMsiPackage failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectPatchTarget( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzProductCode, + __in BOOTSTRAPPER_PACKAGE_STATE patchState + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTPATCHTARGET_ARGS args = { }; + BA_ONDETECTPATCHTARGET_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzProductCode = wzProductCode; + args.patchState = patchState; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTPATCHTARGET, &args, &results); + ExitOnFailure(hr, "BA OnDetectPatchTarget failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectUpdate( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzUpdateLocation, + __in DWORD64 dw64Size, + __in VERUTIL_VERSION* pVersion, + __in_z_opt LPCWSTR wzTitle, + __in_z_opt LPCWSTR wzSummary, + __in_z_opt LPCWSTR wzContentType, + __in_z_opt LPCWSTR wzContent, + __inout BOOL* pfStopProcessingUpdates + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTUPDATE_ARGS args = { }; + BA_ONDETECTUPDATE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzUpdateLocation = wzUpdateLocation; + args.dw64Size = dw64Size; + args.wzVersion = pVersion->sczVersion; + args.wzTitle = wzTitle; + args.wzSummary = wzSummary; + args.wzContentType = wzContentType; + args.wzContent = wzContent; + + results.cbSize = sizeof(results); + results.fStopProcessingUpdates = *pfStopProcessingUpdates; + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTUPDATE, &args, &results); + ExitOnFailure(hr, "BA OnDetectUpdate failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + *pfStopProcessingUpdates = results.fStopProcessingUpdates; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectUpdateBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzUpdateLocation, + __inout BOOL* pfSkip + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTUPDATEBEGIN_ARGS args = { }; + BA_ONDETECTUPDATEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzUpdateLocation = wzUpdateLocation; + + results.cbSize = sizeof(results); + results.fSkip = *pfSkip; + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTUPDATEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnDetectUpdateBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + *pfSkip = results.fSkip; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnDetectUpdateComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __inout BOOL* pfIgnoreError + ) +{ + HRESULT hr = S_OK; + BA_ONDETECTUPDATECOMPLETE_ARGS args = { }; + BA_ONDETECTUPDATECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + results.fIgnoreError = *pfIgnoreError; + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONDETECTUPDATECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnDetectUpdateComplete failed."); + + if (FAILED(hrStatus)) + { + *pfIgnoreError = results.fIgnoreError; + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnElevateBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + BA_ONELEVATEBEGIN_ARGS args = { }; + BA_ONELEVATEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONELEVATEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnElevateBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnElevateComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONELEVATECOMPLETE_ARGS args = { }; + BA_ONELEVATECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONELEVATECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnElevateComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnError( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_ERROR_TYPE errorType, + __in_z_opt LPCWSTR wzPackageId, + __in DWORD dwCode, + __in_z_opt LPCWSTR wzError, + __in DWORD dwUIHint, + __in DWORD cData, + __in_ecount_z_opt(cData) LPCWSTR* rgwzData, + __inout int* pnResult + ) +{ + HRESULT hr = S_OK; + BA_ONERROR_ARGS args = { }; + BA_ONERROR_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.errorType = errorType; + args.wzPackageId = wzPackageId; + args.dwCode = dwCode; + args.wzError = wzError; + args.dwUIHint = dwUIHint; + args.cData = cData; + args.rgwzData = rgwzData; + args.nRecommendation = *pnResult; + + results.cbSize = sizeof(results); + results.nResult = *pnResult; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONERROR, &args, &results); + ExitOnFailure(hr, "BA OnError failed."); + + *pnResult = results.nResult; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnExecuteBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in DWORD cExecutingPackages + ) +{ + HRESULT hr = S_OK; + BA_ONEXECUTEBEGIN_ARGS args = { }; + BA_ONEXECUTEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.cExecutingPackages = cExecutingPackages; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnExecuteBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnExecuteComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONEXECUTECOMPLETE_ARGS args = { }; + BA_ONEXECUTECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnExecuteComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnExecuteFilesInUse( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in DWORD cFiles, + __in_ecount_z_opt(cFiles) LPCWSTR* rgwzFiles, + __inout int* pnResult + ) +{ + HRESULT hr = S_OK; + BA_ONEXECUTEFILESINUSE_ARGS args = { }; + BA_ONEXECUTEFILESINUSE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.cFiles = cFiles; + args.rgwzFiles = rgwzFiles; + args.nRecommendation = *pnResult; + + results.cbSize = sizeof(results); + results.nResult = *pnResult; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEFILESINUSE, &args, &results); + ExitOnFailure(hr, "BA OnExecuteFilesInUse failed."); + + *pnResult = results.nResult; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnExecuteMsiMessage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in INSTALLMESSAGE messageType, + __in DWORD dwUIHint, + __in_z LPCWSTR wzMessage, + __in DWORD cData, + __in_ecount_z_opt(cData) LPCWSTR* rgwzData, + __inout int* pnResult + ) +{ + HRESULT hr = S_OK; + BA_ONEXECUTEMSIMESSAGE_ARGS args = { }; + BA_ONEXECUTEMSIMESSAGE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.messageType = messageType; + args.dwUIHint = dwUIHint; + args.wzMessage = wzMessage; + args.cData = cData; + args.rgwzData = rgwzData; + args.nRecommendation = *pnResult; + + results.cbSize = sizeof(results); + results.nResult = *pnResult; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEMSIMESSAGE, &args, &results); + ExitOnFailure(hr, "BA OnExecuteMsiMessage failed."); + + *pnResult = results.nResult; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnExecutePackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in BOOL fExecute, + __in BOOTSTRAPPER_ACTION_STATE action, + __in INSTALLUILEVEL uiLevel, + __in BOOL fDisableExternalUiHandler + ) +{ + HRESULT hr = S_OK; + BA_ONEXECUTEPACKAGEBEGIN_ARGS args = { }; + BA_ONEXECUTEPACKAGEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.fExecute = fExecute; + args.action = action; + args.uiLevel = uiLevel; + args.fDisableExternalUiHandler = fDisableExternalUiHandler; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPACKAGEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnExecutePackageBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnExecutePackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in HRESULT hrStatus, + __in BOOTSTRAPPER_APPLY_RESTART restart, + __inout BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + BA_ONEXECUTEPACKAGECOMPLETE_ARGS args = { }; + BA_ONEXECUTEPACKAGECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.hrStatus = hrStatus; + args.restart = restart; + args.recommendation = *pAction; + + results.cbSize = sizeof(results); + results.action = *pAction; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPACKAGECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnExecutePackageComplete failed."); + + *pAction = results.action; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnExecutePatchTarget( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzTargetProductCode + ) +{ + HRESULT hr = S_OK; + BA_ONEXECUTEPATCHTARGET_ARGS args = { }; + BA_ONEXECUTEPATCHTARGET_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzTargetProductCode = wzTargetProductCode; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPATCHTARGET, &args, &results); + ExitOnFailure(hr, "BA OnExecutePatchTarget failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnExecuteProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in DWORD dwProgressPercentage, + __in DWORD dwOverallPercentage, + __out int* pnResult + ) +{ + HRESULT hr = S_OK; + BA_ONEXECUTEPROGRESS_ARGS args = { }; + BA_ONEXECUTEPROGRESS_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.dwProgressPercentage = dwProgressPercentage; + args.dwOverallPercentage = dwOverallPercentage; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROGRESS, &args, &results); + ExitOnFailure(hr, "BA OnExecuteProgress failed."); + +LExit: + if (FAILED(hr)) + { + *pnResult = IDERROR; + } + else if (results.fCancel) + { + *pnResult = IDCANCEL; + } + else + { + *pnResult = IDNOACTION; + } + return hr; +} + +EXTERN_C BAAPI UserExperienceOnLaunchApprovedExeBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + BA_ONLAUNCHAPPROVEDEXEBEGIN_ARGS args = { }; + BA_ONLAUNCHAPPROVEDEXEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONLAUNCHAPPROVEDEXEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnLaunchApprovedExeBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnLaunchApprovedExeComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __in DWORD dwProcessId + ) +{ + HRESULT hr = S_OK; + BA_ONLAUNCHAPPROVEDEXECOMPLETE_ARGS args = { }; + BA_ONLAUNCHAPPROVEDEXECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + args.dwProcessId = dwProcessId; + + results.cbSize = sizeof(results); + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONLAUNCHAPPROVEDEXECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnLaunchApprovedExeComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPauseAUBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + BA_ONPAUSEAUTOMATICUPDATESBEGIN_ARGS args = { }; + BA_ONPAUSEAUTOMATICUPDATESBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPAUSEAUTOMATICUPDATESBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnPauseAUBegin failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPauseAUComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONPAUSEAUTOMATICUPDATESCOMPLETE_ARGS args = { }; + BA_ONPAUSEAUTOMATICUPDATESCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPAUSEAUTOMATICUPDATESCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnPauseAUComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in DWORD cPackages + ) +{ + HRESULT hr = S_OK; + BA_ONPLANBEGIN_ARGS args = { }; + BA_ONPLANBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.cPackages = cPackages; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnPlanBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanMsiFeature( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzFeatureId, + __inout BOOTSTRAPPER_FEATURE_STATE* pRequestedState + ) +{ + HRESULT hr = S_OK; + BA_ONPLANMSIFEATURE_ARGS args = { }; + BA_ONPLANMSIFEATURE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzFeatureId = wzFeatureId; + args.recommendedState = *pRequestedState; + + results.cbSize = sizeof(results); + results.requestedState = *pRequestedState; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANMSIFEATURE, &args, &results); + ExitOnFailure(hr, "BA OnPlanMsiFeature failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + *pRequestedState = results.requestedState; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONPLANCOMPLETE_ARGS args = { }; + BA_ONPLANCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessageFromInactiveEngine(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnPlanComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanForwardCompatibleBundle( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzBundleId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in_z LPCWSTR wzBundleTag, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion, + __inout BOOL* pfIgnoreBundle + ) +{ + HRESULT hr = S_OK; + BA_ONPLANFORWARDCOMPATIBLEBUNDLE_ARGS args = { }; + BA_ONPLANFORWARDCOMPATIBLEBUNDLE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzBundleId = wzBundleId; + args.relationType = relationType; + args.wzBundleTag = wzBundleTag; + args.fPerMachine = fPerMachine; + args.wzVersion = pVersion->sczVersion; + args.fRecommendedIgnoreBundle = *pfIgnoreBundle; + + results.cbSize = sizeof(results); + results.fIgnoreBundle = *pfIgnoreBundle; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANFORWARDCOMPATIBLEBUNDLE, &args, &results); + ExitOnFailure(hr, "BA OnPlanForwardCompatibleBundle failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + *pfIgnoreBundle = results.fIgnoreBundle; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanMsiPackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in BOOL fExecute, + __in BOOTSTRAPPER_ACTION_STATE action, + __inout BURN_MSI_PROPERTY* pActionMsiProperty, + __inout INSTALLUILEVEL* pUiLevel, + __inout BOOL* pfDisableExternalUiHandler + ) +{ + HRESULT hr = S_OK; + BA_ONPLANMSIPACKAGE_ARGS args = { }; + BA_ONPLANMSIPACKAGE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.fExecute = fExecute; + args.action = action; + + results.cbSize = sizeof(results); + results.actionMsiProperty = *pActionMsiProperty; + results.uiLevel = *pUiLevel; + results.fDisableExternalUiHandler = *pfDisableExternalUiHandler; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANMSIPACKAGE, &args, &results); + ExitOnFailure(hr, "BA OnPlanMsiPackage failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + *pActionMsiProperty = results.actionMsiProperty; + *pUiLevel = results.uiLevel; + *pfDisableExternalUiHandler = results.fDisableExternalUiHandler; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlannedPackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in BOOTSTRAPPER_ACTION_STATE execute, + __in BOOTSTRAPPER_ACTION_STATE rollback, + __in BOOL fPlannedCache, + __in BOOL fPlannedUncache + ) +{ + HRESULT hr = S_OK; + BA_ONPLANNEDPACKAGE_ARGS args = { }; + BA_ONPLANNEDPACKAGE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.execute = execute; + args.rollback = rollback; + args.fPlannedCache = fPlannedCache; + args.fPlannedUncache = fPlannedUncache; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANNEDPACKAGE, &args, &results); + ExitOnFailure(hr, "BA OnPlannedPackage failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanPackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in BOOTSTRAPPER_PACKAGE_STATE state, + __in BOOL fCached, + __in BOOTSTRAPPER_PACKAGE_CONDITION_RESULT installCondition, + __inout BOOTSTRAPPER_REQUEST_STATE* pRequestedState, + __inout BOOTSTRAPPER_CACHE_TYPE* pRequestedCacheType + ) +{ + HRESULT hr = S_OK; + BA_ONPLANPACKAGEBEGIN_ARGS args = { }; + BA_ONPLANPACKAGEBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.state = state; + args.fCached = fCached; + args.installCondition = installCondition; + args.recommendedState = *pRequestedState; + args.recommendedCacheType = *pRequestedCacheType; + + results.cbSize = sizeof(results); + results.requestedState = *pRequestedState; + results.requestedCacheType = *pRequestedCacheType; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANPACKAGEBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnPlanPackageBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + *pRequestedState = results.requestedState; + *pRequestedCacheType = results.requestedCacheType; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanPackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in HRESULT hrStatus, + __in BOOTSTRAPPER_REQUEST_STATE requested + ) +{ + HRESULT hr = S_OK; + BA_ONPLANPACKAGECOMPLETE_ARGS args = { }; + BA_ONPLANPACKAGECOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.hrStatus = hrStatus; + args.requested = requested; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANPACKAGECOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnPlanPackageComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanRelatedBundle( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzBundleId, + __inout BOOTSTRAPPER_REQUEST_STATE* pRequestedState + ) +{ + HRESULT hr = S_OK; + BA_ONPLANRELATEDBUNDLE_ARGS args = { }; + BA_ONPLANRELATEDBUNDLE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzBundleId = wzBundleId; + args.recommendedState = *pRequestedState; + + results.cbSize = sizeof(results); + results.requestedState = *pRequestedState; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLE, &args, &results); + ExitOnFailure(hr, "BA OnPlanRelatedBundle failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + *pRequestedState = results.requestedState; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnPlanPatchTarget( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzProductCode, + __inout BOOTSTRAPPER_REQUEST_STATE* pRequestedState + ) +{ + HRESULT hr = S_OK; + BA_ONPLANPATCHTARGET_ARGS args = { }; + BA_ONPLANPATCHTARGET_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzPackageId = wzPackageId; + args.wzProductCode = wzProductCode; + args.recommendedState = *pRequestedState; + + results.cbSize = sizeof(results); + results.requestedState = *pRequestedState; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANPATCHTARGET, &args, &results); + ExitOnFailure(hr, "BA OnPlanPatchTarget failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + *pRequestedState = results.requestedState; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fRollback, + __in DWORD dwProgressPercentage, + __in DWORD dwOverallPercentage + ) +{ + HRESULT hr = S_OK; + BA_ONPROGRESS_ARGS args = { }; + BA_ONPROGRESS_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.dwProgressPercentage = dwProgressPercentage; + args.dwOverallPercentage = dwOverallPercentage; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONPROGRESS, &args, &results); + hr = FilterExecuteResult(pUserExperience, hr, fRollback, results.fCancel, L"OnProgress"); + + return hr; +} + +EXTERN_C BAAPI UserExperienceOnRegisterBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + BA_ONREGISTERBEGIN_ARGS args = { }; + BA_ONREGISTERBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONREGISTERBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnRegisterBegin failed."); + + if (results.fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnRegisterComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONREGISTERCOMPLETE_ARGS args = { }; + BA_ONREGISTERCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONREGISTERCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnRegisterComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnRollbackMsiTransactionBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId + ) +{ + HRESULT hr = S_OK; + BA_ONROLLBACKMSITRANSACTIONBEGIN_ARGS args = { }; + BA_ONROLLBACKMSITRANSACTIONBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzTransactionId = wzTransactionId; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONROLLBACKMSITRANSACTIONBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnRollbackMsiTransactionBegin failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnRollbackMsiTransactionComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONROLLBACKMSITRANSACTIONCOMPLETE_ARGS args = { }; + BA_ONROLLBACKMSITRANSACTIONCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzTransactionId = wzTransactionId; + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONROLLBACKMSITRANSACTIONCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnRollbackMsiTransactionComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnShutdown( + __in BURN_USER_EXPERIENCE* pUserExperience, + __inout BOOTSTRAPPER_SHUTDOWN_ACTION* pAction + ) +{ + HRESULT hr = S_OK; + BA_ONSHUTDOWN_ARGS args = { }; + BA_ONSHUTDOWN_RESULTS results = { }; + + args.cbSize = sizeof(args); + + results.cbSize = sizeof(results); + results.action = *pAction; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONSHUTDOWN, &args, &results); + ExitOnFailure(hr, "BA OnShutdown failed."); + + *pAction = results.action; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnStartup( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + BA_ONSTARTUP_ARGS args = { }; + BA_ONSTARTUP_RESULTS results = { }; + + args.cbSize = sizeof(args); + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONSTARTUP, &args, &results); + ExitOnFailure(hr, "BA OnStartup failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnSystemRestorePointBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ) +{ + HRESULT hr = S_OK; + BA_ONSYSTEMRESTOREPOINTBEGIN_ARGS args = { }; + BA_ONSYSTEMRESTOREPOINTBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONSYSTEMRESTOREPOINTBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnSystemRestorePointBegin failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnSystemRestorePointComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONSYSTEMRESTOREPOINTCOMPLETE_ARGS args = { }; + BA_ONSYSTEMRESTOREPOINTCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONSYSTEMRESTOREPOINTCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnSystemRestorePointComplete failed."); + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnSystemShutdown( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in DWORD dwEndSession, + __inout BOOL* pfCancel + ) +{ + HRESULT hr = S_OK; + BA_ONSYSTEMSHUTDOWN_ARGS args = { }; + BA_ONSYSTEMSHUTDOWN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.dwEndSession = dwEndSession; + + results.cbSize = sizeof(results); + results.fCancel = *pfCancel; + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONSYSTEMSHUTDOWN, &args, &results); + ExitOnFailure(hr, "BA OnSystemShutdown failed."); + + *pfCancel = results.fCancel; + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnUnregisterBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __inout BOOL* pfKeepRegistration + ) +{ + HRESULT hr = S_OK; + BA_ONUNREGISTERBEGIN_ARGS args = { }; + BA_ONUNREGISTERBEGIN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.fKeepRegistration = *pfKeepRegistration; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONUNREGISTERBEGIN, &args, &results); + ExitOnFailure(hr, "BA OnUnregisterBegin failed."); + + if (!args.fKeepRegistration && results.fForceKeepRegistration) + { + *pfKeepRegistration = TRUE; + } + +LExit: + return hr; +} + +EXTERN_C BAAPI UserExperienceOnUnregisterComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ) +{ + HRESULT hr = S_OK; + BA_ONUNREGISTERCOMPLETE_ARGS args = { }; + BA_ONUNREGISTERCOMPLETE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.hrStatus = hrStatus; + + results.cbSize = sizeof(results); + + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONUNREGISTERCOMPLETE, &args, &results); + ExitOnFailure(hr, "BA OnUnregisterComplete failed."); + +LExit: + return hr; +} + +extern "C" int UserExperienceCheckExecuteResult( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fRollback, + __in DWORD dwAllowedResults, + __in int nResult + ) +{ + // Do not allow canceling while rolling back. + if (fRollback && (IDCANCEL == nResult || IDABORT == nResult)) + { + nResult = IDNOACTION; + } + else if (FAILED(pUserExperience->hrApplyError) && !fRollback) // if we failed cancel except not during rollback. + { + nResult = IDCANCEL; + } + + nResult = FilterResult(dwAllowedResults, nResult); + return nResult; +} + +extern "C" HRESULT UserExperienceInterpretExecuteResult( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fRollback, + __in DWORD dwAllowedResults, + __in int nResult + ) +{ + HRESULT hr = S_OK; + + // If we failed return that error unless this is rollback which should roll on. + if (FAILED(pUserExperience->hrApplyError) && !fRollback) + { + hr = pUserExperience->hrApplyError; + } + else + { + int nCheckedResult = UserExperienceCheckExecuteResult(pUserExperience, fRollback, dwAllowedResults, nResult); + hr = IDOK == nCheckedResult || IDNOACTION == nCheckedResult ? S_OK : IDCANCEL == nCheckedResult || IDABORT == nCheckedResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE); + } + + return hr; +} + + +// internal functions + +static int FilterResult( + __in DWORD dwAllowedResults, + __in int nResult + ) +{ + DWORD dwFilteredAllowedResults = dwAllowedResults & MB_TYPEMASK; + if (IDNOACTION == nResult || IDERROR == nResult) // do nothing and errors pass through. + { + } + else + { + switch (dwFilteredAllowedResults) + { + case MB_OK: + nResult = IDOK; + break; + + case MB_OKCANCEL: + if (IDOK == nResult || IDYES == nResult) + { + nResult = IDOK; + } + else if (IDCANCEL == nResult || IDABORT == nResult || IDNO == nResult) + { + nResult = IDCANCEL; + } + else + { + nResult = IDNOACTION; + } + break; + + case MB_ABORTRETRYIGNORE: + if (IDCANCEL == nResult || IDABORT == nResult) + { + nResult = IDABORT; + } + else if (IDRETRY == nResult || IDTRYAGAIN == nResult) + { + nResult = IDRETRY; + } + else if (IDIGNORE == nResult) + { + nResult = IDIGNORE; + } + else + { + nResult = IDNOACTION; + } + break; + + case MB_YESNO: + if (IDOK == nResult || IDYES == nResult) + { + nResult = IDYES; + } + else if (IDCANCEL == nResult || IDABORT == nResult || IDNO == nResult) + { + nResult = IDNO; + } + else + { + nResult = IDNOACTION; + } + break; + + case MB_YESNOCANCEL: + if (IDOK == nResult || IDYES == nResult) + { + nResult = IDYES; + } + else if (IDNO == nResult) + { + nResult = IDNO; + } + else if (IDCANCEL == nResult || IDABORT == nResult) + { + nResult = IDCANCEL; + } + else + { + nResult = IDNOACTION; + } + break; + + case MB_RETRYCANCEL: + if (IDRETRY == nResult || IDTRYAGAIN == nResult) + { + nResult = IDRETRY; + } + else if (IDCANCEL == nResult || IDABORT == nResult) + { + nResult = IDABORT; + } + else + { + nResult = IDNOACTION; + } + break; + + case MB_CANCELTRYCONTINUE: + if (IDCANCEL == nResult || IDABORT == nResult) + { + nResult = IDABORT; + } + else if (IDRETRY == nResult || IDTRYAGAIN == nResult) + { + nResult = IDRETRY; + } + else if (IDCONTINUE == nResult || IDIGNORE == nResult) + { + nResult = IDCONTINUE; + } + else + { + nResult = IDNOACTION; + } + break; + + case WIU_MB_OKIGNORECANCELRETRY: // custom Windows Installer utility return code. + if (IDOK == nResult || IDYES == nResult) + { + nResult = IDOK; + } + else if (IDCONTINUE == nResult || IDIGNORE == nResult) + { + nResult = IDIGNORE; + } + else if (IDCANCEL == nResult || IDABORT == nResult) + { + nResult = IDCANCEL; + } + else if (IDRETRY == nResult || IDTRYAGAIN == nResult || IDNO == nResult) + { + nResult = IDRETRY; + } + else + { + nResult = IDNOACTION; + } + break; + + case MB_RETRYTRYAGAIN: // custom return code. + if (IDRETRY != nResult && IDTRYAGAIN != nResult) + { + nResult = IDNOACTION; + } + break; + + default: + AssertSz(FALSE, "Unknown allowed results."); + break; + } + } + + return nResult; +} + +// This filters the BA's responses to events during apply. +// If an apply thread failed, then return its error so this thread will bail out. +// During rollback, the BA can't cancel. +static HRESULT FilterExecuteResult( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __in BOOL fRollback, + __in BOOL fCancel, + __in LPCWSTR sczEventName + ) +{ + HRESULT hr = hrStatus; + HRESULT hrApplyError = pUserExperience->hrApplyError; // make sure to use the same value for the whole method, since it can be changed in other threads. + + // If we failed return that error unless this is rollback which should roll on. + if (FAILED(hrApplyError) && !fRollback) + { + hr = hrApplyError; + } + else if (fRollback) + { + if (fCancel) + { + LogId(REPORT_STANDARD, MSG_APPLY_CANCEL_IGNORED_DURING_ROLLBACK, sczEventName); + } + // TODO: since cancel isn't allowed, should the BA's HRESULT be ignored as well? + // In the previous code, they could still alter rollback by returning IDERROR. + } + else + { + ExitOnFailure(hr, "BA %ls failed.", sczEventName); + + if (fCancel) + { + hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT); + } + } + +LExit: + return hr; +} + +static HRESULT SendBAMessage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_APPLICATION_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + + if (!pUserExperience->hUXModule) + { + ExitFunction(); + } + + hr = pUserExperience->pfnBAProc(message, pvArgs, pvResults, pUserExperience->pvBAProcContext); + if (hr == E_NOTIMPL) + { + hr = S_OK; + } + +LExit: + return hr; +} + +static HRESULT SendBAMessageFromInactiveEngine( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_APPLICATION_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults + ) +{ + HRESULT hr = S_OK; + + if (!pUserExperience->hUXModule) + { + ExitFunction(); + } + + UserExperienceDeactivateEngine(pUserExperience); + + hr = SendBAMessage(pUserExperience, message, pvArgs, pvResults); + + UserExperienceActivateEngine(pUserExperience); + +LExit: + return hr; +} diff --git a/src/burn/engine/userexperience.h b/src/burn/engine/userexperience.h new file mode 100644 index 00000000..f2453dca --- /dev/null +++ b/src/burn/engine/userexperience.h @@ -0,0 +1,545 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#define BAAPI HRESULT __stdcall + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + +const DWORD MB_RETRYTRYAGAIN = 0xF; + + +// structs + +typedef struct _BOOTSTRAPPER_ENGINE_CONTEXT BOOTSTRAPPER_ENGINE_CONTEXT; + +typedef struct _BURN_USER_EXPERIENCE +{ + BOOL fSplashScreen; + BURN_PAYLOADS payloads; + + HMODULE hUXModule; + PFN_BOOTSTRAPPER_APPLICATION_PROC pfnBAProc; + LPVOID pvBAProcContext; + BOOL fDisableUnloading; + LPWSTR sczTempDirectory; + + CRITICAL_SECTION csEngineActive; // Changing the engine active state in the user experience must be + // syncronized through this critical section. + // Note: The engine must never do a UX callback while in this critical section. + + BOOL fEngineActive; // Indicates that the engine is currently active with one of the execution + // steps (detect, plan, apply), and cannot accept requests from the UX. + // This flag should be cleared by the engine prior to UX callbacks that + // allows altering of the engine state. + + HRESULT hrApplyError; // Tracks is an error occurs during apply that requires the cache or + // execute threads to bail. + + HWND hwndApply; // The window handle provided at the beginning of Apply(). Only valid + // during apply. + + HWND hwndDetect; // The window handle provided at the beginning of Detect(). Only valid + // during Detect. + + DWORD dwExitCode; // Exit code returned by the user experience for the engine overall. +} BURN_USER_EXPERIENCE; + +// functions + +HRESULT UserExperienceParseFromXml( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in IXMLDOMNode* pixnBundle + ); +void UserExperienceUninitialize( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +HRESULT UserExperienceLoad( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_ENGINE_CONTEXT* pEngineContext, + __in BOOTSTRAPPER_COMMAND* pCommand + ); +HRESULT UserExperienceUnload( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +HRESULT UserExperienceEnsureWorkingFolder( + __in LPCWSTR wzBundleId, + __deref_out_z LPWSTR* psczUserExperienceWorkingFolder + ); +HRESULT UserExperienceRemove( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +int UserExperienceSendError( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_ERROR_TYPE errorType, + __in_z_opt LPCWSTR wzPackageId, + __in HRESULT hrCode, + __in_z_opt LPCWSTR wzError, + __in DWORD uiFlags, + __in int nRecommendation + ); +void UserExperienceActivateEngine( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +void UserExperienceDeactivateEngine( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +/******************************************************************** + UserExperienceEnsureEngineInactive - Verifies the engine is inactive. + The caller MUST enter the csActive critical section before calling. + +*********************************************************************/ +HRESULT UserExperienceEnsureEngineInactive( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +void UserExperienceExecuteReset( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +void UserExperienceExecutePhaseComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrResult + ); +BAAPI UserExperienceOnApplyBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in DWORD dwPhaseCount + ); +BAAPI UserExperienceOnApplyComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __in BOOTSTRAPPER_APPLY_RESTART restart, + __inout BOOTSTRAPPER_APPLYCOMPLETE_ACTION* pAction + ); +BAAPI UserExperienceOnBeginMsiTransactionBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId + ); +BAAPI UserExperienceOnBeginMsiTransactionComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnCacheAcquireBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in_z LPWSTR* pwzSource, + __in_z LPWSTR* pwzDownloadUrl, + __in_z_opt LPCWSTR wzPayloadContainerId, + __out BOOTSTRAPPER_CACHE_OPERATION* pCacheOperation + ); +BAAPI UserExperienceOnCacheAcquireComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in HRESULT hrStatus, + __inout BOOL* pfRetry + ); +BAAPI UserExperienceOnCacheAcquireProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in DWORD dwOverallPercentage + ); +BAAPI UserExperienceOnCacheAcquireResolving( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in_z LPWSTR* rgSearchPaths, + __in DWORD cSearchPaths, + __in BOOL fFoundLocal, + __in DWORD* pdwChosenSearchPath, + __in_z_opt LPWSTR* pwzDownloadUrl, + __in_z_opt LPCWSTR wzPayloadContainerId, + __inout BOOTSTRAPPER_CACHE_RESOLVE_OPERATION* pCacheOperation + ); +BAAPI UserExperienceOnCacheBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +BAAPI UserExperienceOnCacheComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnCacheContainerOrPayloadVerifyBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId + ); +BAAPI UserExperienceOnCacheContainerOrPayloadVerifyComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnCacheContainerOrPayloadVerifyProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in DWORD dwOverallPercentage + ); +BAAPI UserExperienceOnCachePackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in DWORD cCachePayloads, + __in DWORD64 dw64PackageCacheSize + ); +BAAPI UserExperienceOnCachePackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in HRESULT hrStatus, + __inout BOOTSTRAPPER_CACHEPACKAGECOMPLETE_ACTION* pAction + ); +BAAPI UserExperienceOnCachePayloadExtractBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzContainerId, + __in_z_opt LPCWSTR wzPayloadId + ); +BAAPI UserExperienceOnCachePayloadExtractComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnCachePayloadExtractProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in DWORD dwOverallPercentage + ); +BAAPI UserExperienceOnCacheVerifyBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId + ); +BAAPI UserExperienceOnCacheVerifyComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in HRESULT hrStatus, + __inout BOOTSTRAPPER_CACHEVERIFYCOMPLETE_ACTION* pAction + ); +BAAPI UserExperienceOnCacheVerifyProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzPackageOrContainerId, + __in_z_opt LPCWSTR wzPayloadId, + __in DWORD64 dw64Progress, + __in DWORD64 dw64Total, + __in DWORD dwOverallPercentage, + __in BOOTSTRAPPER_CACHE_VERIFY_STEP verifyStep + ); +BAAPI UserExperienceOnCommitMsiTransactionBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId + ); +BAAPI UserExperienceOnCommitMsiTransactionComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnDetectBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fCached, + __in BOOL fInstalled, + __in DWORD cPackages + ); +BAAPI UserExperienceOnDetectComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __in BOOL fEligibleForCleanup + ); +BAAPI UserExperienceOnDetectForwardCompatibleBundle( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzBundleId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in_z LPCWSTR wzBundleTag, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion, + __in BOOL fMissingFromCache + ); +BAAPI UserExperienceOnDetectMsiFeature( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzFeatureId, + __in BOOTSTRAPPER_FEATURE_STATE state + ); +BAAPI UserExperienceOnDetectPackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId + ); +BAAPI UserExperienceOnDetectPackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in HRESULT hrStatus, + __in BOOTSTRAPPER_PACKAGE_STATE state, + __in BOOL fCached + ); +BAAPI UserExperienceOnDetectRelatedBundle( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzBundleId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in_z LPCWSTR wzBundleTag, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion, + __in BOOTSTRAPPER_RELATED_OPERATION operation, + __in BOOL fMissingFromCache + ); +BAAPI UserExperienceOnDetectRelatedMsiPackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzUpgradeCode, + __in_z LPCWSTR wzProductCode, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion, + __in BOOTSTRAPPER_RELATED_OPERATION operation + ); +BAAPI UserExperienceOnDetectPatchTarget( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzProductCode, + __in BOOTSTRAPPER_PACKAGE_STATE patchState + ); +BAAPI UserExperienceOnDetectUpdate( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z_opt LPCWSTR wzUpdateLocation, + __in DWORD64 dw64Size, + __in VERUTIL_VERSION* pVersion, + __in_z_opt LPCWSTR wzTitle, + __in_z_opt LPCWSTR wzSummary, + __in_z_opt LPCWSTR wzContentType, + __in_z_opt LPCWSTR wzContent, + __inout BOOL* pfStopProcessingUpdates + ); +BAAPI UserExperienceOnDetectUpdateBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzUpdateLocation, + __inout BOOL* pfSkip + ); +BAAPI UserExperienceOnDetectUpdateComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __inout BOOL* pfIgnoreError + ); +BAAPI UserExperienceOnElevateBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +BAAPI UserExperienceOnElevateComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnError( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOTSTRAPPER_ERROR_TYPE errorType, + __in_z_opt LPCWSTR wzPackageId, + __in DWORD dwCode, + __in_z_opt LPCWSTR wzError, + __in DWORD dwUIHint, + __in DWORD cData, + __in_ecount_z_opt(cData) LPCWSTR* rgwzData, + __inout int* pnResult + ); +BAAPI UserExperienceOnExecuteBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in DWORD cExecutingPackages + ); +BAAPI UserExperienceOnExecuteComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnExecuteFilesInUse( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in DWORD cFiles, + __in_ecount_z_opt(cFiles) LPCWSTR* rgwzFiles, + __inout int* pnResult + ); +BAAPI UserExperienceOnExecuteMsiMessage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in INSTALLMESSAGE messageType, + __in DWORD dwUIHint, + __in_z LPCWSTR wzMessage, + __in DWORD cData, + __in_ecount_z_opt(cData) LPCWSTR* rgwzData, + __inout int* pnResult + ); +BAAPI UserExperienceOnExecutePackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in BOOL fExecute, + __in BOOTSTRAPPER_ACTION_STATE action, + __in INSTALLUILEVEL uiLevel, + __in BOOL fDisableExternalUiHandler + ); +BAAPI UserExperienceOnExecutePackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in HRESULT hrStatus, + __in BOOTSTRAPPER_APPLY_RESTART restart, + __inout BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION* pAction + ); +BAAPI UserExperienceOnExecutePatchTarget( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzTargetProductCode + ); +BAAPI UserExperienceOnExecuteProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in DWORD dwProgressPercentage, + __in DWORD dwOverallPercentage, + __out int* pnResult + ); +BAAPI UserExperienceOnLaunchApprovedExeBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +BAAPI UserExperienceOnLaunchApprovedExeComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus, + __in DWORD dwProcessId + ); +BAAPI UserExperienceOnPauseAUBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +BAAPI UserExperienceOnPauseAUComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnPlanBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in DWORD cPackages + ); +BAAPI UserExperienceOnPlanComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnPlanForwardCompatibleBundle( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzBundleId, + __in BOOTSTRAPPER_RELATION_TYPE relationType, + __in_z LPCWSTR wzBundleTag, + __in BOOL fPerMachine, + __in VERUTIL_VERSION* pVersion, + __inout BOOL* pfIgnoreBundle + ); +BAAPI UserExperienceOnPlanMsiFeature( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzFeatureId, + __inout BOOTSTRAPPER_FEATURE_STATE* pRequestedState + ); +BAAPI UserExperienceOnPlanMsiPackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in BOOL fExecute, + __in BOOTSTRAPPER_ACTION_STATE action, + __inout BURN_MSI_PROPERTY* pActionMsiProperty, + __inout INSTALLUILEVEL* pUiLevel, + __inout BOOL* pfDisableExternalUiHandler + ); +BAAPI UserExperienceOnPlannedPackage( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in BOOTSTRAPPER_ACTION_STATE execute, + __in BOOTSTRAPPER_ACTION_STATE rollback, + __in BOOL fPlannedCache, + __in BOOL fPlannedUncache + ); +BAAPI UserExperienceOnPlanPackageBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in BOOTSTRAPPER_PACKAGE_STATE state, + __in BOOL fCached, + __in BOOTSTRAPPER_PACKAGE_CONDITION_RESULT installCondition, + __inout BOOTSTRAPPER_REQUEST_STATE* pRequestedState, + __inout BOOTSTRAPPER_CACHE_TYPE* pRequestedCacheType + ); +BAAPI UserExperienceOnPlanPackageComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in HRESULT hrStatus, + __in BOOTSTRAPPER_REQUEST_STATE requested + ); +BAAPI UserExperienceOnPlanRelatedBundle( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzBundleId, + __inout BOOTSTRAPPER_REQUEST_STATE* pRequestedState + ); +BAAPI UserExperienceOnPlanPatchTarget( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in_z LPCWSTR wzPackageId, + __in_z LPCWSTR wzProductCode, + __inout BOOTSTRAPPER_REQUEST_STATE* pRequestedState + ); +BAAPI UserExperienceOnProgress( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fRollback, + __in DWORD dwProgressPercentage, + __in DWORD dwOverallPercentage + ); +BAAPI UserExperienceOnRegisterBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +BAAPI UserExperienceOnRegisterComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnRollbackMsiTransactionBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId + ); +BAAPI UserExperienceOnRollbackMsiTransactionComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in LPCWSTR wzTransactionId, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnShutdown( + __in BURN_USER_EXPERIENCE* pUserExperience, + __inout BOOTSTRAPPER_SHUTDOWN_ACTION* pAction + ); +BAAPI UserExperienceOnStartup( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +BAAPI UserExperienceOnSystemRestorePointBegin( + __in BURN_USER_EXPERIENCE* pUserExperience + ); +BAAPI UserExperienceOnSystemRestorePointComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ); +BAAPI UserExperienceOnSystemShutdown( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in DWORD dwEndSession, + __inout BOOL* pfCancel + ); +BAAPI UserExperienceOnUnregisterBegin( + __in BURN_USER_EXPERIENCE* pUserExperience, + __inout BOOL* pfKeepRegistration + ); +BAAPI UserExperienceOnUnregisterComplete( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in HRESULT hrStatus + ); +int UserExperienceCheckExecuteResult( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fRollback, + __in DWORD dwAllowedResults, + __in int nResult + ); +HRESULT UserExperienceInterpretExecuteResult( + __in BURN_USER_EXPERIENCE* pUserExperience, + __in BOOL fRollback, + __in DWORD dwAllowedResults, + __in int nResult + ); +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/variable.cpp b/src/burn/engine/variable.cpp new file mode 100644 index 00000000..6f818ff3 --- /dev/null +++ b/src/burn/engine/variable.cpp @@ -0,0 +1,2323 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +// structs + +typedef const struct _BUILT_IN_VARIABLE_DECLARATION +{ + LPCWSTR wzVariable; + PFN_INITIALIZEVARIABLE pfnInitialize; + DWORD_PTR dwpInitializeData; + BOOL fPersist; + BOOL fOverridable; +} BUILT_IN_VARIABLE_DECLARATION; + + +// constants + +const DWORD GROW_VARIABLE_ARRAY = 3; + +enum OS_INFO_VARIABLE +{ + OS_INFO_VARIABLE_NONE, + OS_INFO_VARIABLE_VersionNT, + OS_INFO_VARIABLE_VersionNT64, + OS_INFO_VARIABLE_ServicePackLevel, + OS_INFO_VARIABLE_NTProductType, + OS_INFO_VARIABLE_NTSuiteBackOffice, + OS_INFO_VARIABLE_NTSuiteDataCenter, + OS_INFO_VARIABLE_NTSuiteEnterprise, + OS_INFO_VARIABLE_NTSuitePersonal, + OS_INFO_VARIABLE_NTSuiteSmallBusiness, + OS_INFO_VARIABLE_NTSuiteSmallBusinessRestricted, + OS_INFO_VARIABLE_NTSuiteWebServer, + OS_INFO_VARIABLE_CompatibilityMode, + OS_INFO_VARIABLE_TerminalServer, + OS_INFO_VARIABLE_ProcessorArchitecture, + OS_INFO_VARIABLE_WindowsBuildNumber, +}; + +enum SET_VARIABLE +{ + SET_VARIABLE_NOT_BUILTIN, + SET_VARIABLE_OVERRIDE_BUILTIN, + SET_VARIABLE_OVERRIDE_PERSISTED_BUILTINS, + SET_VARIABLE_ANY, +}; + +// internal function declarations + +static HRESULT FormatString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt SIZE_T* pcchOut, + __in BOOL fObfuscateHiddenVariables, + __out BOOL* pfContainsHiddenVariable + ); +static HRESULT GetFormatted( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out_z LPWSTR* psczValue, + __out BOOL* pfContainsHiddenVariable + ); +static HRESULT AddBuiltInVariable( + __in BURN_VARIABLES* pVariables, + __in LPCWSTR wzVariable, + __in PFN_INITIALIZEVARIABLE pfnInitialize, + __in DWORD_PTR dwpInitializeData, + __in BOOL fPersist, + __in BOOL fOverridable + ); +static HRESULT GetVariable( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out BURN_VARIABLE** ppVariable + ); +static HRESULT FindVariableIndexByName( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out DWORD* piVariable + ); +static HRESULT InsertVariable( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in DWORD iPosition + ); +static HRESULT SetVariableValue( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in BURN_VARIANT* pVariant, + __in SET_VARIABLE setBuiltin, + __in BOOL fLog + ); +static HRESULT InitializeVariableVersionNT( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableOsInfo( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableSystemInfo( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableComputerName( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableVersionMsi( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableCsidlFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableWindowsVolumeFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableTempFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableSystemFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariablePrivileged( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeSystemLanguageID( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeUserUILanguageID( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeUserLanguageID( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableString( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableNumeric( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariable6432Folder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableDate( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableInstallerName( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableInstallerVersion( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableVersion( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT InitializeVariableLogonUser( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +static HRESULT Get64bitFolderFromRegistry( + __in int nFolder, + __deref_out_z LPWSTR* psczPath + ); + +#if !defined(_WIN64) +static HRESULT InitializeVariableRegistryFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); +#endif + + +// function definitions + +extern "C" HRESULT VariableInitialize( + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + + ::InitializeCriticalSection(&pVariables->csAccess); + + const BUILT_IN_VARIABLE_DECLARATION vrgBuiltInVariables[] = { + {L"AdminToolsFolder", InitializeVariableCsidlFolder, CSIDL_ADMINTOOLS}, + {L"AppDataFolder", InitializeVariableCsidlFolder, CSIDL_APPDATA}, + {L"CommonAppDataFolder", InitializeVariableCsidlFolder, CSIDL_COMMON_APPDATA}, +#if defined(_WIN64) + {L"CommonFiles64Folder", InitializeVariableCsidlFolder, CSIDL_PROGRAM_FILES_COMMON}, + {L"CommonFilesFolder", InitializeVariableCsidlFolder, CSIDL_PROGRAM_FILES_COMMONX86}, +#else + {L"CommonFiles64Folder", InitializeVariableRegistryFolder, CSIDL_PROGRAM_FILES_COMMON}, + {L"CommonFilesFolder", InitializeVariableCsidlFolder, CSIDL_PROGRAM_FILES_COMMON}, +#endif + {L"CommonFiles6432Folder", InitializeVariable6432Folder, CSIDL_PROGRAM_FILES_COMMON}, + {L"CompatibilityMode", InitializeVariableOsInfo, OS_INFO_VARIABLE_CompatibilityMode}, + {VARIABLE_DATE, InitializeVariableDate, 0}, + {L"ComputerName", InitializeVariableComputerName, 0}, + {L"DesktopFolder", InitializeVariableCsidlFolder, CSIDL_DESKTOP}, + {L"FavoritesFolder", InitializeVariableCsidlFolder, CSIDL_FAVORITES}, + {L"FontsFolder", InitializeVariableCsidlFolder, CSIDL_FONTS}, + {VARIABLE_INSTALLERNAME, InitializeVariableInstallerName, 0}, + {VARIABLE_INSTALLERVERSION, InitializeVariableInstallerVersion, 0}, + {L"LocalAppDataFolder", InitializeVariableCsidlFolder, CSIDL_LOCAL_APPDATA}, + {VARIABLE_LOGONUSER, InitializeVariableLogonUser, 0}, + {L"MyPicturesFolder", InitializeVariableCsidlFolder, CSIDL_MYPICTURES}, + {L"NTProductType", InitializeVariableOsInfo, OS_INFO_VARIABLE_NTProductType}, + {L"NTSuiteBackOffice", InitializeVariableOsInfo, OS_INFO_VARIABLE_NTSuiteBackOffice}, + {L"NTSuiteDataCenter", InitializeVariableOsInfo, OS_INFO_VARIABLE_NTSuiteDataCenter}, + {L"NTSuiteEnterprise", InitializeVariableOsInfo, OS_INFO_VARIABLE_NTSuiteEnterprise}, + {L"NTSuitePersonal", InitializeVariableOsInfo, OS_INFO_VARIABLE_NTSuitePersonal}, + {L"NTSuiteSmallBusiness", InitializeVariableOsInfo, OS_INFO_VARIABLE_NTSuiteSmallBusiness}, + {L"NTSuiteSmallBusinessRestricted", InitializeVariableOsInfo, OS_INFO_VARIABLE_NTSuiteSmallBusinessRestricted}, + {L"NTSuiteWebServer", InitializeVariableOsInfo, OS_INFO_VARIABLE_NTSuiteWebServer}, + {L"PersonalFolder", InitializeVariableCsidlFolder, CSIDL_PERSONAL}, + {L"Privileged", InitializeVariablePrivileged, 0}, + {L"ProcessorArchitecture", InitializeVariableSystemInfo, OS_INFO_VARIABLE_ProcessorArchitecture}, +#if defined(_WIN64) + {L"ProgramFiles64Folder", InitializeVariableCsidlFolder, CSIDL_PROGRAM_FILES}, + {L"ProgramFilesFolder", InitializeVariableCsidlFolder, CSIDL_PROGRAM_FILESX86}, +#else + {L"ProgramFiles64Folder", InitializeVariableRegistryFolder, CSIDL_PROGRAM_FILES}, + {L"ProgramFilesFolder", InitializeVariableCsidlFolder, CSIDL_PROGRAM_FILES}, +#endif + {L"ProgramFiles6432Folder", InitializeVariable6432Folder, CSIDL_PROGRAM_FILES}, + {L"ProgramMenuFolder", InitializeVariableCsidlFolder, CSIDL_PROGRAMS}, + {L"SendToFolder", InitializeVariableCsidlFolder, CSIDL_SENDTO}, + {L"ServicePackLevel", InitializeVariableVersionNT, OS_INFO_VARIABLE_ServicePackLevel}, + {L"StartMenuFolder", InitializeVariableCsidlFolder, CSIDL_STARTMENU}, + {L"StartupFolder", InitializeVariableCsidlFolder, CSIDL_STARTUP}, + {L"SystemFolder", InitializeVariableSystemFolder, FALSE}, + {L"System64Folder", InitializeVariableSystemFolder, TRUE}, + {L"SystemLanguageID", InitializeSystemLanguageID, 0}, + {L"TempFolder", InitializeVariableTempFolder, 0}, + {L"TemplateFolder", InitializeVariableCsidlFolder, CSIDL_TEMPLATES}, + {L"TerminalServer", InitializeVariableOsInfo, OS_INFO_VARIABLE_TerminalServer}, + {L"UserUILanguageID", InitializeUserUILanguageID, 0}, + {L"UserLanguageID", InitializeUserLanguageID, 0}, + {L"VersionMsi", InitializeVariableVersionMsi, 0}, + {L"VersionNT", InitializeVariableVersionNT, OS_INFO_VARIABLE_VersionNT}, + {L"VersionNT64", InitializeVariableVersionNT, OS_INFO_VARIABLE_VersionNT64}, + {L"WindowsBuildNumber", InitializeVariableVersionNT, OS_INFO_VARIABLE_WindowsBuildNumber}, + {L"WindowsFolder", InitializeVariableCsidlFolder, CSIDL_WINDOWS}, + {L"WindowsVolume", InitializeVariableWindowsVolumeFolder, 0}, + {BURN_BUNDLE_ACTION, InitializeVariableNumeric, 0, FALSE, TRUE}, + {BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, InitializeVariableString, NULL, FALSE, TRUE}, + {BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, InitializeVariableString, NULL, FALSE, TRUE}, + {BURN_BUNDLE_FORCED_RESTART_PACKAGE, InitializeVariableString, NULL, TRUE, TRUE}, + {BURN_BUNDLE_INSTALLED, InitializeVariableNumeric, 0, FALSE, TRUE}, + {BURN_BUNDLE_ELEVATED, InitializeVariableNumeric, 0, FALSE, TRUE}, + {BURN_BUNDLE_ACTIVE_PARENT, InitializeVariableString, NULL, FALSE, TRUE}, + {BURN_BUNDLE_PROVIDER_KEY, InitializeVariableString, (DWORD_PTR)L"", FALSE, TRUE}, + {BURN_BUNDLE_SOURCE_PROCESS_PATH, InitializeVariableString, NULL, FALSE, TRUE}, + {BURN_BUNDLE_SOURCE_PROCESS_FOLDER, InitializeVariableString, NULL, FALSE, TRUE}, + {BURN_BUNDLE_TAG, InitializeVariableString, (DWORD_PTR)L"", FALSE, TRUE}, + {BURN_BUNDLE_UILEVEL, InitializeVariableNumeric, 0, FALSE, TRUE}, + {BURN_BUNDLE_VERSION, InitializeVariableVersion, (DWORD_PTR)L"0", FALSE, TRUE}, + }; + + for (DWORD i = 0; i < countof(vrgBuiltInVariables); ++i) + { + BUILT_IN_VARIABLE_DECLARATION* pBuiltInVariable = &vrgBuiltInVariables[i]; + + hr = AddBuiltInVariable(pVariables, pBuiltInVariable->wzVariable, pBuiltInVariable->pfnInitialize, pBuiltInVariable->dwpInitializeData, pBuiltInVariable->fPersist, pBuiltInVariable->fOverridable); + ExitOnFailure(hr, "Failed to add built-in variable: %ls.", pBuiltInVariable->wzVariable); + } + +LExit: + return hr; +} + +extern "C" HRESULT VariablesParseFromXml( + __in BURN_VARIABLES* pVariables, + __in IXMLDOMNode* pixnBundle + ) +{ + HRESULT hr = S_OK; + IXMLDOMNodeList* pixnNodes = NULL; + IXMLDOMNode* pixnNode = NULL; + DWORD cNodes = 0; + LPWSTR sczId = NULL; + LPWSTR scz = NULL; + BURN_VARIANT value = { }; + BURN_VARIANT_TYPE valueType = BURN_VARIANT_TYPE_NONE; + BOOL fHidden = FALSE; + BOOL fPersisted = FALSE; + DWORD iVariable = 0; + + ::EnterCriticalSection(&pVariables->csAccess); + + // select variable nodes + hr = XmlSelectNodes(pixnBundle, L"Variable", &pixnNodes); + ExitOnFailure(hr, "Failed to select variable nodes."); + + // get variable node count + hr = pixnNodes->get_length((long*)&cNodes); + ExitOnFailure(hr, "Failed to get variable node count."); + + // parse variable elements + for (DWORD i = 0; i < cNodes; ++i) + { + hr = XmlNextElement(pixnNodes, &pixnNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Id + hr = XmlGetAttributeEx(pixnNode, L"Id", &sczId); + ExitOnFailure(hr, "Failed to get @Id."); + + // @Hidden + hr = XmlGetYesNoAttribute(pixnNode, L"Hidden", &fHidden); + ExitOnFailure(hr, "Failed to get @Hidden."); + + // @Persisted + hr = XmlGetYesNoAttribute(pixnNode, L"Persisted", &fPersisted); + ExitOnFailure(hr, "Failed to get @Persisted."); + + // @Value + hr = XmlGetAttributeEx(pixnNode, L"Value", &scz); + if (E_NOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to get @Value."); + + hr = BVariantSetString(&value, scz, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + + // @Type + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnFailure(hr, "Failed to get @Type."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"formatted", -1)) + { + if (!fHidden) + { + LogStringLine(REPORT_STANDARD, "Initializing formatted variable '%ls' to value '%ls'", sczId, value.sczValue); + } + valueType = BURN_VARIANT_TYPE_FORMATTED; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"numeric", -1)) + { + if (!fHidden) + { + LogStringLine(REPORT_STANDARD, "Initializing numeric variable '%ls' to value '%ls'", sczId, value.sczValue); + } + valueType = BURN_VARIANT_TYPE_NUMERIC; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"string", -1)) + { + if (!fHidden) + { + LogStringLine(REPORT_STANDARD, "Initializing string variable '%ls' to value '%ls'", sczId, value.sczValue); + } + valueType = BURN_VARIANT_TYPE_STRING; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"version", -1)) + { + if (!fHidden) + { + LogStringLine(REPORT_STANDARD, "Initializing version variable '%ls' to value '%ls'", sczId, value.sczValue); + } + valueType = BURN_VARIANT_TYPE_VERSION; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for @Type: %ls", scz); + } + } + else + { + valueType = BURN_VARIANT_TYPE_NONE; + } + + if (fHidden) + { + LogStringLine(REPORT_STANDARD, "Initializing hidden variable '%ls'", sczId); + } + + // change value variant to correct type + hr = BVariantChangeType(&value, valueType); + ExitOnFailure(hr, "Failed to change variant type."); + + if (BURN_VARIANT_TYPE_VERSION == valueType && value.pValue->fInvalid) + { + LogId(REPORT_WARNING, MSG_VARIABLE_INVALID_VERSION, sczId); + } + + // find existing variable + hr = FindVariableIndexByName(pVariables, sczId, &iVariable); + ExitOnFailure(hr, "Failed to find variable value '%ls'.", sczId); + + // insert element if not found + if (S_FALSE == hr) + { + hr = InsertVariable(pVariables, sczId, iVariable); + ExitOnFailure(hr, "Failed to insert variable '%ls'.", sczId); + } + else if (BURN_VARIABLE_INTERNAL_TYPE_NORMAL < pVariables->rgVariables[iVariable].internalType) + { + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Attempt to set built-in variable value: %ls", sczId); + } + pVariables->rgVariables[iVariable].fHidden = fHidden; + pVariables->rgVariables[iVariable].fPersisted = fPersisted; + + // update variable value + hr = BVariantSetValue(&pVariables->rgVariables[iVariable].Value, &value); + ExitOnFailure(hr, "Failed to set value of variable: %ls", sczId); + + // prepare next iteration + ReleaseNullObject(pixnNode); + BVariantUninitialize(&value); + ReleaseNullStrSecure(scz); + } + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + ReleaseObject(pixnNodes); + ReleaseObject(pixnNode); + ReleaseStr(scz); + ReleaseStr(sczId); + BVariantUninitialize(&value); + + return hr; +} + +extern "C" void VariablesUninitialize( + __in BURN_VARIABLES* pVariables + ) +{ + ::DeleteCriticalSection(&pVariables->csAccess); + + if (pVariables->rgVariables) + { + for (DWORD i = 0; i < pVariables->cVariables; ++i) + { + BURN_VARIABLE* pVariable = &pVariables->rgVariables[i]; + if (pVariable) + { + ReleaseStr(pVariable->sczName); + BVariantUninitialize(&pVariable->Value); + } + } + MemFree(pVariables->rgVariables); + } +} + +extern "C" void VariablesDump( + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + + for (DWORD i = 0; i < pVariables->cVariables; ++i) + { + BURN_VARIABLE* pVariable = &pVariables->rgVariables[i]; + if (pVariable && BURN_VARIANT_TYPE_NONE != pVariable->Value.Type) + { + hr = StrAllocFormatted(&sczValue, L"%ls = [%ls]", pVariable->sczName, pVariable->sczName); + if (SUCCEEDED(hr)) + { + if (pVariable->fHidden) + { + hr = VariableFormatStringObfuscated(pVariables, sczValue, &sczValue, NULL); + } + else + { + hr = VariableFormatString(pVariables, sczValue, &sczValue, NULL); + } + } + + if (FAILED(hr)) + { + // already logged; best-effort to dump the rest on our way out the door + continue; + } + + LogId(REPORT_VERBOSE, MSG_VARIABLE_DUMP, sczValue); + + ReleaseNullStrSecure(sczValue); + } + } + + StrSecureZeroFreeString(sczValue); +} + +extern "C" HRESULT VariableGetNumeric( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out LONGLONG* pllValue + ) +{ + HRESULT hr = S_OK; + BURN_VARIABLE* pVariable = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + hr = GetVariable(pVariables, wzVariable, &pVariable); + if (SUCCEEDED(hr) && BURN_VARIANT_TYPE_NONE == pVariable->Value.Type) + { + ExitFunction1(hr = E_NOTFOUND); + } + else if (E_NOTFOUND == hr) + { + ExitFunction(); + } + ExitOnFailure(hr, "Failed to get value of variable: %ls", wzVariable); + + hr = BVariantGetNumeric(&pVariable->Value, pllValue); + ExitOnFailure(hr, "Failed to get value as numeric for variable: %ls", wzVariable); + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + return hr; +} + +extern "C" HRESULT VariableGetString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out_z LPWSTR* psczValue + ) +{ + HRESULT hr = S_OK; + BURN_VARIABLE* pVariable = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + hr = GetVariable(pVariables, wzVariable, &pVariable); + if (SUCCEEDED(hr) && BURN_VARIANT_TYPE_NONE == pVariable->Value.Type) + { + ExitFunction1(hr = E_NOTFOUND); + } + else if (E_NOTFOUND == hr) + { + ExitFunction(); + } + ExitOnFailure(hr, "Failed to get value of variable: %ls", wzVariable); + + hr = BVariantGetString(&pVariable->Value, psczValue); + ExitOnFailure(hr, "Failed to get value as string for variable: %ls", wzVariable); + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + return hr; +} + +extern "C" HRESULT VariableGetVersion( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in VERUTIL_VERSION** ppValue + ) +{ + HRESULT hr = S_OK; + BURN_VARIABLE* pVariable = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + hr = GetVariable(pVariables, wzVariable, &pVariable); + if (SUCCEEDED(hr) && BURN_VARIANT_TYPE_NONE == pVariable->Value.Type) + { + ExitFunction1(hr = E_NOTFOUND); + } + else if (E_NOTFOUND == hr) + { + ExitFunction(); + } + ExitOnFailure(hr, "Failed to get value of variable: %ls", wzVariable); + + hr = BVariantGetVersionHidden(&pVariable->Value, pVariable->fHidden, ppValue); + ExitOnFailure(hr, "Failed to get value as version for variable: %ls", wzVariable); + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + return hr; +} + +extern "C" HRESULT VariableGetVariant( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + BURN_VARIABLE* pVariable = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + hr = GetVariable(pVariables, wzVariable, &pVariable); + if (E_NOTFOUND == hr) + { + ExitFunction(); + } + ExitOnFailure(hr, "Failed to get value of variable: %ls", wzVariable); + + hr = BVariantCopy(&pVariable->Value, pValue); + ExitOnFailure(hr, "Failed to copy value of variable: %ls", wzVariable); + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + return hr; +} + +extern "C" HRESULT VariableGetFormatted( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out_z LPWSTR* psczValue, + __out BOOL* pfContainsHiddenVariable + ) +{ + HRESULT hr = S_OK; + + if (pfContainsHiddenVariable) + { + *pfContainsHiddenVariable = FALSE; + } + + hr = GetFormatted(pVariables, wzVariable, psczValue, pfContainsHiddenVariable); + + return hr; +} + +extern "C" HRESULT VariableSetNumeric( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in LONGLONG llValue, + __in BOOL fOverwriteBuiltIn + ) +{ + BURN_VARIANT variant = { }; + + variant.llValue = llValue; + variant.Type = BURN_VARIANT_TYPE_NUMERIC; + + return SetVariableValue(pVariables, wzVariable, &variant, fOverwriteBuiltIn ? SET_VARIABLE_OVERRIDE_BUILTIN : SET_VARIABLE_NOT_BUILTIN, TRUE); +} + +extern "C" HRESULT VariableSetString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in_z_opt LPCWSTR wzValue, + __in BOOL fOverwriteBuiltIn, + __in BOOL fFormatted + ) +{ + BURN_VARIANT variant = { }; + + variant.sczValue = (LPWSTR)wzValue; + variant.Type = fFormatted ? BURN_VARIANT_TYPE_FORMATTED : BURN_VARIANT_TYPE_STRING; + + return SetVariableValue(pVariables, wzVariable, &variant, fOverwriteBuiltIn ? SET_VARIABLE_OVERRIDE_BUILTIN : SET_VARIABLE_NOT_BUILTIN, TRUE); +} + +extern "C" HRESULT VariableSetVersion( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in VERUTIL_VERSION* pValue, + __in BOOL fOverwriteBuiltIn + ) +{ + BURN_VARIANT variant = { }; + + variant.pValue = pValue; + variant.Type = BURN_VARIANT_TYPE_VERSION; + + return SetVariableValue(pVariables, wzVariable, &variant, fOverwriteBuiltIn ? SET_VARIABLE_OVERRIDE_BUILTIN : SET_VARIABLE_NOT_BUILTIN, TRUE); +} + +extern "C" HRESULT VariableSetVariant( + __in BURN_VARIABLES * pVariables, + __in_z LPCWSTR wzVariable, + __in BURN_VARIANT * pVariant + ) +{ + return SetVariableValue(pVariables, wzVariable, pVariant, SET_VARIABLE_NOT_BUILTIN, TRUE); +} + +extern "C" HRESULT VariableFormatString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt SIZE_T* pcchOut + ) +{ + return FormatString(pVariables, wzIn, psczOut, pcchOut, FALSE, NULL); +} + +extern "C" HRESULT VariableFormatStringObfuscated( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt SIZE_T* pcchOut + ) +{ + return FormatString(pVariables, wzIn, psczOut, pcchOut, TRUE, NULL); +} + +extern "C" HRESULT VariableEscapeString( + __in_z LPCWSTR wzIn, + __out_z LPWSTR* psczOut + ) +{ + HRESULT hr = S_OK; + LPCWSTR wzRead = NULL; + LPWSTR pwzEscaped = NULL; + LPWSTR pwz = NULL; + SIZE_T i = 0; + + // allocate buffer for escaped string + hr = StrAlloc(&pwzEscaped, lstrlenW(wzIn) + 1); + ExitOnFailure(hr, "Failed to allocate buffer for escaped string."); + + // read through string and move characters, inserting escapes as needed + wzRead = wzIn; + for (;;) + { + // find next character needing escaping + i = wcscspn(wzRead, L"[]{}"); + + // copy skipped characters + if (0 < i) + { + hr = StrAllocConcat(&pwzEscaped, wzRead, i); + ExitOnFailure(hr, "Failed to append characters."); + } + + if (L'\0' == wzRead[i]) + { + break; // end reached + } + + // escape character + hr = StrAllocFormatted(&pwz, L"[\\%c]", wzRead[i]); + ExitOnFailure(hr, "Failed to format escape sequence."); + + hr = StrAllocConcat(&pwzEscaped, pwz, 0); + ExitOnFailure(hr, "Failed to append escape sequence."); + + // update read pointer + wzRead += i + 1; + } + + // return value + hr = StrAllocString(psczOut, pwzEscaped, 0); + ExitOnFailure(hr, "Failed to copy string."); + +LExit: + ReleaseStr(pwzEscaped); + ReleaseStr(pwz); + return hr; +} + +extern "C" HRESULT VariableSerialize( + __in BURN_VARIABLES* pVariables, + __in BOOL fPersisting, + __inout BYTE** ppbBuffer, + __inout SIZE_T* piBuffer + ) +{ + HRESULT hr = S_OK; + BOOL fIncluded = FALSE; + LONGLONG ll = 0; + LPWSTR scz = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + // Write variable count. + hr = BuffWriteNumber(ppbBuffer, piBuffer, pVariables->cVariables); + ExitOnFailure(hr, "Failed to write variable count."); + + // Write variables. + for (DWORD i = 0; i < pVariables->cVariables; ++i) + { + BURN_VARIABLE* pVariable = &pVariables->rgVariables[i]; + + // If we aren't persisting, include only variables that aren't rejected by the elevated process. + // If we are persisting, include only variables that should be persisted. + fIncluded = (!fPersisting && BURN_VARIABLE_INTERNAL_TYPE_BUILTIN != pVariable->internalType) || + (fPersisting && pVariable->fPersisted); + + // Write included flag. + hr = BuffWriteNumber(ppbBuffer, piBuffer, (DWORD)fIncluded); + ExitOnFailure(hr, "Failed to write included flag."); + + if (!fIncluded) + { + continue; + } + + // Write variable name. + hr = BuffWriteString(ppbBuffer, piBuffer, pVariable->sczName); + ExitOnFailure(hr, "Failed to write variable name."); + + // Write variable value type. + hr = BuffWriteNumber(ppbBuffer, piBuffer, (DWORD)pVariable->Value.Type); + ExitOnFailure(hr, "Failed to write variable value type."); + + // Write variable value. + switch (pVariable->Value.Type) + { + case BURN_VARIANT_TYPE_NONE: + break; + case BURN_VARIANT_TYPE_NUMERIC: + hr = BVariantGetNumeric(&pVariable->Value, &ll); + ExitOnFailure(hr, "Failed to get numeric."); + + hr = BuffWriteNumber64(ppbBuffer, piBuffer, static_cast(ll)); + ExitOnFailure(hr, "Failed to write variable value as number."); + + SecureZeroMemory(&ll, sizeof(ll)); + break; + case BURN_VARIANT_TYPE_VERSION: __fallthrough; + case BURN_VARIANT_TYPE_FORMATTED: __fallthrough; + case BURN_VARIANT_TYPE_STRING: + hr = BVariantGetString(&pVariable->Value, &scz); + ExitOnFailure(hr, "Failed to get string."); + + hr = BuffWriteString(ppbBuffer, piBuffer, scz); + ExitOnFailure(hr, "Failed to write variable value as string."); + + ReleaseNullStrSecure(scz); + break; + default: + hr = E_INVALIDARG; + ExitOnFailure(hr, "Unsupported variable type."); + } + } + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + SecureZeroMemory(&ll, sizeof(ll)); + StrSecureZeroFreeString(scz); + + return hr; +} + +extern "C" HRESULT VariableDeserialize( + __in BURN_VARIABLES* pVariables, + __in BOOL fWasPersisted, + __in_bcount(cbBuffer) BYTE* pbBuffer, + __in SIZE_T cbBuffer, + __inout SIZE_T* piBuffer + ) +{ + HRESULT hr = S_OK; + DWORD cVariables = 0; + LPWSTR sczName = NULL; + BOOL fIncluded = FALSE; + BURN_VARIANT value = { }; + LPWSTR scz = NULL; + DWORD64 qw = 0; + VERUTIL_VERSION* pVersion = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + // Read variable count. + hr = BuffReadNumber(pbBuffer, cbBuffer, piBuffer, &cVariables); + ExitOnFailure(hr, "Failed to read variable count."); + + // Read variables. + for (DWORD i = 0; i < cVariables; ++i) + { + // Read variable included flag. + hr = BuffReadNumber(pbBuffer, cbBuffer, piBuffer, (DWORD*)&fIncluded); + ExitOnFailure(hr, "Failed to read variable included flag."); + + if (!fIncluded) + { + continue; // if variable is not included, skip. + } + + // Read variable name. + hr = BuffReadString(pbBuffer, cbBuffer, piBuffer, &sczName); + ExitOnFailure(hr, "Failed to read variable name."); + + // Read variable value type. + hr = BuffReadNumber(pbBuffer, cbBuffer, piBuffer, (DWORD*)&value.Type); + ExitOnFailure(hr, "Failed to read variable value type."); + + // Read variable value. + switch (value.Type) + { + case BURN_VARIANT_TYPE_NONE: + break; + case BURN_VARIANT_TYPE_NUMERIC: + hr = BuffReadNumber64(pbBuffer, cbBuffer, piBuffer, &qw); + ExitOnFailure(hr, "Failed to read variable value as number."); + + hr = BVariantSetNumeric(&value, static_cast(qw)); + ExitOnFailure(hr, "Failed to set variable value."); + + SecureZeroMemory(&qw, sizeof(qw)); + break; + case BURN_VARIANT_TYPE_VERSION: + hr = BuffReadString(pbBuffer, cbBuffer, piBuffer, &scz); + ExitOnFailure(hr, "Failed to read variable value as string."); + + hr = VerParseVersion(scz, 0, FALSE, &pVersion); + ExitOnFailure(hr, "Failed to parse variable value as version."); + + hr = BVariantSetVersion(&value, pVersion); + ExitOnFailure(hr, "Failed to set variable value."); + + SecureZeroMemory(&qw, sizeof(qw)); + break; + case BURN_VARIANT_TYPE_FORMATTED: __fallthrough; + case BURN_VARIANT_TYPE_STRING: + hr = BuffReadString(pbBuffer, cbBuffer, piBuffer, &scz); + ExitOnFailure(hr, "Failed to read variable value as string."); + + hr = BVariantSetString(&value, scz, NULL, BURN_VARIANT_TYPE_FORMATTED == value.Type); + ExitOnFailure(hr, "Failed to set variable value."); + + ReleaseNullStrSecure(scz); + break; + default: + hr = E_INVALIDARG; + ExitOnFailure(hr, "Unsupported variable type."); + } + + // Set variable. + hr = SetVariableValue(pVariables, sczName, &value, fWasPersisted ? SET_VARIABLE_OVERRIDE_PERSISTED_BUILTINS : SET_VARIABLE_ANY, FALSE); + ExitOnFailure(hr, "Failed to set variable."); + + // Clean up. + BVariantUninitialize(&value); + } + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + ReleaseVerutilVersion(pVersion); + ReleaseStr(sczName); + BVariantUninitialize(&value); + SecureZeroMemory(&qw, sizeof(qw)); + StrSecureZeroFreeString(scz); + + return hr; +} + +extern "C" HRESULT VariableStrAlloc( + __in BOOL fZeroOnRealloc, + __deref_out_ecount_part(cch, 0) LPWSTR* ppwz, + __in DWORD_PTR cch + ) +{ + HRESULT hr = S_OK; + + if (fZeroOnRealloc) + { + hr = StrAllocSecure(ppwz, cch); + } + else + { + hr = StrAlloc(ppwz, cch); + } + + return hr; +} + +extern "C" HRESULT VariableStrAllocString( + __in BOOL fZeroOnRealloc, + __deref_out_ecount_z(cchSource + 1) LPWSTR* ppwz, + __in_z LPCWSTR wzSource, + __in DWORD_PTR cchSource + ) +{ + HRESULT hr = S_OK; + + if (fZeroOnRealloc) + { + hr = StrAllocStringSecure(ppwz, wzSource, cchSource); + } + else + { + hr = StrAllocString(ppwz, wzSource, cchSource); + } + + return hr; +} + +extern "C" HRESULT VariableStrAllocConcat( + __in BOOL fZeroOnRealloc, + __deref_out_z LPWSTR* ppwz, + __in_z LPCWSTR wzSource, + __in DWORD_PTR cchSource + ) +{ + HRESULT hr = S_OK; + + if (fZeroOnRealloc) + { + hr = StrAllocConcatSecure(ppwz, wzSource, cchSource); + } + else + { + hr = StrAllocConcat(ppwz, wzSource, cchSource); + } + + return hr; +} + +extern "C" HRESULT __cdecl VariableStrAllocFormatted( + __in BOOL fZeroOnRealloc, + __deref_out_z LPWSTR* ppwz, + __in __format_string LPCWSTR wzFormat, + ... + ) +{ + HRESULT hr = S_OK; + va_list args; + + va_start(args, wzFormat); + if (fZeroOnRealloc) + { + hr = StrAllocFormattedArgsSecure(ppwz, wzFormat, args); + } + else + { + hr = StrAllocFormattedArgs(ppwz, wzFormat, args); + } + va_end(args); + + return hr; +} + +extern "C" HRESULT VariableIsHidden( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out BOOL* pfHidden + ) +{ + HRESULT hr = S_OK; + BURN_VARIABLE* pVariable = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + hr = GetVariable(pVariables, wzVariable, &pVariable); + if (E_NOTFOUND == hr) + { + // A missing variable does not need its data hidden. + *pfHidden = FALSE; + + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to get visibility of variable: %ls", wzVariable); + + *pfHidden = pVariable->fHidden; + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + return hr; +} + + +// internal function definitions + +static HRESULT FormatString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt SIZE_T* pcchOut, + __in BOOL fObfuscateHiddenVariables, + __out BOOL* pfContainsHiddenVariable + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + LPWSTR sczUnformatted = NULL; + LPWSTR sczFormat = NULL; + LPCWSTR wzRead = NULL; + LPCWSTR wzOpen = NULL; + LPCWSTR wzClose = NULL; + LPWSTR scz = NULL; + LPWSTR* rgVariables = NULL; + DWORD cVariables = 0; + DWORD cch = 0; + size_t cchIn = 0; + BOOL fHidden = FALSE; + MSIHANDLE hRecord = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + // allocate buffer for format string + hr = ::StringCchLengthW(wzIn, STRSAFE_MAX_LENGTH, &cchIn); + ExitOnFailure(hr, "Failed to length of format string."); + + hr = StrAlloc(&sczFormat, cchIn + 1); + ExitOnFailure(hr, "Failed to allocate buffer for format string."); + + // read out variables from the unformatted string and build a format string + wzRead = wzIn; + for (;;) + { + // scan for opening '[' + wzOpen = wcschr(wzRead, L'['); + if (!wzOpen) + { + // end reached, append the remainder of the string and end loop + hr = VariableStrAllocConcat(!fObfuscateHiddenVariables, &sczFormat, wzRead, 0); + ExitOnFailure(hr, "Failed to append string."); + break; + } + + // scan for closing ']' + wzClose = wcschr(wzOpen + 1, L']'); + if (!wzClose) + { + // end reached, treat unterminated expander as literal + hr = VariableStrAllocConcat(!fObfuscateHiddenVariables, &sczFormat, wzRead, 0); + ExitOnFailure(hr, "Failed to append string."); + break; + } + cch = (DWORD)(wzClose - wzOpen - 1); + + if (0 == cch) + { + // blank, copy all text including the terminator + hr = VariableStrAllocConcat(!fObfuscateHiddenVariables, &sczFormat, wzRead, (DWORD_PTR)(wzClose - wzRead) + 1); + ExitOnFailure(hr, "Failed to append string."); + } + else + { + // append text preceding expander + if (wzOpen > wzRead) + { + hr = VariableStrAllocConcat(!fObfuscateHiddenVariables, &sczFormat, wzRead, (DWORD_PTR)(wzOpen - wzRead)); + ExitOnFailure(hr, "Failed to append string."); + } + + // get variable name + hr = VariableStrAllocString(!fObfuscateHiddenVariables, &scz, wzOpen + 1, cch); + ExitOnFailure(hr, "Failed to get variable name."); + + // allocate space in variable array + if (rgVariables) + { + LPVOID pv = MemReAlloc(rgVariables, sizeof(LPWSTR) * (cVariables + 1), TRUE); + ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate variable array."); + rgVariables = (LPWSTR*)pv; + } + else + { + rgVariables = (LPWSTR*)MemAlloc(sizeof(LPWSTR) * (cVariables + 1), TRUE); + ExitOnNull(rgVariables, hr, E_OUTOFMEMORY, "Failed to allocate variable array."); + } + + // set variable value + if (2 <= cch && L'\\' == wzOpen[1]) + { + // escape sequence, copy character + hr = VariableStrAllocString(!fObfuscateHiddenVariables, &rgVariables[cVariables], &wzOpen[2], 1); + } + else + { + hr = VariableIsHidden(pVariables, scz, &fHidden); + ExitOnFailure(hr, "Failed to determine variable visibility: '%ls'.", scz); + + if (pfContainsHiddenVariable) + { + *pfContainsHiddenVariable |= fHidden; + } + + if (fObfuscateHiddenVariables && fHidden) + { + hr = StrAllocString(&rgVariables[cVariables], L"*****", 0); + } + else + { + // get formatted variable value + hr = GetFormatted(pVariables, scz, &rgVariables[cVariables], pfContainsHiddenVariable); + if (E_NOTFOUND == hr) // variable not found + { + hr = StrAllocStringSecure(&rgVariables[cVariables], L"", 0); + } + } + } + ExitOnFailure(hr, "Failed to set variable value."); + ++cVariables; + + // append placeholder to format string + hr = VariableStrAllocFormatted(!fObfuscateHiddenVariables, &scz, L"[%d]", cVariables); + ExitOnFailure(hr, "Failed to format placeholder string."); + + hr = VariableStrAllocConcat(!fObfuscateHiddenVariables, &sczFormat, scz, 0); + ExitOnFailure(hr, "Failed to append placeholder."); + } + + // update read pointer + wzRead = wzClose + 1; + } + + // create record + hRecord = ::MsiCreateRecord(cVariables); + ExitOnNull(hRecord, hr, E_OUTOFMEMORY, "Failed to allocate record."); + + // set format string + er = ::MsiRecordSetStringW(hRecord, 0, sczFormat); + ExitOnWin32Error(er, hr, "Failed to set record format string."); + + // copy record fields + for (DWORD i = 0; i < cVariables; ++i) + { + if (*rgVariables[i]) // not setting if blank + { + er = ::MsiRecordSetStringW(hRecord, i + 1, rgVariables[i]); + ExitOnWin32Error(er, hr, "Failed to set record string."); + } + } + + // get formatted character count + cch = 0; +#pragma prefast(push) +#pragma prefast(disable:6298) + er = ::MsiFormatRecordW(NULL, hRecord, L"", &cch); +#pragma prefast(pop) + if (ERROR_MORE_DATA != er) + { + ExitOnWin32Error(er, hr, "Failed to get formatted length."); + } + + // return formatted string + if (psczOut) + { + hr = VariableStrAlloc(!fObfuscateHiddenVariables, &scz, ++cch); + ExitOnFailure(hr, "Failed to allocate string."); + + er = ::MsiFormatRecordW(NULL, hRecord, scz, &cch); + ExitOnWin32Error(er, hr, "Failed to format record."); + + hr = VariableStrAllocString(!fObfuscateHiddenVariables, psczOut, scz, 0); + ExitOnFailure(hr, "Failed to copy string."); + } + + // return character count + if (pcchOut) + { + *pcchOut = cch; + } + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + if (rgVariables) + { + for (DWORD i = 0; i < cVariables; ++i) + { + if (fObfuscateHiddenVariables) + { + ReleaseStr(rgVariables[i]); + } + else + { + StrSecureZeroFreeString(rgVariables[i]); + } + } + MemFree(rgVariables); + } + + if (hRecord) + { + ::MsiCloseHandle(hRecord); + } + + if (fObfuscateHiddenVariables) + { + ReleaseStr(sczUnformatted); + ReleaseStr(sczFormat); + ReleaseStr(scz); + } + else + { + StrSecureZeroFreeString(sczUnformatted); + StrSecureZeroFreeString(sczFormat); + StrSecureZeroFreeString(scz); + } + + return hr; +} + +static HRESULT GetFormatted( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out_z LPWSTR* psczValue, + __out BOOL* pfContainsHiddenVariable + ) +{ + HRESULT hr = S_OK; + BURN_VARIABLE* pVariable = NULL; + LPWSTR scz = NULL; + + ::EnterCriticalSection(&pVariables->csAccess); + + hr = GetVariable(pVariables, wzVariable, &pVariable); + if (SUCCEEDED(hr) && BURN_VARIANT_TYPE_NONE == pVariable->Value.Type) + { + ExitFunction1(hr = E_NOTFOUND); + } + else if (E_NOTFOUND == hr) + { + ExitFunction(); + } + ExitOnFailure(hr, "Failed to get variable: %ls", wzVariable); + + if (pfContainsHiddenVariable) + { + *pfContainsHiddenVariable |= pVariable->fHidden; + } + + if (BURN_VARIANT_TYPE_FORMATTED == pVariable->Value.Type) + { + hr = BVariantGetString(&pVariable->Value, &scz); + ExitOnFailure(hr, "Failed to get unformatted string."); + + hr = FormatString(pVariables, scz, psczValue, NULL, FALSE, pfContainsHiddenVariable); + ExitOnFailure(hr, "Failed to format value '%ls' of variable: %ls", pVariable->fHidden ? L"*****" : pVariable->Value.sczValue, wzVariable); + } + else + { + hr = BVariantGetString(&pVariable->Value, psczValue); + ExitOnFailure(hr, "Failed to get value as string for variable: %ls", wzVariable); + } + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + StrSecureZeroFreeString(scz); + + return hr; +} + +static HRESULT AddBuiltInVariable( + __in BURN_VARIABLES* pVariables, + __in LPCWSTR wzVariable, + __in PFN_INITIALIZEVARIABLE pfnInitialize, + __in DWORD_PTR dwpInitializeData, + __in BOOL fPersist, + __in BOOL fOverridable + ) +{ + HRESULT hr = S_OK; + DWORD iVariable = 0; + BURN_VARIABLE* pVariable = NULL; + + hr = FindVariableIndexByName(pVariables, wzVariable, &iVariable); + ExitOnFailure(hr, "Failed to find variable value."); + + // insert element if not found + if (S_FALSE == hr) + { + hr = InsertVariable(pVariables, wzVariable, iVariable); + ExitOnFailure(hr, "Failed to insert variable."); + } + + // set variable values + pVariable = &pVariables->rgVariables[iVariable]; + pVariable->fPersisted = fPersist; + pVariable->internalType = fOverridable ? BURN_VARIABLE_INTERNAL_TYPE_OVERRIDABLE_BUILTIN : BURN_VARIABLE_INTERNAL_TYPE_BUILTIN; + pVariable->pfnInitialize = pfnInitialize; + pVariable->dwpInitializeData = dwpInitializeData; + +LExit: + return hr; +} + +static HRESULT GetVariable( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out BURN_VARIABLE** ppVariable + ) +{ + HRESULT hr = S_OK; + DWORD iVariable = 0; + BURN_VARIABLE* pVariable = NULL; + + hr = FindVariableIndexByName(pVariables, wzVariable, &iVariable); + ExitOnFailure(hr, "Failed to find variable value '%ls'.", wzVariable); + + if (S_FALSE == hr) + { + ExitFunction1(hr = E_NOTFOUND); + } + + pVariable = &pVariables->rgVariables[iVariable]; + + // initialize built-in variable + if (BURN_VARIANT_TYPE_NONE == pVariable->Value.Type && BURN_VARIABLE_INTERNAL_TYPE_NORMAL < pVariable->internalType) + { + hr = pVariable->pfnInitialize(pVariable->dwpInitializeData, &pVariable->Value); + ExitOnFailure(hr, "Failed to initialize built-in variable value '%ls'.", wzVariable); + } + + *ppVariable = pVariable; + +LExit: + return hr; +} + +static HRESULT FindVariableIndexByName( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out DWORD* piVariable + ) +{ + HRESULT hr = S_OK; + DWORD iRangeFirst = 0; + DWORD cRangeLength = pVariables->cVariables; + + while (cRangeLength) + { + // get variable in middle of range + DWORD iPosition = cRangeLength / 2; + BURN_VARIABLE* pVariable = &pVariables->rgVariables[iRangeFirst + iPosition]; + + switch (::CompareStringW(LOCALE_INVARIANT, SORT_STRINGSORT, wzVariable, -1, pVariable->sczName, -1)) + { + case CSTR_LESS_THAN: + // restrict range to elements before the current + cRangeLength = iPosition; + break; + case CSTR_EQUAL: + // variable found + *piVariable = iRangeFirst + iPosition; + ExitFunction1(hr = S_OK); + case CSTR_GREATER_THAN: + // restrict range to elements after the current + iRangeFirst += iPosition + 1; + cRangeLength -= iPosition + 1; + break; + default: + ExitWithLastError(hr, "Failed to compare strings."); + } + } + + *piVariable = iRangeFirst; + hr = S_FALSE; // variable not found + +LExit: + return hr; +} + +static HRESULT InsertVariable( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in DWORD iPosition + ) +{ + HRESULT hr = S_OK; + size_t cbAllocSize = 0; + + // ensure there is room in the variable array + if (pVariables->cVariables == pVariables->dwMaxVariables) + { + hr = ::DWordAdd(pVariables->dwMaxVariables, GROW_VARIABLE_ARRAY, &(pVariables->dwMaxVariables)); + ExitOnRootFailure(hr, "Overflow while growing variable array size"); + + if (pVariables->rgVariables) + { + hr = ::SizeTMult(sizeof(BURN_VARIABLE), pVariables->dwMaxVariables, &cbAllocSize); + ExitOnRootFailure(hr, "Overflow while calculating size of variable array buffer"); + + LPVOID pv = MemReAlloc(pVariables->rgVariables, cbAllocSize, FALSE); + ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate room for more variables."); + + // Prefast claims it's possible to hit this. Putting the check in just in case. + if (pVariables->dwMaxVariables < pVariables->cVariables) + { + hr = INTSAFE_E_ARITHMETIC_OVERFLOW; + ExitOnRootFailure(hr, "Overflow while dealing with variable array buffer allocation"); + } + + pVariables->rgVariables = (BURN_VARIABLE*)pv; + memset(&pVariables->rgVariables[pVariables->cVariables], 0, sizeof(BURN_VARIABLE) * (pVariables->dwMaxVariables - pVariables->cVariables)); + } + else + { + pVariables->rgVariables = (BURN_VARIABLE*)MemAlloc(sizeof(BURN_VARIABLE) * pVariables->dwMaxVariables, TRUE); + ExitOnNull(pVariables->rgVariables, hr, E_OUTOFMEMORY, "Failed to allocate room for variables."); + } + } + + // move variables + if (0 < pVariables->cVariables - iPosition) + { + memmove(&pVariables->rgVariables[iPosition + 1], &pVariables->rgVariables[iPosition], sizeof(BURN_VARIABLE) * (pVariables->cVariables - iPosition)); + memset(&pVariables->rgVariables[iPosition], 0, sizeof(BURN_VARIABLE)); + } + + ++pVariables->cVariables; + + // allocate name + hr = StrAllocString(&pVariables->rgVariables[iPosition].sczName, wzVariable, 0); + ExitOnFailure(hr, "Failed to copy variable name."); + +LExit: + return hr; +} + +static HRESULT SetVariableValue( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in BURN_VARIANT* pVariant, + __in SET_VARIABLE setBuiltin, + __in BOOL fLog + ) +{ + HRESULT hr = S_OK; + DWORD iVariable = 0; + + ::EnterCriticalSection(&pVariables->csAccess); + + hr = FindVariableIndexByName(pVariables, wzVariable, &iVariable); + ExitOnFailure(hr, "Failed to find variable value '%ls'.", wzVariable); + + // Insert element if not found. + if (S_FALSE == hr) + { + hr = InsertVariable(pVariables, wzVariable, iVariable); + ExitOnFailure(hr, "Failed to insert variable '%ls'.", wzVariable); + } + else if (BURN_VARIABLE_INTERNAL_TYPE_NORMAL < pVariables->rgVariables[iVariable].internalType) // built-in variables must be overridden. + { + if (SET_VARIABLE_OVERRIDE_BUILTIN == setBuiltin || + (SET_VARIABLE_OVERRIDE_PERSISTED_BUILTINS == setBuiltin && pVariables->rgVariables[iVariable].fPersisted) || + SET_VARIABLE_ANY == setBuiltin && BURN_VARIABLE_INTERNAL_TYPE_BUILTIN != pVariables->rgVariables[iVariable].internalType) + { + hr = S_OK; + } + else + { + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Attempt to set built-in variable value: %ls", wzVariable); + } + } + else // must *not* be a built-in variable so caller should not have tried to override it as a built-in. + { + // Not possible from external callers so just assert. + AssertSz(SET_VARIABLE_OVERRIDE_BUILTIN != setBuiltin, "Intent to overwrite non-built-in variable."); + } + + // Log value when not overwriting a built-in variable. + if (fLog && BURN_VARIABLE_INTERNAL_TYPE_NORMAL == pVariables->rgVariables[iVariable].internalType) + { + if (pVariables->rgVariables[iVariable].fHidden) + { + LogStringLine(REPORT_STANDARD, "Setting hidden variable '%ls'", wzVariable); + } + else + { + switch (pVariant->Type) + { + case BURN_VARIANT_TYPE_NONE: + if (BURN_VARIANT_TYPE_NONE != pVariables->rgVariables[iVariable].Value.Type) + { + LogStringLine(REPORT_STANDARD, "Unsetting variable '%ls'", wzVariable); + } + break; + + case BURN_VARIANT_TYPE_NUMERIC: + LogStringLine(REPORT_STANDARD, "Setting numeric variable '%ls' to value %lld", wzVariable, pVariant->llValue); + break; + + case BURN_VARIANT_TYPE_FORMATTED: __fallthrough; + case BURN_VARIANT_TYPE_STRING: + if (!pVariant->sczValue) + { + LogStringLine(REPORT_STANDARD, "Unsetting variable '%ls'", wzVariable); + } + else + { + LogStringLine(REPORT_STANDARD, "Setting %ls variable '%ls' to value '%ls'", BURN_VARIANT_TYPE_FORMATTED == pVariant->Type ? L"formatted" : L"string", wzVariable, pVariant->sczValue); + } + break; + + case BURN_VARIANT_TYPE_VERSION: + if (!pVariant->pValue) + { + LogStringLine(REPORT_STANDARD, "Unsetting variable '%ls'", wzVariable); + } + else + { + LogStringLine(REPORT_STANDARD, "Setting version variable '%ls' to value '%ls'", wzVariable, pVariant->pValue->sczVersion); + } + break; + + default: + AssertSz(FALSE, "Unknown variant type."); + break; + } + } + + if (BURN_VARIANT_TYPE_VERSION == pVariant->Type && pVariant->pValue && pVariant->pValue->fInvalid) + { + LogId(REPORT_WARNING, MSG_VARIABLE_INVALID_VERSION, wzVariable); + } + } + + // Update variable value. + hr = BVariantSetValue(&pVariables->rgVariables[iVariable].Value, pVariant); + ExitOnFailure(hr, "Failed to set value of variable: %ls", wzVariable); + +LExit: + ::LeaveCriticalSection(&pVariables->csAccess); + + if (FAILED(hr) && fLog) + { + LogStringLine(REPORT_STANDARD, "Setting variable failed: ID '%ls', HRESULT 0x%x", wzVariable, hr); + } + + return hr; +} + +static HRESULT InitializeVariableVersionNT( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + RTL_OSVERSIONINFOEXW ovix = { }; + BURN_VARIANT value = { }; + VERUTIL_VERSION* pVersion = NULL; + + hr = OsRtlGetVersion(&ovix); + ExitOnFailure(hr, "Failed to get OS info."); + + switch ((OS_INFO_VARIABLE)dwpData) + { + case OS_INFO_VARIABLE_ServicePackLevel: + if (0 != ovix.wServicePackMajor) + { + value.llValue = static_cast(ovix.wServicePackMajor); + value.Type = BURN_VARIANT_TYPE_NUMERIC; + } + break; + case OS_INFO_VARIABLE_VersionNT: + hr = VerVersionFromQword(MAKEQWORDVERSION(ovix.dwMajorVersion, ovix.dwMinorVersion, 0, 0), &pVersion); + ExitOnFailure(hr, "Failed to create VersionNT from QWORD."); + + value.pValue = pVersion; + value.Type = BURN_VARIANT_TYPE_VERSION; + break; + case OS_INFO_VARIABLE_VersionNT64: + { +#if !defined(_WIN64) + BOOL fIsWow64 = FALSE; + + ProcWow64(::GetCurrentProcess(), &fIsWow64); + if (fIsWow64) +#endif + { + hr = VerVersionFromQword(MAKEQWORDVERSION(ovix.dwMajorVersion, ovix.dwMinorVersion, 0, 0), &pVersion); + ExitOnFailure(hr, "Failed to create VersionNT64 from QWORD."); + + value.pValue = pVersion; + value.Type = BURN_VARIANT_TYPE_VERSION; + } + } + break; + case OS_INFO_VARIABLE_WindowsBuildNumber: + value.llValue = static_cast(ovix.dwBuildNumber); + value.Type = BURN_VARIANT_TYPE_NUMERIC; + default: + AssertSz(FALSE, "Unknown OS info type."); + break; + } + + hr = BVariantCopy(&value, pValue); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + ReleaseVerutilVersion(pVersion); + + return hr; +} + +static HRESULT InitializeVariableOsInfo( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + RTL_OSVERSIONINFOEXW ovix = { }; + BURN_VARIANT value = { }; + + hr = OsRtlGetVersion(&ovix); + ExitOnFailure(hr, "Failed to get OS info."); + + switch ((OS_INFO_VARIABLE)dwpData) + { + case OS_INFO_VARIABLE_NTProductType: + value.llValue = ovix.wProductType; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + case OS_INFO_VARIABLE_NTSuiteBackOffice: + value.llValue = VER_SUITE_BACKOFFICE & ovix.wSuiteMask ? 1 : 0; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + case OS_INFO_VARIABLE_NTSuiteDataCenter: + value.llValue = VER_SUITE_DATACENTER & ovix.wSuiteMask ? 1 : 0; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + case OS_INFO_VARIABLE_NTSuiteEnterprise: + value.llValue = VER_SUITE_ENTERPRISE & ovix.wSuiteMask ? 1 : 0; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + case OS_INFO_VARIABLE_NTSuitePersonal: + value.llValue = VER_SUITE_PERSONAL & ovix.wSuiteMask ? 1 : 0; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + case OS_INFO_VARIABLE_NTSuiteSmallBusiness: + value.llValue = VER_SUITE_SMALLBUSINESS & ovix.wSuiteMask ? 1 : 0; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + case OS_INFO_VARIABLE_NTSuiteSmallBusinessRestricted: + value.llValue = VER_SUITE_SMALLBUSINESS_RESTRICTED & ovix.wSuiteMask ? 1 : 0; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + case OS_INFO_VARIABLE_NTSuiteWebServer: + value.llValue = VER_SUITE_BLADE & ovix.wSuiteMask ? 1 : 0; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + case OS_INFO_VARIABLE_CompatibilityMode: + { + DWORDLONG dwlConditionMask = 0; + VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL); + VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL); + VER_SET_CONDITION(dwlConditionMask, VER_SERVICEPACKMAJOR, VER_EQUAL); + VER_SET_CONDITION(dwlConditionMask, VER_SERVICEPACKMINOR, VER_EQUAL); + + value.llValue = ::VerifyVersionInfoW(&ovix, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, dwlConditionMask); + value.Type = BURN_VARIANT_TYPE_NUMERIC; + } + break; + case OS_INFO_VARIABLE_TerminalServer: + value.llValue = (VER_SUITE_TERMINAL == (ovix.wSuiteMask & VER_SUITE_TERMINAL)) && (VER_SUITE_SINGLEUSERTS != (ovix.wSuiteMask & VER_SUITE_SINGLEUSERTS)) ? 1 : 0; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + default: + AssertSz(FALSE, "Unknown OS info type."); + break; + } + + hr = BVariantCopy(&value, pValue); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeVariableSystemInfo( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + SYSTEM_INFO si = { }; + BURN_VARIANT value = { }; + + ::GetNativeSystemInfo(&si); + + switch ((OS_INFO_VARIABLE)dwpData) + { + case OS_INFO_VARIABLE_ProcessorArchitecture: + value.llValue = si.wProcessorArchitecture; + value.Type = BURN_VARIANT_TYPE_NUMERIC; + break; + default: + AssertSz(FALSE, "Unknown OS info type."); + break; + } + + hr = BVariantCopy(&value, pValue); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeVariableComputerName( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + UNREFERENCED_PARAMETER(dwpData); + + HRESULT hr = S_OK; + WCHAR wzComputerName[MAX_COMPUTERNAME_LENGTH + 1] = { }; + DWORD cchComputerName = countof(wzComputerName); + + // get computer name + if (!::GetComputerNameW(wzComputerName, &cchComputerName)) + { + ExitWithLastError(hr, "Failed to get computer name."); + } + + // set value + hr = BVariantSetString(pValue, wzComputerName, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeVariableVersionMsi( + __in DWORD_PTR /*dwpData*/, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + DLLGETVERSIONPROC pfnMsiDllGetVersion = NULL; + DLLVERSIONINFO msiVersionInfo = { }; + VERUTIL_VERSION* pVersion = NULL; + + // get DllGetVersion proc address + pfnMsiDllGetVersion = (DLLGETVERSIONPROC)::GetProcAddress(::GetModuleHandleW(L"msi"), "DllGetVersion"); + ExitOnNullWithLastError(pfnMsiDllGetVersion, hr, "Failed to find DllGetVersion entry point in msi.dll."); + + // get msi.dll version info + msiVersionInfo.cbSize = sizeof(DLLVERSIONINFO); + hr = pfnMsiDllGetVersion(&msiVersionInfo); + ExitOnFailure(hr, "Failed to get msi.dll version info."); + + hr = VerVersionFromQword(MAKEQWORDVERSION(msiVersionInfo.dwMajorVersion, msiVersionInfo.dwMinorVersion, 0, 0), &pVersion); + ExitOnFailure(hr, "Failed to create msi.dll version from QWORD."); + + hr = BVariantSetVersion(pValue, pVersion); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + ReleaseVerutilVersion(pVersion); + + return hr; +} + +static HRESULT InitializeVariableCsidlFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + LPWSTR sczPath = NULL; + int nFolder = (int)dwpData; + + // get folder path + hr = ShelGetFolder(&sczPath, nFolder); + ExitOnRootFailure(hr, "Failed to get shell folder."); + + // set value + hr = BVariantSetString(pValue, sczPath, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + ReleaseStr(sczPath); + + return hr; +} + +static HRESULT InitializeVariableTempFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + UNREFERENCED_PARAMETER(dwpData); + + HRESULT hr = S_OK; + WCHAR wzPath[MAX_PATH] = { }; + + // get volume path name + if (!::GetTempPathW(MAX_PATH, wzPath)) + { + ExitWithLastError(hr, "Failed to get temp path."); + } + + // set value + hr = BVariantSetString(pValue, wzPath, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeVariableSystemFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + BOOL f64 = (BOOL)dwpData; + WCHAR wzSystemFolder[MAX_PATH] = { }; + +#if !defined(_WIN64) + BOOL fIsWow64 = FALSE; + ProcWow64(::GetCurrentProcess(), &fIsWow64); + + if (fIsWow64) + { + if (f64) + { + if (!::GetSystemDirectoryW(wzSystemFolder, countof(wzSystemFolder))) + { + ExitWithLastError(hr, "Failed to get 64-bit system folder."); + } + } + else + { + if (!::GetSystemWow64DirectoryW(wzSystemFolder, countof(wzSystemFolder))) + { + ExitWithLastError(hr, "Failed to get 32-bit system folder."); + } + } + } + else + { + if (!f64) + { + if (!::GetSystemDirectoryW(wzSystemFolder, countof(wzSystemFolder))) + { + ExitWithLastError(hr, "Failed to get 32-bit system folder."); + } + } + } +#else + if (f64) + { + if (!::GetSystemDirectoryW(wzSystemFolder, countof(wzSystemFolder))) + { + ExitWithLastError(hr, "Failed to get 64-bit system folder."); + } + } + else + { + if (!::GetSystemWow64DirectoryW(wzSystemFolder, countof(wzSystemFolder))) + { + ExitWithLastError(hr, "Failed to get 32-bit system folder."); + } + } +#endif + + if (*wzSystemFolder) + { + hr = PathFixedBackslashTerminate(wzSystemFolder, countof(wzSystemFolder)); + ExitOnFailure(hr, "Failed to backslash terminate system folder."); + } + + // set value + hr = BVariantSetString(pValue, wzSystemFolder, 0, FALSE); + ExitOnFailure(hr, "Failed to set system folder variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeVariableWindowsVolumeFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + UNREFERENCED_PARAMETER(dwpData); + + HRESULT hr = S_OK; + WCHAR wzWindowsPath[MAX_PATH] = { }; + WCHAR wzVolumePath[MAX_PATH] = { }; + + // get windows directory + if (!::GetWindowsDirectoryW(wzWindowsPath, countof(wzWindowsPath))) + { + ExitWithLastError(hr, "Failed to get windows directory."); + } + + // get volume path name + if (!::GetVolumePathNameW(wzWindowsPath, wzVolumePath, MAX_PATH)) + { + ExitWithLastError(hr, "Failed to get volume path name."); + } + + // set value + hr = BVariantSetString(pValue, wzVolumePath, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeVariablePrivileged( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + UNREFERENCED_PARAMETER(dwpData); + + HRESULT hr = S_OK; + BOOL fPrivileged = FALSE; + + // check if process could run privileged. + hr = OsCouldRunPrivileged(&fPrivileged); + ExitOnFailure(hr, "Failed to check if process could run privileged."); + + // set value + hr = BVariantSetNumeric(pValue, fPrivileged); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeSystemLanguageID( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + UNREFERENCED_PARAMETER(dwpData); + + HRESULT hr = S_OK; + LANGID langid = ::GetSystemDefaultLangID(); + + hr = BVariantSetNumeric(pValue, langid); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeUserUILanguageID( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + UNREFERENCED_PARAMETER(dwpData); + + HRESULT hr = S_OK; + LANGID langid = ::GetUserDefaultUILanguage(); + + hr = BVariantSetNumeric(pValue, langid); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeUserLanguageID( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + UNREFERENCED_PARAMETER(dwpData); + + HRESULT hr = S_OK; + LANGID langid = ::GetUserDefaultLangID(); + + hr = BVariantSetNumeric(pValue, langid); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeVariableString( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + LPCWSTR wzValue = (LPCWSTR)dwpData; + + // set value + hr = BVariantSetString(pValue, wzValue, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeVariableNumeric( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + LONGLONG llValue = (LONGLONG)dwpData; + + // set value + hr = BVariantSetNumeric(pValue, llValue); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +#if !defined(_WIN64) +static HRESULT InitializeVariableRegistryFolder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + int nFolder = (int)dwpData; + LPWSTR sczPath = NULL; + + BOOL fIsWow64 = FALSE; + + ProcWow64(::GetCurrentProcess(), &fIsWow64); + if (!fIsWow64) // on 32-bit machines, variables aren't set + { + ExitFunction(); + } + + hr = Get64bitFolderFromRegistry(nFolder, &sczPath); + ExitOnFailure(hr, "Failed to get 64-bit folder."); + + // set value + hr = BVariantSetString(pValue, sczPath, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + ReleaseStr(sczPath); + + return hr; +} +#endif + +static HRESULT InitializeVariable6432Folder( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + int nFolder = (int)dwpData; + LPWSTR sczPath = NULL; + +#if !defined(_WIN64) + BOOL fIsWow64 = FALSE; + + // If 32-bit use shell-folder. + ProcWow64(::GetCurrentProcess(), &fIsWow64); + if (!fIsWow64) + { + hr = ShelGetFolder(&sczPath, nFolder); + ExitOnRootFailure(hr, "Failed to get shell folder."); + } + else +#endif + { + hr = Get64bitFolderFromRegistry(nFolder, &sczPath); + ExitOnFailure(hr, "Failed to get 64-bit folder."); + } + + // set value + hr = BVariantSetString(pValue, sczPath, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + ReleaseStr(sczPath); + + return hr; +} + +// Get the date in the same format as Windows Installer. +static HRESULT InitializeVariableDate( + __in DWORD_PTR /*dwpData*/, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + SYSTEMTIME systime = { }; + LPWSTR sczDate = NULL; + int cchDate = 0; + + ::GetSystemTime(&systime); + + cchDate = ::GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime, NULL, NULL, cchDate); + if (!cchDate) + { + ExitOnLastError(hr, "Failed to get the required buffer length for the Date."); + } + + hr = StrAlloc(&sczDate, cchDate); + ExitOnFailure(hr, "Failed to allocate the buffer for the Date."); + + if (!::GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime, NULL, sczDate, cchDate)) + { + ExitOnLastError(hr, "Failed to get the Date."); + } + + // set value + hr = BVariantSetString(pValue, sczDate, cchDate, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + ReleaseStr(sczDate); + + return hr; +} + +static HRESULT InitializeVariableInstallerName( + __in DWORD_PTR /*dwpData*/, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + + // set value + hr = BVariantSetString(pValue, L"WiX Burn", 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT InitializeVariableInstallerVersion( + __in DWORD_PTR /*dwpData*/, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + LPWSTR sczVersion = NULL; + + hr = StrAllocStringAnsi(&sczVersion, szVerMajorMinorBuild, 0, CP_ACP); + ExitOnFailure(hr, "Failed to copy the engine version."); + + // set value + hr = BVariantSetString(pValue, sczVersion, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + ReleaseStr(sczVersion); + + return hr; +} + +static HRESULT InitializeVariableVersion( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + LPCWSTR wzValue = (LPCWSTR)dwpData; + VERUTIL_VERSION* pVersion = NULL; + + hr = VerParseVersion(wzValue, 0, FALSE, &pVersion); + ExitOnFailure(hr, "Failed to initialize version."); + + // set value + hr = BVariantSetVersion(pValue, pVersion); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + ReleaseVerutilVersion(pVersion); + + return hr; +} + +// Get the current user the same as Windows Installer. +static HRESULT InitializeVariableLogonUser( + __in DWORD_PTR /*dwpData*/, + __inout BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + WCHAR wzUserName[UNLEN + 1]; + DWORD cchUserName = countof(wzUserName); + + if (!::GetUserNameW(wzUserName, &cchUserName)) + { + ExitOnLastError(hr, "Failed to get the user name."); + } + + // set value + hr = BVariantSetString(pValue, wzUserName, 0, FALSE); + ExitOnFailure(hr, "Failed to set variant value."); + +LExit: + return hr; +} + +static HRESULT Get64bitFolderFromRegistry( + __in int nFolder, + __deref_out_z LPWSTR* psczPath + ) +{ + HRESULT hr = S_OK; + HKEY hkFolders = NULL; + + AssertSz(CSIDL_PROGRAM_FILES == nFolder || CSIDL_PROGRAM_FILES_COMMON == nFolder, "Unknown folder CSIDL."); + LPCWSTR wzFolderValue = CSIDL_PROGRAM_FILES_COMMON == nFolder ? L"CommonFilesDir" : L"ProgramFilesDir"; + + hr = RegOpen(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion", KEY_READ | KEY_WOW64_64KEY, &hkFolders); + ExitOnFailure(hr, "Failed to open Windows folder key."); + + hr = RegReadString(hkFolders, wzFolderValue, psczPath); + ExitOnFailure(hr, "Failed to read folder path for '%ls'.", wzFolderValue); + + hr = PathBackslashTerminate(psczPath); + ExitOnFailure(hr, "Failed to ensure path was backslash terminated."); + +LExit: + ReleaseRegKey(hkFolders); + + return hr; +} + diff --git a/src/burn/engine/variable.h b/src/burn/engine/variable.h new file mode 100644 index 00000000..a38c9daa --- /dev/null +++ b/src/burn/engine/variable.h @@ -0,0 +1,185 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + +const LPCWSTR VARIABLE_DATE = L"Date"; +const LPCWSTR VARIABLE_LOGONUSER = L"LogonUser"; +const LPCWSTR VARIABLE_INSTALLERNAME = L"InstallerName"; +const LPCWSTR VARIABLE_INSTALLERVERSION = L"InstallerVersion"; + + +// typedefs + +typedef HRESULT (*PFN_INITIALIZEVARIABLE)( + __in DWORD_PTR dwpData, + __inout BURN_VARIANT* pValue + ); + + +// constants + +enum BURN_VARIABLE_INTERNAL_TYPE +{ + BURN_VARIABLE_INTERNAL_TYPE_NORMAL, // the BA can set this variable. + BURN_VARIABLE_INTERNAL_TYPE_OVERRIDABLE_BUILTIN, // the BA can't set this variable, but the unelevated process can serialize it to the elevated process. + BURN_VARIABLE_INTERNAL_TYPE_BUILTIN, // the BA can't set this variable, and the unelevated process can't serialize it to the elevated process. +}; + + +// structs + +typedef struct _BURN_VARIABLE +{ + LPWSTR sczName; + BURN_VARIANT Value; + BOOL fHidden; + BOOL fPersisted; + + // used for late initialization of built-in variables + BURN_VARIABLE_INTERNAL_TYPE internalType; + PFN_INITIALIZEVARIABLE pfnInitialize; + DWORD_PTR dwpInitializeData; +} BURN_VARIABLE; + +typedef struct _BURN_VARIABLES +{ + CRITICAL_SECTION csAccess; + DWORD dwMaxVariables; + DWORD cVariables; + BURN_VARIABLE* rgVariables; +} BURN_VARIABLES; + + +// function declarations + +HRESULT VariableInitialize( + __in BURN_VARIABLES* pVariables + ); +HRESULT VariablesParseFromXml( + __in BURN_VARIABLES* pVariables, + __in IXMLDOMNode* pixnBundle + ); +void VariablesUninitialize( + __in BURN_VARIABLES* pVariables + ); +void VariablesDump( + __in BURN_VARIABLES* pVariables + ); +HRESULT VariableGetNumeric( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out LONGLONG* pllValue + ); +HRESULT VariableGetString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out_z LPWSTR* psczValue + ); +HRESULT VariableGetVersion( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in VERUTIL_VERSION** ppValue + ); +HRESULT VariableGetVariant( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in BURN_VARIANT* pValue + ); +HRESULT VariableGetFormatted( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out_z LPWSTR* psczValue, + __out BOOL* pfContainsHiddenVariable + ); +HRESULT VariableSetNumeric( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in LONGLONG llValue, + __in BOOL fOverwriteBuiltIn + ); +HRESULT VariableSetString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in_z_opt LPCWSTR wzValue, + __in BOOL fOverwriteBuiltIn, + __in BOOL fFormatted + ); +HRESULT VariableSetVersion( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in VERUTIL_VERSION* pValue, + __in BOOL fOverwriteBuiltIn + ); +HRESULT VariableSetVariant( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __in BURN_VARIANT* pVariant + ); +HRESULT VariableFormatString( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt SIZE_T* pcchOut + ); +HRESULT VariableFormatStringObfuscated( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzIn, + __out_z_opt LPWSTR* psczOut, + __out_opt SIZE_T* pcchOut + ); +HRESULT VariableEscapeString( + __in_z LPCWSTR wzIn, + __out_z LPWSTR* psczOut + ); +HRESULT VariableSerialize( + __in BURN_VARIABLES* pVariables, + __in BOOL fPersisting, + __inout BYTE** ppbBuffer, + __inout SIZE_T* piBuffer + ); +HRESULT VariableDeserialize( + __in BURN_VARIABLES* pVariables, + __in BOOL fWasPersisted, + __in_bcount(cbBuffer) BYTE* pbBuffer, + __in SIZE_T cbBuffer, + __inout SIZE_T* piBuffer + ); +HRESULT VariableStrAlloc( + __in BOOL fZeroOnRealloc, + __deref_out_ecount_part(cch, 0) LPWSTR* ppwz, + __in DWORD_PTR cch + ); +HRESULT VariableStrAllocString( + __in BOOL fZeroOnRealloc, + __deref_out_ecount_z(cchSource + 1) LPWSTR* ppwz, + __in_z LPCWSTR wzSource, + __in DWORD_PTR cchSource + ); +HRESULT VariableStrAllocConcat( + __in BOOL fZeroOnRealloc, + __deref_out_z LPWSTR* ppwz, + __in_z LPCWSTR wzSource, + __in DWORD_PTR cchSource + ); +HRESULT __cdecl VariableStrAllocFormatted( + __in BOOL fZeroOnRealloc, + __deref_out_z LPWSTR* ppwz, + __in __format_string LPCWSTR wzFormat, + ... + ); +HRESULT VariableIsHidden( + __in BURN_VARIABLES* pVariables, + __in_z LPCWSTR wzVariable, + __out BOOL* pfHidden + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/engine/variant.cpp b/src/burn/engine/variant.cpp new file mode 100644 index 00000000..2267ee7b --- /dev/null +++ b/src/burn/engine/variant.cpp @@ -0,0 +1,321 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// internal function declarations + +static HRESULT GetVersionInternal( + __in BURN_VARIANT* pVariant, + __in BOOL fHidden, + __in BOOL fSilent, + __out VERUTIL_VERSION** ppValue + ); + +// function definitions + +extern "C" void BVariantUninitialize( + __in BURN_VARIANT* pVariant + ) +{ + if (BURN_VARIANT_TYPE_FORMATTED == pVariant->Type || + BURN_VARIANT_TYPE_STRING == pVariant->Type) + { + StrSecureZeroFreeString(pVariant->sczValue); + } + SecureZeroMemory(pVariant, sizeof(BURN_VARIANT)); +} + +extern "C" HRESULT BVariantGetNumeric( + __in BURN_VARIANT* pVariant, + __out LONGLONG* pllValue + ) +{ + HRESULT hr = S_OK; + + switch (pVariant->Type) + { + case BURN_VARIANT_TYPE_NUMERIC: + *pllValue = pVariant->llValue; + break; + case BURN_VARIANT_TYPE_FORMATTED: __fallthrough; + case BURN_VARIANT_TYPE_STRING: + hr = StrStringToInt64(pVariant->sczValue, 0, pllValue); + if (FAILED(hr)) + { + hr = DISP_E_TYPEMISMATCH; + } + break; + case BURN_VARIANT_TYPE_VERSION: + hr = StrStringToInt64(pVariant->pValue ? pVariant->pValue->sczVersion : NULL, 0, pllValue); + if (FAILED(hr)) + { + hr = DISP_E_TYPEMISMATCH; + } + break; + default: + hr = E_INVALIDARG; + break; + } + + return hr; +} + +extern "C" HRESULT BVariantGetString( + __in BURN_VARIANT* pVariant, + __out_z LPWSTR* psczValue + ) +{ + HRESULT hr = S_OK; + + switch (pVariant->Type) + { + case BURN_VARIANT_TYPE_NUMERIC: + hr = StrAllocFormattedSecure(psczValue, L"%I64d", pVariant->llValue); + ExitOnFailure(hr, "Failed to convert int64 to string."); + break; + case BURN_VARIANT_TYPE_FORMATTED: __fallthrough; + case BURN_VARIANT_TYPE_STRING: + hr = StrAllocStringSecure(psczValue, pVariant->sczValue, 0); + ExitOnFailure(hr, "Failed to copy string value."); + break; + case BURN_VARIANT_TYPE_VERSION: + hr = StrAllocStringSecure(psczValue, pVariant->pValue ? pVariant->pValue->sczVersion : NULL, 0); + ExitOnFailure(hr, "Failed to copy version value."); + break; + default: + hr = E_INVALIDARG; + break; + } + +LExit: + return hr; +} + +extern "C" HRESULT BVariantGetVersion( + __in BURN_VARIANT* pVariant, + __out VERUTIL_VERSION** ppValue + ) +{ + return GetVersionInternal(pVariant, FALSE, FALSE, ppValue); +} + +extern "C" HRESULT BVariantGetVersionHidden( + __in BURN_VARIANT* pVariant, + __in BOOL fHidden, + __out VERUTIL_VERSION** ppValue + ) +{ + return GetVersionInternal(pVariant, fHidden, FALSE, ppValue); +} + +extern "C" HRESULT BVariantGetVersionSilent( + __in BURN_VARIANT* pVariant, + __in BOOL fSilent, + __out VERUTIL_VERSION** ppValue + ) +{ + return GetVersionInternal(pVariant, FALSE, fSilent, ppValue); +} + +static HRESULT GetVersionInternal( + __in BURN_VARIANT* pVariant, + __in BOOL fHidden, + __in BOOL fSilent, + __out VERUTIL_VERSION** ppValue + ) +{ + HRESULT hr = S_OK; + + switch (pVariant->Type) + { + case BURN_VARIANT_TYPE_NUMERIC: + hr = VerVersionFromQword(pVariant->llValue, ppValue); + break; + case BURN_VARIANT_TYPE_FORMATTED: __fallthrough; + case BURN_VARIANT_TYPE_STRING: + hr = VerParseVersion(pVariant->sczValue, 0, FALSE, ppValue); + if (SUCCEEDED(hr) && !fSilent && (*ppValue)->fInvalid) + { + LogId(REPORT_WARNING, MSG_INVALID_VERSION_COERSION, fHidden ? L"*****" : pVariant->sczValue); + } + break; + case BURN_VARIANT_TYPE_VERSION: + if (!pVariant->pValue) + { + *ppValue = NULL; + } + else + { + hr = VerCopyVersion(pVariant->pValue, ppValue); + } + break; + default: + hr = E_INVALIDARG; + break; + } + + return hr; +} + +extern "C" HRESULT BVariantSetNumeric( + __in BURN_VARIANT* pVariant, + __in LONGLONG llValue + ) +{ + HRESULT hr = S_OK; + + if (BURN_VARIANT_TYPE_FORMATTED == pVariant->Type || + BURN_VARIANT_TYPE_STRING == pVariant->Type) + { + StrSecureZeroFreeString(pVariant->sczValue); + } + memset(pVariant, 0, sizeof(BURN_VARIANT)); + pVariant->llValue = llValue; + pVariant->Type = BURN_VARIANT_TYPE_NUMERIC; + + return hr; +} + +extern "C" HRESULT BVariantSetString( + __in BURN_VARIANT* pVariant, + __in_z_opt LPCWSTR wzValue, + __in DWORD_PTR cchValue, + __in BOOL fFormatted + ) +{ + HRESULT hr = S_OK; + + if (!wzValue) // if we're nulling out the string, make the variable NONE. + { + BVariantUninitialize(pVariant); + } + else // assign the value. + { + if (BURN_VARIANT_TYPE_FORMATTED != pVariant->Type && + BURN_VARIANT_TYPE_STRING != pVariant->Type) + { + memset(pVariant, 0, sizeof(BURN_VARIANT)); + } + + hr = StrAllocStringSecure(&pVariant->sczValue, wzValue, cchValue); + ExitOnFailure(hr, "Failed to copy string."); + + pVariant->Type = fFormatted ? BURN_VARIANT_TYPE_FORMATTED : BURN_VARIANT_TYPE_STRING; + } + +LExit: + return hr; +} + +extern "C" HRESULT BVariantSetVersion( + __in BURN_VARIANT* pVariant, + __in VERUTIL_VERSION* pValue + ) +{ + HRESULT hr = S_OK; + + if (!pValue) // if we're nulling out the version, make the variable NONE. + { + BVariantUninitialize(pVariant); + } + else // assign the value. + { + if (BURN_VARIANT_TYPE_FORMATTED == pVariant->Type || + BURN_VARIANT_TYPE_STRING == pVariant->Type) + { + StrSecureZeroFreeString(pVariant->sczValue); + } + memset(pVariant, 0, sizeof(BURN_VARIANT)); + hr = VerCopyVersion(pValue, &pVariant->pValue); + pVariant->Type = BURN_VARIANT_TYPE_VERSION; + } + + return hr; +} + +extern "C" HRESULT BVariantSetValue( + __in BURN_VARIANT* pVariant, + __in BURN_VARIANT* pValue + ) +{ + HRESULT hr = S_OK; + + switch (pValue->Type) + { + case BURN_VARIANT_TYPE_NONE: + BVariantUninitialize(pVariant); + break; + case BURN_VARIANT_TYPE_NUMERIC: + hr = BVariantSetNumeric(pVariant, pValue->llValue); + break; + case BURN_VARIANT_TYPE_FORMATTED: __fallthrough; + case BURN_VARIANT_TYPE_STRING: + hr = BVariantSetString(pVariant, pValue->sczValue, 0, BURN_VARIANT_TYPE_FORMATTED == pValue->Type); + break; + case BURN_VARIANT_TYPE_VERSION: + hr = BVariantSetVersion(pVariant, pValue->pValue); + break; + default: + hr = E_INVALIDARG; + } + ExitOnFailure(hr, "Failed to copy variant value."); + +LExit: + return hr; +} + +extern "C" HRESULT BVariantCopy( + __in BURN_VARIANT* pSource, + __out BURN_VARIANT* pTarget + ) +{ + return BVariantSetValue(pTarget, pSource); +} + +extern "C" HRESULT BVariantChangeType( + __in BURN_VARIANT* pVariant, + __in BURN_VARIANT_TYPE type + ) +{ + HRESULT hr = S_OK; + BURN_VARIANT variant = { }; + + if (pVariant->Type == type) + { + ExitFunction(); // variant already is of the requested type + } + else if (BURN_VARIANT_TYPE_FORMATTED == pVariant->Type && BURN_VARIANT_TYPE_STRING == type || + BURN_VARIANT_TYPE_STRING == pVariant->Type && BURN_VARIANT_TYPE_FORMATTED == type) + { + pVariant->Type = type; + ExitFunction(); + } + + switch (type) + { + case BURN_VARIANT_TYPE_NONE: + hr = S_OK; + break; + case BURN_VARIANT_TYPE_NUMERIC: + hr = BVariantGetNumeric(pVariant, &variant.llValue); + break; + case BURN_VARIANT_TYPE_FORMATTED: __fallthrough; + case BURN_VARIANT_TYPE_STRING: + hr = BVariantGetString(pVariant, &variant.sczValue); + break; + case BURN_VARIANT_TYPE_VERSION: + hr = BVariantGetVersionSilent(pVariant, TRUE, &variant.pValue); + break; + default: + ExitFunction1(hr = E_INVALIDARG); + } + variant.Type = type; + ExitOnFailure(hr, "Failed to copy variant value."); + + BVariantUninitialize(pVariant); + memcpy_s(pVariant, sizeof(BURN_VARIANT), &variant, sizeof(BURN_VARIANT)); + SecureZeroMemory(&variant, sizeof(BURN_VARIANT)); + +LExit: + return hr; +} diff --git a/src/burn/engine/variant.h b/src/burn/engine/variant.h new file mode 100644 index 00000000..e460005b --- /dev/null +++ b/src/burn/engine/variant.h @@ -0,0 +1,100 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#if defined(__cplusplus) +extern "C" { +#endif + + +// constants + +enum BURN_VARIANT_TYPE +{ + BURN_VARIANT_TYPE_NONE, + BURN_VARIANT_TYPE_FORMATTED, + BURN_VARIANT_TYPE_NUMERIC, + BURN_VARIANT_TYPE_STRING, // when formatting this value should be used as is (don't continue recursively formatting). + BURN_VARIANT_TYPE_VERSION, +}; + + +// struct + +typedef struct _BURN_VARIANT +{ + union + { + LONGLONG llValue; + VERUTIL_VERSION* pValue; + LPWSTR sczValue; + }; + BURN_VARIANT_TYPE Type; +} BURN_VARIANT; + + +// function declarations + +void BVariantUninitialize( + __in BURN_VARIANT* pVariant + ); +HRESULT BVariantGetNumeric( + __in BURN_VARIANT* pVariant, + __out LONGLONG* pllValue + ); +HRESULT BVariantGetString( + __in BURN_VARIANT* pVariant, + __out_z LPWSTR* psczValue + ); +HRESULT BVariantGetVersion( + __in BURN_VARIANT* pVariant, + __out VERUTIL_VERSION** ppValue + ); +HRESULT BVariantGetVersionHidden( + __in BURN_VARIANT* pVariant, + __in BOOL fHidden, + __out VERUTIL_VERSION** ppValue + ); +HRESULT BVariantGetVersionSilent( + __in BURN_VARIANT* pVariant, + __in BOOL fSilent, + __out VERUTIL_VERSION** ppValue + ); +HRESULT BVariantSetNumeric( + __in BURN_VARIANT* pVariant, + __in LONGLONG llValue + ); +HRESULT BVariantSetString( + __in BURN_VARIANT* pVariant, + __in_z_opt LPCWSTR wzValue, + __in DWORD_PTR cchValue, + __in BOOL fFormatted + ); +HRESULT BVariantSetVersion( + __in BURN_VARIANT* pVariant, + __in VERUTIL_VERSION* pValue + ); +/******************************************************************** +BVariantSetValue - Convenience function that calls BVariantUninitialize, + BVariantSetNumeric, BVariantSetString, or + BVariantSetVersion based on the type of pValue. +********************************************************************/ +HRESULT BVariantSetValue( + __in BURN_VARIANT* pVariant, + __in BURN_VARIANT* pValue + ); +/******************************************************************** +BVariantCopy - creates a copy of pSource. +********************************************************************/ +HRESULT BVariantCopy( + __in BURN_VARIANT* pSource, + __out BURN_VARIANT* pTarget + ); +HRESULT BVariantChangeType( + __in BURN_VARIANT* pVariant, + __in BURN_VARIANT_TYPE type + ); + +#if defined(__cplusplus) +} +#endif diff --git a/src/burn/nuget.config b/src/burn/nuget.config new file mode 100644 index 00000000..237c522e --- /dev/null +++ b/src/burn/nuget.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/burn/stub/StubSection.cpp b/src/burn/stub/StubSection.cpp new file mode 100644 index 00000000..962bb3cf --- /dev/null +++ b/src/burn/stub/StubSection.cpp @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +#pragma section(".wixburn",read) + +// If these defaults ever change, be sure to update constants in burn\engine\section.cpp as well. +#pragma data_seg(push, ".wixburn") +static DWORD dwMagic = 0x00f14300; +static DWORD dwVersion = 0x00000002; + +static GUID guidBundleId = { }; + +static DWORD dwStubSize = 0; +static DWORD dwOriginalChecksum = 0; +static DWORD dwOriginalSignatureOffset = 0; +static DWORD dwOriginalSignatureSize = 0; + +static DWORD dwContainerFormat = 1; +static DWORD dwContainerCount = 0; +static DWORD qwBootstrapperApplicationContainerSize = 0; +static DWORD qwAttachedContainerSize = 0; +#pragma data_seg(pop) diff --git a/src/burn/stub/WixToolset.Burn.props b/src/burn/stub/WixToolset.Burn.props new file mode 100644 index 00000000..38cd333e --- /dev/null +++ b/src/burn/stub/WixToolset.Burn.props @@ -0,0 +1,13 @@ + + + + + + + + %(RecursiveDir)%(FileName)%(Extension) + False + PreserveNewest + + + diff --git a/src/burn/stub/packages.config b/src/burn/stub/packages.config new file mode 100644 index 00000000..a98c0c8e --- /dev/null +++ b/src/burn/stub/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/burn/stub/precomp.cpp b/src/burn/stub/precomp.cpp new file mode 100644 index 00000000..37664a1c --- /dev/null +++ b/src/burn/stub/precomp.cpp @@ -0,0 +1,3 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" diff --git a/src/burn/stub/precomp.h b/src/burn/stub/precomp.h new file mode 100644 index 00000000..bb7ded9c --- /dev/null +++ b/src/burn/stub/precomp.h @@ -0,0 +1,17 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "engine.h" diff --git a/src/burn/stub/stub.cpp b/src/burn/stub/stub.cpp new file mode 100644 index 00000000..0cb202e0 --- /dev/null +++ b/src/burn/stub/stub.cpp @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +static void CALLBACK BurnTraceError( + __in_z LPCSTR szFile, + __in int iLine, + __in REPORT_LEVEL rl, + __in UINT source, + __in HRESULT hrError, + __in_z __format_string LPCSTR szFormat, + __in va_list args + ); + +int WINAPI wWinMain( + __in HINSTANCE hInstance, + __in_opt HINSTANCE /* hPrevInstance */, + __in_z_opt LPWSTR lpCmdLine, + __in int nCmdShow + ) +{ + HRESULT hr = S_OK; + DWORD dwExitCode = 0; + LPWSTR sczPath = NULL; + HANDLE hEngineFile = INVALID_HANDLE_VALUE; + + LPCWSTR rgsczSafelyLoadSystemDlls[] = + { + L"cabinet.dll", // required by Burn. + L"msi.dll", // required by Burn. + L"version.dll", // required by Burn. + L"wininet.dll", // required by Burn. + + L"comres.dll", // required by CLSIDFromProgID() when loading clbcatq.dll. + L"clbcatq.dll", // required by CLSIDFromProgID() when loading msxml?.dll. + + L"msasn1.dll", // required by DecryptFile() when loading crypt32.dll. + L"crypt32.dll", // required by DecryptFile() when loading feclient.dll. + L"feclient.dll", // unsafely loaded by DecryptFile(). + }; + + DutilInitialize(&BurnTraceError); + + // Best effort attempt to get our file handle as soon as possible. + hr = PathForCurrentProcess(&sczPath, NULL); + if (SUCCEEDED(hr)) + { + hEngineFile = ::CreateFileW(sczPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + } + + // If the engine is in the clean room, we'll do the unsafe initialization + // because some systems in Windows (namely GDI+) will fail when run in + // a process that protects against DLL hijacking. Since we know the clean + // room is in a clean folder and not subject to DLL hijacking we won't + // make ourselves perfectly secure so that we can load BAs that still + // depend on those parts of Windows that are insecure to DLL hijacking. + if (EngineInCleanRoom(lpCmdLine)) + { + AppInitializeUnsafe(); + } + else + { + AppInitialize(rgsczSafelyLoadSystemDlls, countof(rgsczSafelyLoadSystemDlls)); + } + + // call run + hr = EngineRun(hInstance, hEngineFile, lpCmdLine, nCmdShow, &dwExitCode); + ExitOnFailure(hr, "Failed to run application."); + +LExit: + ReleaseFileHandle(hEngineFile); + ReleaseStr(sczPath); + + DutilUninitialize(); + + return FAILED(hr) ? (int)hr : (int)dwExitCode; +} + +static void CALLBACK BurnTraceError( + __in_z LPCSTR /*szFile*/, + __in int /*iLine*/, + __in REPORT_LEVEL /*rl*/, + __in UINT source, + __in HRESULT hrError, + __in_z __format_string LPCSTR szFormat, + __in va_list args + ) +{ + BOOL fLog = FALSE; + + switch (source) + { + case DUTIL_SOURCE_DEFAULT: + fLog = TRUE; + break; + default: + fLog = REPORT_VERBOSE < LogGetLevel(); + break; + } + + if (fLog) + { + LogErrorStringArgs(hrError, szFormat, args); + } +} diff --git a/src/burn/stub/stub.ico b/src/burn/stub/stub.ico new file mode 100644 index 00000000..c2e2717c Binary files /dev/null and b/src/burn/stub/stub.ico differ diff --git a/src/burn/stub/stub.nuspec b/src/burn/stub/stub.nuspec new file mode 100644 index 00000000..968feff3 --- /dev/null +++ b/src/burn/stub/stub.nuspec @@ -0,0 +1,25 @@ + + + + $id$ + $version$ + $title$ + $description$ + $authors$ + MS-RL + false + $copyright$ + $projectUrl$ + + + + + + + + + + + + + diff --git a/src/burn/stub/stub.rc b/src/burn/stub/stub.rc new file mode 100644 index 00000000..80e1aac4 --- /dev/null +++ b/src/burn/stub/stub.rc @@ -0,0 +1,3 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +1 ICON "stub.ico" diff --git a/src/burn/stub/stub.vcxproj b/src/burn/stub/stub.vcxproj new file mode 100644 index 00000000..97972848 --- /dev/null +++ b/src/burn/stub/stub.vcxproj @@ -0,0 +1,120 @@ + + + + + + + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + {C38373AA-882F-4F55-B03F-2AAB4BFBE3F1} + Application + Windows + burn + v142 + Unicode + false + Native component of WixToolset.Burn + + 1033 + Burn + WixToolset.Burn + false + + + + + + + + + + + + + + + + + $(ProjectDir)..\engine\inc + cabinet.lib;crypt32.lib;msi.lib;rpcrt4.lib;shlwapi.lib;wininet.lib;wuguid.lib;engine.res + + + + + true + true + cabinet.dll;crypt32.dll;msi.dll;shlwapi.dll;version.dll;wininet.dll + + + + + + + + + Create + + + + + false + + + + + + + + + {8119537D-E1D9-6591-D51A-49768A2F9C37} + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable 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/burn/test/BurnUnitTest/AssemblyInfo.cpp b/src/burn/test/BurnUnitTest/AssemblyInfo.cpp new file mode 100644 index 00000000..0282b1b7 --- /dev/null +++ b/src/burn/test/BurnUnitTest/AssemblyInfo.cpp @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +using namespace System::Reflection; +using namespace System::Runtime::CompilerServices; +using namespace System::Runtime::InteropServices; + +[assembly: AssemblyTitleAttribute("Windows Installer XML Burn unit tests")]; +[assembly: AssemblyDescriptionAttribute("Burn unit tests")]; +[assembly: AssemblyCultureAttribute("")]; +[assembly: ComVisible(false)]; diff --git a/src/burn/test/BurnUnitTest/BurnTestException.h b/src/burn/test/BurnUnitTest/BurnTestException.h new file mode 100644 index 00000000..bd94b4fc --- /dev/null +++ b/src/burn/test/BurnUnitTest/BurnTestException.h @@ -0,0 +1,93 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + + public ref struct BurnTestException : public System::Exception + { + public: + BurnTestException(HRESULT error) + { + this->HResult = error; + } + + BurnTestException(HRESULT error, String^ message) + : Exception(message) + { + this->HResult = error; + } + + property Int32 ErrorCode + { + Int32 get() + { + return this->HResult; + } + } + + }; +} +} +} +} +} + +// this class is used by __TestThrowOnFailure_Format() below to deallocate +// the string created after the function call has returned +class __TestThrowOnFailure_StringFree +{ + LPWSTR m_scz; + +public: + __TestThrowOnFailure_StringFree(LPWSTR scz) + { + m_scz = scz; + } + + ~__TestThrowOnFailure_StringFree() + { + ReleaseStr(m_scz); + } + + operator LPCWSTR() + { + return m_scz; + } +}; + +// used by the TestThrowOnFailure macros to format the error string and +// return an LPCWSTR that can be used to initialize a System::String +#pragma warning (push) +#pragma warning (disable : 4793) +inline __TestThrowOnFailure_StringFree __TestThrowOnFailure_Format(LPCWSTR wzFormat, ...) +{ + Assert(wzFormat && *wzFormat); + + HRESULT hr = S_OK; + LPWSTR scz = NULL; + va_list args; + + va_start(args, wzFormat); + hr = StrAllocFormattedArgs(&scz, wzFormat, args); + va_end(args); + ExitOnFailure(hr, "Failed to format message string."); + +LExit: + return scz; +} +#pragma warning (pop) + +#define TestThrowOnFailure(hr, s) if (FAILED(hr)) { throw gcnew Microsoft::Tools::WindowsInstallerXml::Test::Bootstrapper::BurnTestException(hr, gcnew System::String(s)); } +#define TestThrowOnFailure1(hr, s, p) if (FAILED(hr)) { throw gcnew Microsoft::Tools::WindowsInstallerXml::Test::Bootstrapper::BurnTestException(hr, gcnew System::String(__TestThrowOnFailure_Format(s, p))); } +#define TestThrowOnFailure2(hr, s, p1, p2) if (FAILED(hr)) { throw gcnew Microsoft::Tools::WindowsInstallerXml::Test::Bootstrapper::BurnTestException(hr, gcnew System::String(__TestThrowOnFailure_Format(s, p1, p2))); } diff --git a/src/burn/test/BurnUnitTest/BurnTestFixture.h b/src/burn/test/BurnUnitTest/BurnTestFixture.h new file mode 100644 index 00000000..103972ef --- /dev/null +++ b/src/burn/test/BurnUnitTest/BurnTestFixture.h @@ -0,0 +1,75 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace WixBuildTools::TestSupport; + + public ref class BurnTestFixture : IDisposable + { + public: + BurnTestFixture() + { + HRESULT hr = XmlInitialize(); + TestThrowOnFailure(hr, L"Failed to initialize XML support."); + + hr = RegInitialize(); + TestThrowOnFailure(hr, L"Failed to initialize Regutil."); + + hr = CrypInitialize(); + TestThrowOnFailure(hr, L"Failed to initialize Cryputil."); + + PlatformInitialize(); + + this->testDirectory = WixBuildTools::TestSupport::TestData::Get(); + + LogInitialize(::GetModuleHandleW(NULL)); + + LogSetLevel(REPORT_DEBUG, FALSE); + + hr = LogOpen(NULL, L"BurnUnitTest", NULL, L"txt", FALSE, FALSE, NULL); + TestThrowOnFailure(hr, L"Failed to open log."); + } + + ~BurnTestFixture() + { + CrypUninitialize(); + XmlUninitialize(); + RegUninitialize(); + LogUninitialize(FALSE); + } + + property String^ DataDirectory + { + String^ get() + { + return this->testDirectory; + } + } + + property String^ TestDirectory + { + String^ get() + { + return this->testDirectory; + } + } + + private: + String^ testDirectory; + }; +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.h b/src/burn/test/BurnUnitTest/BurnUnitTest.h new file mode 100644 index 00000000..ed1d2956 --- /dev/null +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.h @@ -0,0 +1,48 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace Xunit; + + [CollectionDefinition("Burn")] + public ref class BurnCollectionDefinition : ICollectionFixture + { + + }; + + [Collection("Burn")] + public ref class BurnUnitTest + { + public: + BurnUnitTest(BurnTestFixture^ fixture) + { + this->testContext = fixture; + } + + property BurnTestFixture^ TestContext + { + BurnTestFixture^ get() + { + return this->testContext; + } + } + + private: + BurnTestFixture^ testContext; + }; +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.rc b/src/burn/test/BurnUnitTest/BurnUnitTest.rc new file mode 100644 index 00000000..3a815db2 --- /dev/null +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.rc @@ -0,0 +1,6 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#define VER_APP +#define VER_ORIGINAL_FILENAME "BurnUnitTest.dll" +#define VER_INTERNAL_NAME "setup" +#define VER_FILE_DESCRIPTION "WiX Toolset Bootstrapper unit tests" diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj new file mode 100644 index 00000000..33c8ed6c --- /dev/null +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj @@ -0,0 +1,109 @@ + + + + + + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + + + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} + {9D1F1BA3-9393-4833-87A3-D5F1FC08EF67} + UnitTest + ManagedCProj + DynamicLibrary + Unicode + true + false + + + + + + + $(ProjectDir)..\..\..\..\balutil\src\WixToolset.BootstrapperCore.Native\inc + $(ProjectAdditionalIncludeDirectories);..\..\engine + cabinet.lib;crypt32.lib;msi.lib;rpcrt4.lib;shlwapi.lib;wininet.lib + + + + + + + + + + + Create + + 4564;4691 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\..\..\packages\WixBuildTools.TestSupport.4.0.50\lib\net472\WixBuildTools.TestSupport.dll + + + ..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.50\lib\net472\WixBuildTools.TestSupport.Native.dll + + + + + {8119537D-E1D9-6591-D51A-49770A2F9C37} + + + + + + + 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/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters new file mode 100644 index 00000000..f9461f53 --- /dev/null +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters @@ -0,0 +1,80 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;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 + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/burn/test/BurnUnitTest/CacheTest.cpp b/src/burn/test/BurnUnitTest/CacheTest.cpp new file mode 100644 index 00000000..d0cc237f --- /dev/null +++ b/src/burn/test/BurnUnitTest/CacheTest.cpp @@ -0,0 +1,119 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +static HRESULT CALLBACK CacheTestEventRoutine( + __in BURN_CACHE_MESSAGE* pMessage, + __in LPVOID pvContext + ); + +static DWORD CALLBACK CacheTestProgressRoutine( + __in LARGE_INTEGER TotalFileSize, + __in LARGE_INTEGER TotalBytesTransferred, + __in LARGE_INTEGER StreamSize, + __in LARGE_INTEGER StreamBytesTransferred, + __in DWORD dwStreamNumber, + __in DWORD dwCallbackReason, + __in HANDLE hSourceFile, + __in HANDLE hDestinationFile, + __in_opt LPVOID lpData + ); + +typedef struct _CACHE_TEST_CONTEXT +{ +} CACHE_TEST_CONTEXT; + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace System::IO; + using namespace Xunit; + + public ref class CacheTest : BurnUnitTest + { + public: + CacheTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture) + { + } + + [Fact] + void CacheSignatureTest() + { + HRESULT hr = S_OK; + BURN_PACKAGE package = { }; + BURN_PAYLOAD payload = { }; + LPWSTR sczPayloadPath = NULL; + BYTE* pb = NULL; + DWORD cb = NULL; + CACHE_TEST_CONTEXT context = { }; + + try + { + pin_ptr dataDirectory = PtrToStringChars(this->TestContext->TestDirectory); + hr = PathConcat(dataDirectory, L"TestData\\CacheTest\\CacheSignatureTest.File", &sczPayloadPath); + Assert::True(S_OK == hr, "Failed to get path to test file."); + Assert::True(FileExistsEx(sczPayloadPath, NULL), "Test file does not exist."); + + hr = StrAllocHexDecode(L"25e61cd83485062b70713aebddd3fe4992826cb121466fddc8de3eacb1e42f39d4bdd8455d95eec8c9529ced4c0296ab861931fe2c86df2f2b4e8d259a6d9223", &pb, &cb); + Assert::Equal(S_OK, hr); + + package.fPerMachine = FALSE; + package.sczCacheId = L"Bootstrapper.CacheTest.CacheSignatureTest"; + payload.sczKey = L"CacheSignatureTest.PayloadKey"; + payload.sczFilePath = L"CacheSignatureTest.File"; + payload.pbHash = pb; + payload.cbHash = cb; + + hr = CacheCompletePayload(package.fPerMachine, &payload, package.sczCacheId, sczPayloadPath, FALSE, CacheTestEventRoutine, CacheTestProgressRoutine, &context); + Assert::Equal(S_OK, hr); + } + finally + { + ReleaseMem(pb); + ReleaseStr(sczPayloadPath); + + String^ filePath = Path::Combine(Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData), "Package Cache\\Bootstrapper.CacheTest.CacheSignatureTest\\CacheSignatureTest.File"); + if (File::Exists(filePath)) + { + File::SetAttributes(filePath, FileAttributes::Normal); + File::Delete(filePath); + } + } + } + }; +} +} +} +} +} + +static HRESULT CALLBACK CacheTestEventRoutine( + __in BURN_CACHE_MESSAGE* /*pMessage*/, + __in LPVOID /*pvContext*/ + ) +{ + return S_OK; +} + +static DWORD CALLBACK CacheTestProgressRoutine( + __in LARGE_INTEGER /*TotalFileSize*/, + __in LARGE_INTEGER /*TotalBytesTransferred*/, + __in LARGE_INTEGER /*StreamSize*/, + __in LARGE_INTEGER /*StreamBytesTransferred*/, + __in DWORD /*dwStreamNumber*/, + __in DWORD /*dwCallbackReason*/, + __in HANDLE /*hSourceFile*/, + __in HANDLE /*hDestinationFile*/, + __in_opt LPVOID /*lpData*/ + ) +{ + return PROGRESS_QUIET; +} diff --git a/src/burn/test/BurnUnitTest/ElevationTest.cpp b/src/burn/test/BurnUnitTest/ElevationTest.cpp new file mode 100644 index 00000000..3d144128 --- /dev/null +++ b/src/burn/test/BurnUnitTest/ElevationTest.cpp @@ -0,0 +1,221 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +const DWORD TEST_CHILD_SENT_MESSAGE_ID = 0xFFFE; +const DWORD TEST_PARENT_SENT_MESSAGE_ID = 0xFFFF; +const HRESULT S_TEST_SUCCEEDED = 0x3133; +const char TEST_MESSAGE_DATA[] = "{94949868-7EAE-4ac5-BEAC-AFCA2821DE01}"; + + +static BOOL STDAPICALLTYPE ElevateTest_ShellExecuteExW( + __inout LPSHELLEXECUTEINFOW lpExecInfo + ); +static DWORD CALLBACK ElevateTest_ThreadProc( + __in LPVOID lpThreadParameter + ); +static HRESULT ProcessParentMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); +static HRESULT ProcessChildMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ); + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace System::IO; + using namespace System::Threading; + using namespace Xunit; + + public ref class ElevationTest : BurnUnitTest + { + public: + ElevationTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture) + { + } + + [Fact] + void ElevateTest() + { + HRESULT hr = S_OK; + BURN_PIPE_CONNECTION connection = { }; + HANDLE hEvent = NULL; + DWORD dwResult = S_OK; + try + { + ShelFunctionOverride(ElevateTest_ShellExecuteExW); + + PipeConnectionInitialize(&connection); + + // + // per-user side setup + // + hr = PipeCreateNameAndSecret(&connection.sczName, &connection.sczSecret); + TestThrowOnFailure(hr, L"Failed to create connection name and secret."); + + hr = PipeCreatePipes(&connection, TRUE, &hEvent); + TestThrowOnFailure(hr, L"Failed to create pipes."); + + hr = PipeLaunchChildProcess(L"tests\\ignore\\this\\path\\to\\burn.exe", &connection, TRUE, NULL); + TestThrowOnFailure(hr, L"Failed to create elevated process."); + + hr = PipeWaitForChildConnect(&connection); + TestThrowOnFailure(hr, L"Failed to wait for child process to connect."); + + // post execute message + hr = PipeSendMessage(connection.hPipe, TEST_PARENT_SENT_MESSAGE_ID, NULL, 0, ProcessParentMessages, NULL, &dwResult); + TestThrowOnFailure(hr, "Failed to post execute message to per-machine process."); + + // + // initiate termination + // + hr = PipeTerminateChildProcess(&connection, 666, FALSE); + TestThrowOnFailure(hr, L"Failed to terminate elevated process."); + + // check flags + Assert::Equal(S_TEST_SUCCEEDED, (HRESULT)dwResult); + } + finally + { + PipeConnectionUninitialize(&connection); + ReleaseHandle(hEvent); + } + } + }; +} +} +} +} +} + + +static BOOL STDAPICALLTYPE ElevateTest_ShellExecuteExW( + __inout LPSHELLEXECUTEINFOW lpExecInfo + ) +{ + HRESULT hr = S_OK; + LPWSTR scz = NULL; + + hr = StrAllocString(&scz, lpExecInfo->lpParameters, 0); + ExitOnFailure(hr, "Failed to copy arguments."); + + // Pretend this thread is the elevated process. + lpExecInfo->hProcess = ::CreateThread(NULL, 0, ElevateTest_ThreadProc, scz, 0, NULL); + ExitOnNullWithLastError(lpExecInfo->hProcess, hr, "Failed to create thread."); + scz = NULL; + +LExit: + ReleaseStr(scz); + + return SUCCEEDED(hr); +} + +static DWORD CALLBACK ElevateTest_ThreadProc( + __in LPVOID lpThreadParameter + ) +{ + HRESULT hr = S_OK; + LPWSTR sczArguments = (LPWSTR)lpThreadParameter; + BURN_PIPE_CONNECTION connection = { }; + BURN_PIPE_RESULT result = { }; + + PipeConnectionInitialize(&connection); + + StrAlloc(&connection.sczName, MAX_PATH); + StrAlloc(&connection.sczSecret, MAX_PATH); + + // parse command line arguments + if (3 != swscanf_s(sczArguments, L"-q -burn.elevated %s %s %u", connection.sczName, MAX_PATH, connection.sczSecret, MAX_PATH, &connection.dwProcessId)) + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Failed to parse argument string."); + } + + // set up connection with per-user process + hr = PipeChildConnect(&connection, TRUE); + ExitOnFailure(hr, "Failed to connect to per-user process."); + + // pump messages + hr = PipePumpMessages(connection.hPipe, ProcessChildMessages, static_cast(connection.hPipe), &result); + ExitOnFailure(hr, "Failed while pumping messages in child 'process'."); + +LExit: + PipeConnectionUninitialize(&connection); + ReleaseStr(sczArguments); + + return FAILED(hr) ? (DWORD)hr : result.dwResult; +} + +static HRESULT ProcessParentMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID /*pvContext*/, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + HRESULT hrResult = E_INVALIDDATA; + + // Process the message. + switch (pMsg->dwMessage) + { + case TEST_CHILD_SENT_MESSAGE_ID: + if (sizeof(TEST_MESSAGE_DATA) == pMsg->cbData && 0 == memcmp(TEST_MESSAGE_DATA, pMsg->pvData, sizeof(TEST_MESSAGE_DATA))) + { + hrResult = S_TEST_SUCCEEDED; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Unexpected elevated message sent to parent process, msg: %u", pMsg->dwMessage); + } + + *pdwResult = static_cast(hrResult); + +LExit: + return hr; +} + +static HRESULT ProcessChildMessages( + __in BURN_PIPE_MESSAGE* pMsg, + __in_opt LPVOID pvContext, + __out DWORD* pdwResult + ) +{ + HRESULT hr = S_OK; + HANDLE hPipe = static_cast(pvContext); + DWORD dwResult = 0; + + // Process the message. + switch (pMsg->dwMessage) + { + case TEST_PARENT_SENT_MESSAGE_ID: + // send test message + hr = PipeSendMessage(hPipe, TEST_CHILD_SENT_MESSAGE_ID, (LPVOID)TEST_MESSAGE_DATA, sizeof(TEST_MESSAGE_DATA), NULL, NULL, &dwResult); + ExitOnFailure(hr, "Failed to send message to per-machine process."); + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Unexpected elevated message sent to child process, msg: %u", pMsg->dwMessage); + } + + *pdwResult = dwResult; + +LExit: + return hr; +} diff --git a/src/burn/test/BurnUnitTest/ManifestHelpers.cpp b/src/burn/test/BurnUnitTest/ManifestHelpers.cpp new file mode 100644 index 00000000..96d5fab4 --- /dev/null +++ b/src/burn/test/BurnUnitTest/ManifestHelpers.cpp @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +using namespace System; +using namespace Xunit; + + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + void LoadBundleXmlHelper(LPCWSTR wzDocument, IXMLDOMElement** ppixeBundle) + { + HRESULT hr = S_OK; + IXMLDOMDocument* pixdDocument = NULL; + try + { + hr = XmlLoadDocument(wzDocument, &pixdDocument); + TestThrowOnFailure(hr, L"Failed to load XML document."); + + hr = pixdDocument->get_documentElement(ppixeBundle); + TestThrowOnFailure(hr, L"Failed to get bundle element."); + } + finally + { + ReleaseObject(pixdDocument); + } + } +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/ManifestHelpers.h b/src/burn/test/BurnUnitTest/ManifestHelpers.h new file mode 100644 index 00000000..e3e57555 --- /dev/null +++ b/src/burn/test/BurnUnitTest/ManifestHelpers.h @@ -0,0 +1,24 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + + +void LoadBundleXmlHelper(LPCWSTR wzDocument, IXMLDOMElement** ppixeBundle); + + +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/ManifestTest.cpp b/src/burn/test/BurnUnitTest/ManifestTest.cpp new file mode 100644 index 00000000..963be156 --- /dev/null +++ b/src/burn/test/BurnUnitTest/ManifestTest.cpp @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace Xunit; + + public ref class ManifestTest : BurnUnitTest + { + public: + ManifestTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture) + { + } + + [Fact] + void ManifestLoadXmlTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + try + { + LPCSTR szDocument = + "" + " " + " " + " " + " " + " "; + + hr = VariableInitialize(&engineState.variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // load manifest from XML + hr = ManifestLoadXmlFromBuffer((BYTE*)szDocument, lstrlenA(szDocument), &engineState); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // check variable values + Assert::True(VariableExistsHelper(&engineState.variables, L"Variable1")); + } + finally + { + //CoreUninitialize(&engineState); + } + } + }; +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/PlanTest.cpp b/src/burn/test/BurnUnitTest/PlanTest.cpp new file mode 100644 index 00000000..a7c1d83c --- /dev/null +++ b/src/burn/test/BurnUnitTest/PlanTest.cpp @@ -0,0 +1,1473 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +static HRESULT WINAPI PlanTestBAProc( + __in BOOTSTRAPPER_APPLICATION_MESSAGE message, + __in const LPVOID pvArgs, + __inout LPVOID pvResults, + __in_opt LPVOID pvContext + ); + +static LPCWSTR wzMsiTransactionManifestFileName = L"MsiTransaction_BundleAv1_manifest.xml"; +static LPCWSTR wzSingleMsiManifestFileName = L"BasicFunctionality_BundleA_manifest.xml"; +static LPCWSTR wzSlipstreamManifestFileName = L"Slipstream_BundleA_manifest.xml"; + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace Xunit; + + public ref class PlanTest : BurnUnitTest + { + public: + PlanTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture) + { + } + + [Fact] + void MsiTransactionInstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzMsiTransactionManifestFileName, pEngineState); + DetectPackagesAsAbsent(pEngineState); + DetectUpgradeBundle(pEngineState, L"{FD9920AD-DBCA-4C6C-8CD5-B47431CE8D21}", L"1.0.0.0"); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 9); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageB"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 14); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageC"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(107082ull, pPlan->qwEstimatedSize); + Assert::Equal(506145ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteWaitSyncpoint(pPlan, fRollback, dwIndex++, pPlan->rgCacheActions[2].syncpoint.hEvent); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"rbaOCA08D8ky7uBOK71_6FWz1K3TuQ", TRUE, TRUE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteBeginMsiTransaction(pPlan, fRollback, dwIndex++, L"rbaOCA08D8ky7uBOK71_6FWz1K3TuQ"); + ValidateExecuteWaitSyncpoint(pPlan, fRollback, dwIndex++, pPlan->rgCacheActions[5].syncpoint.hEvent); + dwExecuteCheckpointId += 1; // cache checkpoints + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageB", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageB", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageB", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteWaitSyncpoint(pPlan, fRollback, dwIndex++, pPlan->rgCacheActions[8].syncpoint.hEvent); + dwExecuteCheckpointId += 1; // cache checkpoints + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageC", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageC", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageC", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCommitMsiTransaction(pPlan, fRollback, dwIndex++, L"rbaOCA08D8ky7uBOK71_6FWz1K3TuQ"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"{FD9920AD-DBCA-4C6C-8CD5-B47431CE8D21}", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, NULL); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"rbaOCA08D8ky7uBOK71_6FWz1K3TuQ", TRUE, TRUE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"PackageB"); + dwExecuteCheckpointId += 1; // cache checkpoints + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageB", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageB", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"PackageC"); + dwExecuteCheckpointId += 1; // cache checkpoints + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageC", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageC", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"{FD9920AD-DBCA-4C6C-8CD5-B47431CE8D21}", BOOTSTRAPPER_ACTION_STATE_INSTALL, NULL); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(4ul, pPlan->cExecutePackagesTotal); + Assert::Equal(7ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(3ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"PackageB", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[2], L"PackageC", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + } + + [Fact] + void MsiTransactionUninstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzMsiTransactionManifestFileName, pEngineState); + DetectPackagesAsPresentAndCached(pEngineState); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNINSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_UNINSTALL, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(0ull, pPlan->qwEstimatedSize); + Assert::Equal(0ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"rbaOCA08D8ky7uBOK71_6FWz1K3TuQ", TRUE, TRUE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteBeginMsiTransaction(pPlan, fRollback, dwIndex++, L"rbaOCA08D8ky7uBOK71_6FWz1K3TuQ"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageC", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageC", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageC", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageB", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageB", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageB", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCommitMsiTransaction(pPlan, fRollback, dwIndex++, L"rbaOCA08D8ky7uBOK71_6FWz1K3TuQ"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"rbaOCA08D8ky7uBOK71_6FWz1K3TuQ", TRUE, TRUE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageC", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageC", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageB", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageB", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(3ul, pPlan->cExecutePackagesTotal); + Assert::Equal(3ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"PackageC"); + ValidateCleanAction(pPlan, dwIndex++, L"PackageB"); + ValidateCleanAction(pPlan, dwIndex++, L"PackageA"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{E6469F05-BDC8-4EB8-B218-67412543EFAA}", NULL); + ValidatePlannedProvider(pPlan, uIndex++, L"{A497C5E5-C78B-4F0B-BF72-B33E1DB1C4B8}", NULL); + ValidatePlannedProvider(pPlan, uIndex++, L"{D1D01094-23CE-4AF0-84B6-4A1A133F21D3}", NULL); + ValidatePlannedProvider(pPlan, uIndex++, L"{01E6B748-7B95-4BA9-976D-B6F35076CEF4}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(3ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"PackageB", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[2], L"PackageC", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + } + + [Fact] + void RelatedBundleMissingFromCacheTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSingleMsiManifestFileName, pEngineState); + DetectAttachedContainerAsAttached(pEngineState); + DetectPackagesAsAbsent(pEngineState); + BURN_RELATED_BUNDLE* pRelatedBundle = DetectUpgradeBundle(pEngineState, L"{FD9920AD-DBCA-4C6C-8CD5-B47431CE8D21}", L"0.9.0.0"); + pRelatedBundle->fPlannable = FALSE; + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(35694ull, pPlan->qwEstimatedSize); + Assert::Equal(168715ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteWaitSyncpoint(pPlan, fRollback, dwIndex++, pPlan->rgCacheActions[2].syncpoint.hEvent); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(1ul, pPlan->cExecutePackagesTotal); + Assert::Equal(2ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(1ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + } + + [Fact] + void SingleMsiCacheTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSingleMsiManifestFileName, pEngineState); + DetectAttachedContainerAsAttached(pEngineState); + DetectPackagesAsAbsent(pEngineState); + DetectUpgradeBundle(pEngineState, L"{FD9920AD-DBCA-4C6C-8CD5-B47431CE8D21}", L"0.9.0.0"); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_CACHE); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_CACHE, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(33743ull, pPlan->qwEstimatedSize); + Assert::Equal(168715ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteWaitSyncpoint(pPlan, fRollback, dwIndex++, pPlan->rgCacheActions[2].syncpoint.hEvent); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(0ul, pPlan->cExecutePackagesTotal); + Assert::Equal(1ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(1ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + } + + [Fact] + void SingleMsiInstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSingleMsiManifestFileName, pEngineState); + DetectAttachedContainerAsAttached(pEngineState); + DetectPackagesAsAbsent(pEngineState); + DetectUpgradeBundle(pEngineState, L"{FD9920AD-DBCA-4C6C-8CD5-B47431CE8D21}", L"0.9.0.0"); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(35694ull, pPlan->qwEstimatedSize); + Assert::Equal(168715ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteWaitSyncpoint(pPlan, fRollback, dwIndex++, pPlan->rgCacheActions[2].syncpoint.hEvent); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"{FD9920AD-DBCA-4C6C-8CD5-B47431CE8D21}", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, NULL); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"{FD9920AD-DBCA-4C6C-8CD5-B47431CE8D21}", BOOTSTRAPPER_ACTION_STATE_INSTALL, NULL); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(2ul, pPlan->cExecutePackagesTotal); + Assert::Equal(3ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(1ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + } + + [Fact] + void SingleMsiInstalledWithNoInstalledPackagesModifyTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSingleMsiManifestFileName, pEngineState); + DetectPackagesAsAbsent(pEngineState); + + pEngineState->registration.fInstalled = TRUE; + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_MODIFY); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_MODIFY, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(0ull, pPlan->qwEstimatedSize); + Assert::Equal(0ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(0ul, pPlan->cExecutePackagesTotal); + Assert::Equal(0ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"PackageA"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(1ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + } + + [Fact] + void SingleMsiUninstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSingleMsiManifestFileName, pEngineState); + DetectPackagesAsPresentAndCached(pEngineState); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNINSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_UNINSTALL, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(0ull, pPlan->qwEstimatedSize); + Assert::Equal(0ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(1ul, pPlan->cExecutePackagesTotal); + Assert::Equal(1ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"PackageA"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", NULL); + ValidatePlannedProvider(pPlan, uIndex++, L"{64633047-D172-4BBB-B202-64337D15C952}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(1ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + } + + [Fact] + void SingleMsiUninstallTestFromUpgradeBundleWithSameExactPackage() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSingleMsiManifestFileName, pEngineState); + DetectAsRelatedUpgradeBundle(&engineState, L"{02940F3E-C83E-452D-BFCF-C943777ACEAE}", L"2.0.0.0"); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNINSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_UNINSTALL, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(0ull, pPlan->qwEstimatedSize); + Assert::Equal(0ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(0ul, pPlan->cExecutePackagesTotal); + Assert::Equal(0ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{A6F0CBF7-1578-450C-B9D7-9CF2EEC40002}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(1ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_IGNORED, BURN_PACKAGE_REGISTRATION_STATE_IGNORED); + } + + [Fact] + void SlipstreamInstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSlipstreamManifestFileName, pEngineState); + DetectPermanentPackagesAsPresentAndCached(pEngineState); + PlanTestDetectPatchInitialize(pEngineState); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PatchA"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 2); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 2); + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(3055111ull, pPlan->qwEstimatedSize); + Assert::Equal(212992ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 3; + BURN_EXECUTE_ACTION* pExecuteAction = NULL; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteWaitSyncpoint(pPlan, fRollback, dwIndex++, pPlan->rgCacheActions[5].syncpoint.hEvent); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteWaitSyncpoint(pPlan, fRollback, dwIndex++, pPlan->rgCacheActions[2].syncpoint.hEvent); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PatchA", BURN_DEPENDENCY_ACTION_REGISTER); + pExecuteAction = ValidateDeletedExecuteMspTarget(pPlan, fRollback, dwIndex++, L"PatchA", BOOTSTRAPPER_ACTION_STATE_INSTALL, L"{5FF7F534-3FFC-41E0-80CD-E6361E5E7B7B}", TRUE, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, TRUE); + ValidateExecuteMspTargetPatch(pExecuteAction, 0, L"PatchA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PatchA", L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 3; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"PackageA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"PatchA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PatchA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + pExecuteAction = ValidateDeletedExecuteMspTarget(pPlan, fRollback, dwIndex++, L"PatchA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, L"{5FF7F534-3FFC-41E0-80CD-E6361E5E7B7B}", TRUE, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, TRUE); + ValidateExecuteMspTargetPatch(pExecuteAction, 0, L"PatchA"); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PatchA", L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(2ul, pPlan->cExecutePackagesTotal); + Assert::Equal(4ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(3ul, pEngineState->packages.cPackages); + ValidatePermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"NetFx48Web"); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[2], L"PatchA", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + } + + [Fact] + void SlipstreamUninstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzSlipstreamManifestFileName, pEngineState); + DetectPackagesAsPresentAndCached(pEngineState); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNINSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_UNINSTALL, pPlan->action); + Assert::Equal(TRUE, pPlan->fPerMachine); + Assert::Equal(FALSE, pPlan->fDisableRollback); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(0ull, pPlan->qwEstimatedSize); + Assert::Equal(0ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 1; + BURN_EXECUTE_ACTION* pExecuteAction = NULL; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PatchA", L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PatchA", BURN_DEPENDENCY_ACTION_UNREGISTER); + pExecuteAction = ValidateDeletedExecuteMspTarget(pPlan, fRollback, dwIndex++, L"PatchA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, L"{5FF7F534-3FFC-41E0-80CD-E6361E5E7B7B}", TRUE, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, TRUE); + ValidateExecuteMspTargetPatch(pExecuteAction, 0, L"PatchA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_UNREGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL, BURN_MSI_PROPERTY_UNINSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundary(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PatchA", L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PatchA", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + pExecuteAction = ValidateDeletedExecuteMspTarget(pPlan, fRollback, dwIndex++, L"PatchA", BOOTSTRAPPER_ACTION_STATE_INSTALL, L"{5FF7F534-3FFC-41E0-80CD-E6361E5E7B7B}", TRUE, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, TRUE); + ValidateExecuteMspTargetPatch(pExecuteAction, 0, L"PatchA"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageDependency(pPlan, fRollback, dwIndex++, L"PackageA", L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecutePackageProvider(pPlan, fRollback, dwIndex++, L"PackageA", BURN_DEPENDENCY_ACTION_REGISTER); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteMsiPackage(pPlan, fRollback, dwIndex++, L"PackageA", BOOTSTRAPPER_ACTION_STATE_INSTALL, BURN_MSI_PROPERTY_INSTALL, INSTALLUILEVEL_NONE, FALSE, 0); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(2ul, pPlan->cExecutePackagesTotal); + Assert::Equal(2ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"PatchA"); + ValidateCleanAction(pPlan, dwIndex++, L"PackageA"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{22D1DDBA-284D-40A7-BD14-95EA07906F21}", NULL); + ValidatePlannedProvider(pPlan, uIndex++, L"{0A5113E3-06A5-4CE0-8E83-9EB42F6764A6}", NULL); + ValidatePlannedProvider(pPlan, uIndex++, L"{5FF7F534-3FFC-41E0-80CD-E6361E5E7B7B}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(3ul, pEngineState->packages.cPackages); + ValidatePermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"NetFx48Web"); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[2], L"PatchA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + } + + private: + // This doesn't initialize everything, just enough for CorePlan to work. + void InitializeEngineStateForCorePlan(LPCWSTR wzManifestFileName, BURN_ENGINE_STATE* pEngineState) + { + HRESULT hr = S_OK; + LPWSTR sczFilePath = NULL; + + ::InitializeCriticalSection(&pEngineState->userExperience.csEngineActive); + + hr = VariableInitialize(&pEngineState->variables); + NativeAssert::Succeeded(hr, "Failed to initialize variables."); + + try + { + pin_ptr dataDirectory = PtrToStringChars(this->TestContext->TestDirectory); + hr = PathConcat(dataDirectory, L"TestData\\PlanTest", &sczFilePath); + NativeAssert::Succeeded(hr, "Failed to get path to test file directory."); + hr = PathConcat(sczFilePath, wzManifestFileName, &sczFilePath); + NativeAssert::Succeeded(hr, "Failed to get path to test file."); + Assert::True(FileExistsEx(sczFilePath, NULL), "Test file does not exist."); + + hr = ManifestLoadXmlFromFile(sczFilePath, pEngineState); + NativeAssert::Succeeded(hr, "Failed to load manifest."); + } + finally + { + ReleaseStr(sczFilePath); + } + + hr = CoreInitializeConstants(pEngineState); + NativeAssert::Succeeded(hr, "Failed to initialize core constants"); + + pEngineState->userExperience.pfnBAProc = PlanTestBAProc; + } + + void PlanTestDetect(BURN_ENGINE_STATE* pEngineState) + { + HRESULT hr = S_OK; + BURN_REGISTRATION* pRegistration = &pEngineState->registration; + + DetectReset(pRegistration, &pEngineState->packages); + PlanReset(&pEngineState->plan, &pEngineState->containers, &pEngineState->packages, &pEngineState->layoutPayloads); + + hr = DepDependencyArrayAlloc(&pRegistration->rgIgnoredDependencies, &pRegistration->cIgnoredDependencies, pRegistration->sczProviderKey, NULL); + NativeAssert::Succeeded(hr, "Failed to add the bundle provider key to the list of dependencies to ignore."); + + pEngineState->userExperience.fEngineActive = TRUE; + pEngineState->fDetected = TRUE; + } + + void PlanTestDetectPatchInitialize(BURN_ENGINE_STATE* pEngineState) + { + HRESULT hr = MsiEngineDetectInitialize(&pEngineState->packages); + NativeAssert::Succeeded(hr, "MsiEngineDetectInitialize failed"); + + for (DWORD i = 0; i < pEngineState->packages.cPackages; ++i) + { + BURN_PACKAGE* pPackage = pEngineState->packages.rgPackages + i; + + if (BURN_PACKAGE_TYPE_MSP == pPackage->type) + { + for (DWORD j = 0; j < pPackage->Msp.cTargetProductCodes; ++j) + { + BURN_MSPTARGETPRODUCT* pTargetProduct = pPackage->Msp.rgTargetProducts + j; + + if (BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN == pTargetProduct->patchPackageState) + { + pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + } + } + } + } + } + + void DetectAttachedContainerAsAttached(BURN_ENGINE_STATE* pEngineState) + { + for (DWORD i = 0; i < pEngineState->containers.cContainers; ++i) + { + BURN_CONTAINER* pContainer = pEngineState->containers.rgContainers + i; + if (pContainer->fAttached) + { + pContainer->fActuallyAttached = TRUE; + } + } + } + + void DetectPackageAsAbsent(BURN_PACKAGE* pPackage) + { + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + if (pPackage->fCanAffectRegistration) + { + pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_ABSENT; + } + } + + void DetectPackageAsPresentAndCached(BURN_PACKAGE* pPackage) + { + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_PRESENT; + pPackage->fCached = TRUE; + if (pPackage->fCanAffectRegistration) + { + pPackage->cacheRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + pPackage->installRegistrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + } + + void DetectPackageDependent(BURN_PACKAGE* pPackage, LPCWSTR wzId) + { + HRESULT hr = S_OK; + + for (DWORD i = 0; i < pPackage->cDependencyProviders; ++i) + { + BURN_DEPENDENCY_PROVIDER* pProvider = pPackage->rgDependencyProviders + i; + + hr = DepDependencyArrayAlloc(&pProvider->rgDependents, &pProvider->cDependents, wzId, NULL); + NativeAssert::Succeeded(hr, "Failed to add package dependent"); + } + } + + void DetectPackagesAsAbsent(BURN_ENGINE_STATE* pEngineState) + { + PlanTestDetect(pEngineState); + + for (DWORD i = 0; i < pEngineState->packages.cPackages; ++i) + { + BURN_PACKAGE* pPackage = pEngineState->packages.rgPackages + i; + DetectPackageAsAbsent(pPackage); + } + } + + void DetectPackagesAsPresentAndCached(BURN_ENGINE_STATE* pEngineState) + { + PlanTestDetect(pEngineState); + + pEngineState->registration.fInstalled = TRUE; + + for (DWORD i = 0; i < pEngineState->packages.cPackages; ++i) + { + BURN_PACKAGE* pPackage = pEngineState->packages.rgPackages + i; + DetectPackageAsPresentAndCached(pPackage); + DetectPackageDependent(pPackage, pEngineState->registration.sczId); + + if (BURN_PACKAGE_TYPE_MSI == pPackage->type) + { + for (DWORD j = 0; j < pPackage->Msi.cSlipstreamMspPackages; ++j) + { + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_SUPERSEDED; + + BURN_PACKAGE* pMspPackage = pPackage->Msi.rgSlipstreamMsps[j].pMspPackage; + MspEngineAddDetectedTargetProduct(&pEngineState->packages, pMspPackage, j, pPackage->Msi.sczProductCode, pPackage->fPerMachine ? MSIINSTALLCONTEXT_MACHINE : MSIINSTALLCONTEXT_USERUNMANAGED); + + BURN_MSPTARGETPRODUCT* pTargetProduct = pMspPackage->Msp.rgTargetProducts + (pMspPackage->Msp.cTargetProductCodes - 1); + pTargetProduct->patchPackageState = BOOTSTRAPPER_PACKAGE_STATE_PRESENT; + pTargetProduct->registrationState = BURN_PACKAGE_REGISTRATION_STATE_PRESENT; + } + } + } + } + + void DetectPermanentPackagesAsPresentAndCached(BURN_ENGINE_STATE* pEngineState) + { + PlanTestDetect(pEngineState); + + pEngineState->registration.fInstalled = TRUE; + + for (DWORD i = 0; i < pEngineState->packages.cPackages; ++i) + { + BURN_PACKAGE* pPackage = pEngineState->packages.rgPackages + i; + if (pPackage->fUninstallable) + { + DetectPackageAsAbsent(pPackage); + } + else + { + DetectPackageAsPresentAndCached(pPackage); + DetectPackageDependent(pPackage, pEngineState->registration.sczId); + } + } + } + + BURN_RELATED_BUNDLE* DetectUpgradeBundle( + __in BURN_ENGINE_STATE* pEngineState, + __in LPCWSTR wzId, + __in LPCWSTR wzVersion + ) + { + HRESULT hr = S_OK; + BURN_RELATED_BUNDLES* pRelatedBundles = &pEngineState->registration.relatedBundles; + BURN_DEPENDENCY_PROVIDER dependencyProvider = { }; + + hr = StrAllocString(&dependencyProvider.sczKey, wzId, 0); + NativeAssert::Succeeded(hr, "Failed to copy provider key"); + + dependencyProvider.fImported = TRUE; + + hr = StrAllocString(&dependencyProvider.sczVersion, wzVersion, 0); + NativeAssert::Succeeded(hr, "Failed to copy version"); + + hr = MemEnsureArraySize(reinterpret_cast(&pRelatedBundles->rgRelatedBundles), pRelatedBundles->cRelatedBundles + 1, sizeof(BURN_RELATED_BUNDLE), 5); + NativeAssert::Succeeded(hr, "Failed to ensure there is space for related bundles."); + + BURN_RELATED_BUNDLE* pRelatedBundle = pRelatedBundles->rgRelatedBundles + pRelatedBundles->cRelatedBundles; + + hr = VerParseVersion(wzVersion, 0, FALSE, &pRelatedBundle->pVersion); + NativeAssert::Succeeded(hr, "Failed to parse pseudo bundle version: %ls", wzVersion); + + pRelatedBundle->fPlannable = TRUE; + pRelatedBundle->relationType = BOOTSTRAPPER_RELATION_UPGRADE; + + hr = PseudoBundleInitialize(0, &pRelatedBundle->package, TRUE, wzId, pRelatedBundle->relationType, BOOTSTRAPPER_PACKAGE_STATE_PRESENT, TRUE, NULL, NULL, NULL, 0, FALSE, L"-quiet", L"-repair -quiet", L"-uninstall -quiet", &dependencyProvider, NULL, 0); + NativeAssert::Succeeded(hr, "Failed to initialize related bundle to represent bundle: %ls", wzId); + + ++pRelatedBundles->cRelatedBundles; + + return pRelatedBundle; + } + + void DetectAsRelatedUpgradeBundle( + __in BURN_ENGINE_STATE* pEngineState, + __in LPCWSTR wzId, + __in LPCWSTR wzVersion + ) + { + HRESULT hr = StrAllocString(&pEngineState->registration.sczAncestors, wzId, 0); + NativeAssert::Succeeded(hr, "Failed to set registration's ancestors"); + + pEngineState->command.relationType = BOOTSTRAPPER_RELATION_UPGRADE; + + DetectPackagesAsPresentAndCached(pEngineState); + DetectUpgradeBundle(pEngineState, wzId, wzVersion); + + for (DWORD i = 0; i < pEngineState->packages.cPackages; ++i) + { + BURN_PACKAGE* pPackage = pEngineState->packages.rgPackages + i; + DetectPackageDependent(pPackage, wzId); + } + } + + void ValidateCacheContainer( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzContainerId + ) + { + BURN_CACHE_ACTION* pAction = ValidateCacheActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_CACHE_ACTION_TYPE_CONTAINER, pAction->type); + NativeAssert::StringEqual(wzContainerId, pAction->container.pContainer->sczId); + } + + BURN_CACHE_ACTION* ValidateCacheActionExists(BURN_PLAN* pPlan, BOOL fRollback, DWORD dwIndex) + { + Assert::InRange(dwIndex + 1ul, 1ul, (fRollback ? pPlan->cRollbackCacheActions : pPlan->cCacheActions)); + return (fRollback ? pPlan->rgRollbackCacheActions : pPlan->rgCacheActions) + dwIndex; + } + + void ValidateCacheCheckpoint( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in DWORD dwId + ) + { + BURN_CACHE_ACTION* pAction = ValidateCacheActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_CACHE_ACTION_TYPE_CHECKPOINT, pAction->type); + Assert::Equal(dwId, pAction->checkpoint.dwId); + } + + DWORD ValidateCachePackage( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzPackageId + ) + { + BURN_CACHE_ACTION* pAction = ValidateCacheActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_CACHE_ACTION_TYPE_PACKAGE, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->package.pPackage->sczId); + return dwIndex + 1; + } + + void ValidateCacheRollbackPackage( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzPackageId + ) + { + BURN_CACHE_ACTION* pAction = ValidateCacheActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_CACHE_ACTION_TYPE_ROLLBACK_PACKAGE, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->rollbackPackage.pPackage->sczId); + } + + void ValidateCacheSignalSyncpoint( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex + ) + { + BURN_CACHE_ACTION* pAction = ValidateCacheActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_CACHE_ACTION_TYPE_SIGNAL_SYNCPOINT, pAction->type); + Assert::NotEqual((DWORD_PTR)NULL, (DWORD_PTR)pAction->syncpoint.hEvent); + } + + void ValidateCleanAction( + __in BURN_PLAN* pPlan, + __in DWORD dwIndex, + __in LPCWSTR wzPackageId + ) + { + Assert::InRange(dwIndex + 1ul, 1ul, pPlan->cCleanActions); + + BURN_CLEAN_ACTION* pCleanAction = pPlan->rgCleanActions + dwIndex; + Assert::NotEqual((DWORD_PTR)0, (DWORD_PTR)pCleanAction->pPackage); + NativeAssert::StringEqual(wzPackageId, pCleanAction->pPackage->sczId); + } + + BURN_EXECUTE_ACTION* ValidateExecuteActionExists(BURN_PLAN* pPlan, BOOL fRollback, DWORD dwIndex) + { + Assert::InRange(dwIndex + 1ul, 1ul, (fRollback ? pPlan->cRollbackActions : pPlan->cExecuteActions)); + return (fRollback ? pPlan->rgRollbackActions : pPlan->rgExecuteActions) + dwIndex; + } + + void ValidateExecuteBeginMsiTransaction( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzRollbackBoundaryId + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_BEGIN_MSI_TRANSACTION, pAction->type); + NativeAssert::StringEqual(wzRollbackBoundaryId, pAction->msiTransaction.pRollbackBoundary->sczId); + Assert::Equal(FALSE, pAction->fDeleted); + } + + void ValidateExecuteCheckpoint( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in DWORD dwId + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_CHECKPOINT, pAction->type); + Assert::Equal(dwId, pAction->checkpoint.dwId); + Assert::Equal(FALSE, pAction->fDeleted); + } + + void ValidateExecuteCommitMsiTransaction( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzRollbackBoundaryId + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_COMMIT_MSI_TRANSACTION, pAction->type); + NativeAssert::StringEqual(wzRollbackBoundaryId, pAction->msiTransaction.pRollbackBoundary->sczId); + Assert::Equal(FALSE, pAction->fDeleted); + } + + void ValidateExecuteExePackage( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzPackageId, + __in BOOTSTRAPPER_ACTION_STATE action, + __in LPCWSTR wzIgnoreDependencies + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_EXE_PACKAGE, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->exePackage.pPackage->sczId); + Assert::Equal(action, pAction->exePackage.action); + NativeAssert::StringEqual(wzIgnoreDependencies, pAction->exePackage.sczIgnoreDependencies); + Assert::Equal(FALSE, pAction->fDeleted); + } + + void ValidateExecuteMsiPackage( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in_z LPCWSTR wzPackageId, + __in BOOTSTRAPPER_ACTION_STATE action, + __in BURN_MSI_PROPERTY actionMsiProperty, + __in DWORD uiLevel, + __in BOOL fDisableExternalUiHandler, + __in DWORD dwLoggingAttributes + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_MSI_PACKAGE, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->msiPackage.pPackage->sczId); + Assert::Equal(action, pAction->msiPackage.action); + Assert::Equal(actionMsiProperty, pAction->msiPackage.actionMsiProperty); + Assert::Equal(uiLevel, pAction->msiPackage.uiLevel); + Assert::Equal(fDisableExternalUiHandler, pAction->msiPackage.fDisableExternalUiHandler); + NativeAssert::NotNull(pAction->msiPackage.sczLogPath); + Assert::Equal(dwLoggingAttributes, pAction->msiPackage.dwLoggingAttributes); + Assert::Equal(FALSE, pAction->fDeleted); + } + + BURN_EXECUTE_ACTION* ValidateDeletedExecuteMspTarget( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in_z LPCWSTR wzPackageId, + __in BOOTSTRAPPER_ACTION_STATE action, + __in_z LPCWSTR wzTargetProductCode, + __in BOOL fPerMachineTarget, + __in BURN_MSI_PROPERTY actionMsiProperty, + __in DWORD uiLevel, + __in BOOL fDisableExternalUiHandler, + __in BOOL fDeleted + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_MSP_TARGET, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->mspTarget.pPackage->sczId); + Assert::Equal(action, pAction->mspTarget.action); + NativeAssert::StringEqual(wzTargetProductCode, pAction->mspTarget.sczTargetProductCode); + Assert::Equal(fPerMachineTarget, pAction->mspTarget.fPerMachineTarget); + Assert::Equal(actionMsiProperty, pAction->mspTarget.actionMsiProperty); + Assert::Equal(uiLevel, pAction->mspTarget.uiLevel); + Assert::Equal(fDisableExternalUiHandler, pAction->mspTarget.fDisableExternalUiHandler); + NativeAssert::NotNull(pAction->mspTarget.sczLogPath); + Assert::Equal(fDeleted, pAction->fDeleted); + return pAction; + } + + void ValidateExecuteMspTargetPatch( + __in BURN_EXECUTE_ACTION* pAction, + __in DWORD dwIndex, + __in_z LPCWSTR wzPackageId + ) + { + Assert::InRange(dwIndex + 1ul, 1ul, pAction->mspTarget.cOrderedPatches); + BURN_ORDERED_PATCHES* pOrderedPatch = pAction->mspTarget.rgOrderedPatches + dwIndex; + NativeAssert::StringEqual(wzPackageId, pOrderedPatch->pPackage->sczId); + } + + void ValidateExecutePackageDependency( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzPackageId, + __in LPCWSTR wzBundleProviderKey, + __in BURN_DEPENDENCY_ACTION action + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_PACKAGE_DEPENDENCY, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->packageDependency.pPackage->sczId); + NativeAssert::StringEqual(wzBundleProviderKey, pAction->packageDependency.sczBundleProviderKey); + Assert::Equal(action, pAction->packageDependency.action); + Assert::Equal(FALSE, pAction->fDeleted); + } + + void ValidateExecutePackageProvider( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzPackageId, + __in BURN_DEPENDENCY_ACTION action + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_PACKAGE_PROVIDER, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->packageProvider.pPackage->sczId); + Assert::Equal(action, pAction->packageProvider.action); + Assert::Equal(FALSE, pAction->fDeleted); + } + + void ValidateExecuteRollbackBoundary( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzId, + __in BOOL fVital, + __in BOOL fTransaction + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_ROLLBACK_BOUNDARY, pAction->type); + NativeAssert::StringEqual(wzId, pAction->rollbackBoundary.pRollbackBoundary->sczId); + Assert::Equal(fVital, pAction->rollbackBoundary.pRollbackBoundary->fVital); + Assert::Equal(fTransaction, pAction->rollbackBoundary.pRollbackBoundary->fTransaction); + Assert::Equal(FALSE, pAction->fDeleted); + } + + void ValidateExecuteUncachePackage( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in LPCWSTR wzPackageId + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_UNCACHE_PACKAGE, pAction->type); + NativeAssert::StringEqual(wzPackageId, pAction->uncachePackage.pPackage->sczId); + Assert::Equal(FALSE, pAction->fDeleted); + } + + void ValidateExecuteWaitSyncpoint( + __in BURN_PLAN* pPlan, + __in BOOL fRollback, + __in DWORD dwIndex, + __in HANDLE hEvent + ) + { + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex); + Assert::Equal(BURN_EXECUTE_ACTION_TYPE_WAIT_SYNCPOINT, pAction->type); + Assert::Equal((DWORD_PTR)hEvent, (DWORD_PTR)pAction->syncpoint.hEvent); + Assert::Equal(FALSE, pAction->fDeleted); + } + + void ValidateNonPermanentPackageExpectedStates( + __in BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzPackageId, + __in BURN_PACKAGE_REGISTRATION_STATE expectedCacheState, + __in BURN_PACKAGE_REGISTRATION_STATE expectedInstallState + ) + { + NativeAssert::StringEqual(wzPackageId, pPackage->sczId); + Assert::Equal(TRUE, pPackage->fCanAffectRegistration); + Assert::Equal(expectedCacheState, pPackage->expectedCacheRegistrationState); + Assert::Equal(expectedInstallState, pPackage->expectedInstallRegistrationState); + } + + void ValidatePermanentPackageExpectedStates( + __in BURN_PACKAGE* pPackage, + __in_z LPCWSTR wzPackageId + ) + { + NativeAssert::StringEqual(wzPackageId, pPackage->sczId); + Assert::Equal(FALSE, pPackage->fCanAffectRegistration); + Assert::Equal(BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN, pPackage->expectedCacheRegistrationState); + Assert::Equal(BURN_PACKAGE_REGISTRATION_STATE_UNKNOWN, pPackage->expectedInstallRegistrationState); + } + + void ValidatePlannedProvider( + __in BURN_PLAN* pPlan, + __in UINT uIndex, + __in LPCWSTR wzKey, + __in LPCWSTR wzName + ) + { + Assert::InRange(uIndex + 1u, 1u, pPlan->cPlannedProviders); + + DEPENDENCY* pProvider = pPlan->rgPlannedProviders + uIndex; + NativeAssert::StringEqual(wzKey, pProvider->sczKey); + NativeAssert::StringEqual(wzName, pProvider->sczName); + } + }; +} +} +} +} +} + +static HRESULT WINAPI PlanTestBAProc( + __in BOOTSTRAPPER_APPLICATION_MESSAGE /*message*/, + __in const LPVOID /*pvArgs*/, + __inout LPVOID /*pvResults*/, + __in_opt LPVOID /*pvContext*/ + ) +{ + return S_OK; +} diff --git a/src/burn/test/BurnUnitTest/RegistrationTest.cpp b/src/burn/test/BurnUnitTest/RegistrationTest.cpp new file mode 100644 index 00000000..7b126f61 --- /dev/null +++ b/src/burn/test/BurnUnitTest/RegistrationTest.cpp @@ -0,0 +1,772 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +#define ROOT_PATH L"SOFTWARE\\WiX_Burn_UnitTest" +#define HKLM_PATH L"SOFTWARE\\WiX_Burn_UnitTest\\HKLM" +#define HKCU_PATH L"SOFTWARE\\WiX_Burn_UnitTest\\HKCU" +#define REGISTRY_UNINSTALL_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall" +#define REGISTRY_RUN_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce" + +#define TEST_UNINSTALL_KEY L"HKEY_CURRENT_USER\\" HKCU_PATH L"\\" REGISTRY_UNINSTALL_KEY L"\\{D54F896D-1952-43e6-9C67-B5652240618C}" +#define TEST_RUN_KEY L"HKEY_CURRENT_USER\\" HKCU_PATH L"\\" REGISTRY_RUN_KEY + + +static LSTATUS APIENTRY RegistrationTest_RegCreateKeyExW( + __in HKEY hKey, + __in LPCWSTR lpSubKey, + __reserved DWORD Reserved, + __in_opt LPWSTR lpClass, + __in DWORD dwOptions, + __in REGSAM samDesired, + __in_opt CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes, + __out PHKEY phkResult, + __out_opt LPDWORD lpdwDisposition + ); +static LSTATUS APIENTRY RegistrationTest_RegOpenKeyExW( + __in HKEY hKey, + __in_opt LPCWSTR lpSubKey, + __reserved DWORD ulOptions, + __in REGSAM samDesired, + __out PHKEY phkResult + ); +static LSTATUS APIENTRY RegistrationTest_RegDeleteKeyExW( + __in HKEY hKey, + __in LPCWSTR lpSubKey, + __in REGSAM samDesired, + __reserved DWORD Reserved + ); + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace Microsoft::Win32; + using namespace System; + using namespace System::IO; + using namespace Xunit; + + public ref class RegistrationTest : BurnUnitTest + { + public: + RegistrationTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture) + { + } + + [Fact] + void RegisterBasicTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + LPWSTR sczCurrentProcess = NULL; + BURN_VARIABLES variables = { }; + BURN_USER_EXPERIENCE userExperience = { }; + BOOTSTRAPPER_COMMAND command = { }; + BURN_REGISTRATION registration = { }; + BURN_LOGGING logging = { }; + BURN_PACKAGES packages = { }; + String^ cacheDirectory = Path::Combine(Path::Combine(Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData), gcnew String(L"Package Cache")), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}")); + + try + { + // set mock API's + RegFunctionOverride(RegistrationTest_RegCreateKeyExW, RegistrationTest_RegOpenKeyExW, RegistrationTest_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL); + + Registry::CurrentUser->CreateSubKey(gcnew String(HKCU_PATH)); + + logging.sczPath = L"BurnUnitTest.txt"; + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + hr = UserExperienceParseFromXml(&userExperience, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse UX from XML."); + + hr = RegistrationParseFromXml(®istration, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse registration from XML."); + + hr = PlanSetResumeCommand(®istration, BOOTSTRAPPER_ACTION_INSTALL, &command, &logging); + TestThrowOnFailure(hr, L"Failed to set registration resume command."); + + hr = PathForCurrentProcess(&sczCurrentProcess, NULL); + TestThrowOnFailure(hr, L"Failed to get current process path."); + + // write registration + hr = RegistrationSessionBegin(sczCurrentProcess, ®istration, &variables, BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE | BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER, 0); + TestThrowOnFailure(hr, L"Failed to register bundle."); + + // verify that registration was created + Assert::True(Directory::Exists(cacheDirectory)); + Assert::True(File::Exists(Path::Combine(cacheDirectory, gcnew String(L"setup.exe")))); + + Assert::Equal(Int32(BURN_RESUME_MODE_ACTIVE), (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal(String::Concat(L"\"", Path::Combine(cacheDirectory, gcnew String(L"setup.exe")), L"\" /burn.runonce"), (String^)(Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr))); + + // end session + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_NONE, BOOTSTRAPPER_APPLY_RESTART_NONE, BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER); + TestThrowOnFailure(hr, L"Failed to unregister bundle."); + + // verify that registration was removed + Assert::False(Directory::Exists(cacheDirectory)); + + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + } + finally + { + ReleaseStr(sczCurrentProcess); + ReleaseObject(pixeBundle); + UserExperienceUninitialize(&userExperience); + RegistrationUninitialize(®istration); + VariablesUninitialize(&variables); + + Registry::CurrentUser->DeleteSubKeyTree(gcnew String(ROOT_PATH)); + if (Directory::Exists(cacheDirectory)) + { + Directory::Delete(cacheDirectory, true); + } + + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + [Fact] + void RegisterArpMinimumTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + LPWSTR sczCurrentProcess = NULL; + BURN_VARIABLES variables = { }; + BURN_USER_EXPERIENCE userExperience = { }; + BOOTSTRAPPER_COMMAND command = { }; + BURN_REGISTRATION registration = { }; + BURN_LOGGING logging = { }; + BURN_PACKAGES packages = { }; + String^ cacheDirectory = Path::Combine(Path::Combine(Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData), gcnew String(L"Package Cache")), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}")); + try + { + // set mock API's + RegFunctionOverride(RegistrationTest_RegCreateKeyExW, RegistrationTest_RegOpenKeyExW, RegistrationTest_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL); + + Registry::CurrentUser->CreateSubKey(gcnew String(HKCU_PATH)); + + logging.sczPath = L"BurnUnitTest.txt"; + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + hr = UserExperienceParseFromXml(&userExperience, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse UX from XML."); + + hr = RegistrationParseFromXml(®istration, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse registration from XML."); + + hr = PlanSetResumeCommand(®istration, BOOTSTRAPPER_ACTION_INSTALL, &command, &logging); + TestThrowOnFailure(hr, L"Failed to set registration resume command."); + + hr = PathForCurrentProcess(&sczCurrentProcess, NULL); + TestThrowOnFailure(hr, L"Failed to get current process path."); + + // + // install + // + + // write registration + hr = RegistrationSessionBegin(sczCurrentProcess, ®istration, &variables, BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER, 0); + TestThrowOnFailure(hr, L"Failed to register bundle."); + + // verify that registration was created + Assert::Equal(Int32(BURN_RESUME_MODE_ACTIVE), (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal(String::Concat(L"\"", Path::Combine(cacheDirectory, gcnew String(L"setup.exe")), L"\" /burn.runonce"), (String^)Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + + // complete registration + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_ARP, BOOTSTRAPPER_APPLY_RESTART_NONE, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER); + TestThrowOnFailure(hr, L"Failed to unregister bundle."); + + // verify that registration was updated + Assert::Equal(Int32(BURN_RESUME_MODE_ARP), (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal(1, (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Installed"), nullptr)); + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + + // + // uninstall + // + + // write registration + hr = RegistrationSessionBegin(sczCurrentProcess, ®istration, &variables, BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION, BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER, 0); + TestThrowOnFailure(hr, L"Failed to register bundle."); + + // verify that registration was updated + Assert::Equal(Int32(BURN_RESUME_MODE_ACTIVE), (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal(1, (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Installed"), nullptr)); + Assert::Equal(String::Concat(L"\"", Path::Combine(cacheDirectory, gcnew String(L"setup.exe")), L"\" /burn.runonce"), (String^)Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + + // delete registration + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_NONE, BOOTSTRAPPER_APPLY_RESTART_NONE, BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER); + TestThrowOnFailure(hr, L"Failed to unregister bundle."); + + // verify that registration was removed + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Installed"), nullptr)); + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + } + finally + { + ReleaseStr(sczCurrentProcess); + ReleaseObject(pixeBundle); + UserExperienceUninitialize(&userExperience); + RegistrationUninitialize(®istration); + VariablesUninitialize(&variables); + + Registry::CurrentUser->DeleteSubKeyTree(gcnew String(ROOT_PATH)); + if (Directory::Exists(cacheDirectory)) + { + Directory::Delete(cacheDirectory, true); + } + + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + [Fact] + void RegisterVariablesTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + LPWSTR sczCurrentProcess = NULL; + BURN_VARIABLES variables = { }; + BURN_USER_EXPERIENCE userExperience = { }; + BOOTSTRAPPER_COMMAND command = { }; + BURN_REGISTRATION registration = { }; + BURN_LOGGING logging = { }; + BURN_PACKAGES packages = { }; + String^ cacheDirectory = Path::Combine(Path::Combine(Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData), gcnew String(L"Package Cache")), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}")); + try + { + // set mock API's + RegFunctionOverride(RegistrationTest_RegCreateKeyExW, RegistrationTest_RegOpenKeyExW, RegistrationTest_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL); + + Registry::CurrentUser->CreateSubKey(gcnew String(HKCU_PATH)); + + logging.sczPath = L"BurnUnitTest.txt"; + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + hr = UserExperienceParseFromXml(&userExperience, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse UX from XML."); + + hr = RegistrationParseFromXml(®istration, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse registration from XML."); + + hr = PlanSetResumeCommand(®istration, BOOTSTRAPPER_ACTION_INSTALL, &command, &logging); + TestThrowOnFailure(hr, L"Failed to set registration resume command."); + + hr = PathForCurrentProcess(&sczCurrentProcess, NULL); + TestThrowOnFailure(hr, L"Failed to get current process path."); + + // + // install + // + + // write registration + hr = RegistrationSessionBegin(sczCurrentProcess, ®istration, &variables, BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER, 0); + TestThrowOnFailure(hr, L"Failed to register bundle."); + + // verify that registration was created + Assert::Equal(Int32(BURN_RESUME_MODE_ACTIVE), (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal(String::Concat(L"\"", Path::Combine(cacheDirectory, gcnew String(L"setup.exe")), L"\" /burn.runonce"), (String^)Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + + // complete registration + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_ARP, BOOTSTRAPPER_APPLY_RESTART_REQUIRED, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER); + TestThrowOnFailure(hr, L"Failed to unregister bundle."); + + // verify that registration variables were updated + registration.fInstalled = TRUE; + + hr = RegistrationSetVariables(®istration, &variables); + TestThrowOnFailure(hr, L"Failed to set registration variables."); + + Assert::Equal(1ll, VariableGetNumericHelper(&variables, BURN_BUNDLE_INSTALLED)); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, BURN_REBOOT_PENDING)); + Assert::Equal(gcnew String(L"foo"), VariableGetStringHelper(&variables, BURN_BUNDLE_TAG)); + Assert::Equal(gcnew String(L"bar"), VariableGetStringHelper(&variables, BURN_BUNDLE_PROVIDER_KEY)); + Assert::Equal(gcnew String(L"1.0.0.0"), VariableGetVersionHelper(&variables, BURN_BUNDLE_VERSION)); + + // + // uninstall + // + + // delete registration + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_NONE, BOOTSTRAPPER_APPLY_RESTART_NONE, BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER); + TestThrowOnFailure(hr, L"Failed to unregister bundle."); + + // verify that registration was removed + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Installed"), nullptr)); + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + } + finally + { + ReleaseStr(sczCurrentProcess); + ReleaseObject(pixeBundle); + UserExperienceUninitialize(&userExperience); + RegistrationUninitialize(®istration); + VariablesUninitialize(&variables); + + Registry::CurrentUser->DeleteSubKeyTree(gcnew String(ROOT_PATH)); + if (Directory::Exists(cacheDirectory)) + { + Directory::Delete(cacheDirectory, true); + } + + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + [Fact] + void RegisterArpFullTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + LPWSTR sczCurrentProcess = NULL; + BURN_VARIABLES variables = { }; + BURN_USER_EXPERIENCE userExperience = { }; + BOOTSTRAPPER_COMMAND command = { }; + BURN_REGISTRATION registration = { }; + BURN_LOGGING logging = { }; + BURN_PACKAGES packages = { }; + String^ cacheDirectory = Path::Combine(Path::Combine(Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData), gcnew String(L"Package Cache")), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}")); + try + { + // set mock API's + RegFunctionOverride(RegistrationTest_RegCreateKeyExW, RegistrationTest_RegOpenKeyExW, RegistrationTest_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL); + + Registry::CurrentUser->CreateSubKey(gcnew String(HKCU_PATH)); + + logging.sczPath = L"BurnUnitTest.txt"; + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + hr = UserExperienceParseFromXml(&userExperience, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse UX from XML."); + + hr = RegistrationParseFromXml(®istration, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse registration from XML."); + + hr = PlanSetResumeCommand(®istration, BOOTSTRAPPER_ACTION_INSTALL, &command, &logging); + TestThrowOnFailure(hr, L"Failed to set registration resume command."); + + hr = PathForCurrentProcess(&sczCurrentProcess, NULL); + TestThrowOnFailure(hr, L"Failed to get current process path."); + + // + // install + // + + // write registration + hr = RegistrationSessionBegin(sczCurrentProcess, ®istration, &variables, BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER, 0); + TestThrowOnFailure(hr, L"Failed to register bundle."); + + // verify that registration was created + Assert::Equal(Int32(BURN_RESUME_MODE_ACTIVE), (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal(String::Concat(L"\"", Path::Combine(cacheDirectory, gcnew String(L"setup.exe")), L"\" /burn.runonce"), (String^)Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + + // finish registration + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_ARP, BOOTSTRAPPER_APPLY_RESTART_NONE, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER); + TestThrowOnFailure(hr, L"Failed to register bundle."); + + // verify that registration was updated + Assert::Equal(Int32(BURN_RESUME_MODE_ARP), (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal(1, (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Installed"), nullptr)); + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + + Assert::Equal(gcnew String(L"DisplayName1"), (String^)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"DisplayName"), nullptr)); + Assert::Equal(gcnew String(L"1.2.3.4"), (String^)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"DisplayVersion"), nullptr)); + Assert::Equal(gcnew String(L"Publisher1"), (String^)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Publisher"), nullptr)); + Assert::Equal(gcnew String(L"http://www.microsoft.com/help"), (String^)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"HelpLink"), nullptr)); + Assert::Equal(gcnew String(L"555-555-5555"), (String^)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"HelpTelephone"), nullptr)); + Assert::Equal(gcnew String(L"http://www.microsoft.com/about"), (String^)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"URLInfoAbout"), nullptr)); + Assert::Equal(gcnew String(L"http://www.microsoft.com/update"), (String^)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"URLUpdateInfo"), nullptr)); + Assert::Equal(gcnew String(L"Comments1"), (String^)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Comments"), nullptr)); + Assert::Equal(gcnew String(L"Contact1"), (String^)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Contact"), nullptr)); + Assert::Equal(1, (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"NoModify"), nullptr)); + Assert::Equal(1, (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"NoRemove"), nullptr)); + + // + // uninstall + // + + // write registration + hr = RegistrationSessionBegin(sczCurrentProcess, ®istration, &variables, BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION, BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER, 0); + TestThrowOnFailure(hr, L"Failed to register bundle."); + + // verify that registration was updated + Assert::Equal(Int32(BURN_RESUME_MODE_ACTIVE), (Int32)Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal(String::Concat(L"\"", Path::Combine(cacheDirectory, gcnew String(L"setup.exe")), L"\" /burn.runonce"), (String^)Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + + // delete registration + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_NONE, BOOTSTRAPPER_APPLY_RESTART_NONE, BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER); + TestThrowOnFailure(hr, L"Failed to unregister bundle."); + + // verify that registration was removed + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Resume"), nullptr)); + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_UNINSTALL_KEY), gcnew String(L"Installed"), nullptr)); + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + } + finally + { + ReleaseStr(sczCurrentProcess); + ReleaseObject(pixeBundle); + UserExperienceUninitialize(&userExperience); + RegistrationUninitialize(®istration); + VariablesUninitialize(&variables); + + Registry::CurrentUser->DeleteSubKeyTree(gcnew String(ROOT_PATH)); + if (Directory::Exists(cacheDirectory)) + { + Directory::Delete(cacheDirectory, true); + } + + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + [Fact(Skip = "Currently fails")] + void ResumeTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + LPWSTR sczCurrentProcess = NULL; + BURN_VARIABLES variables = { }; + BURN_USER_EXPERIENCE userExperience = { }; + BOOTSTRAPPER_COMMAND command = { }; + BURN_REGISTRATION registration = { }; + BURN_LOGGING logging = { }; + BURN_PACKAGES packages = { }; + BYTE rgbData[256] = { }; + BOOTSTRAPPER_RESUME_TYPE resumeType = BOOTSTRAPPER_RESUME_TYPE_NONE; + BYTE* pbBuffer = NULL; + SIZE_T cbBuffer = 0; + String^ cacheDirectory = Path::Combine(Path::Combine(Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData), gcnew String(L"Package Cache")), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}")); + try + { + for (DWORD i = 0; i < 256; ++i) + { + rgbData[i] = (BYTE)i; + } + + // set mock API's + RegFunctionOverride(RegistrationTest_RegCreateKeyExW, RegistrationTest_RegOpenKeyExW, RegistrationTest_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL); + + Registry::CurrentUser->CreateSubKey(gcnew String(HKCU_PATH)); + + logging.sczPath = L"BurnUnitTest.txt"; + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + hr = UserExperienceParseFromXml(&userExperience, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse UX from XML."); + + hr = RegistrationParseFromXml(®istration, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse registration from XML."); + + hr = PlanSetResumeCommand(®istration, BOOTSTRAPPER_ACTION_INSTALL, &command, &logging); + TestThrowOnFailure(hr, L"Failed to set registration resume command."); + + hr = PathForCurrentProcess(&sczCurrentProcess, NULL); + TestThrowOnFailure(hr, L"Failed to get current process path."); + + // read resume type before session + hr = RegistrationDetectResumeType(®istration, &resumeType); + TestThrowOnFailure(hr, L"Failed to read resume type."); + + Assert::Equal((int)BOOTSTRAPPER_RESUME_TYPE_NONE, (int)resumeType); + + // begin session + hr = RegistrationSessionBegin(sczCurrentProcess, ®istration, &variables, BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER, 0); + TestThrowOnFailure(hr, L"Failed to register bundle."); + + hr = RegistrationSaveState(®istration, rgbData, sizeof(rgbData)); + TestThrowOnFailure(hr, L"Failed to save state."); + + // read interrupted resume type + hr = RegistrationDetectResumeType(®istration, &resumeType); + TestThrowOnFailure(hr, L"Failed to read interrupted resume type."); + + Assert::Equal((int)BOOTSTRAPPER_RESUME_TYPE_INTERRUPTED, (int)resumeType); + + // suspend session + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_SUSPEND, BOOTSTRAPPER_APPLY_RESTART_NONE, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER); + TestThrowOnFailure(hr, L"Failed to suspend session."); + + // verify that run key was removed + Assert::Equal((Object^)nullptr, Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + + // read suspend resume type + hr = RegistrationDetectResumeType(®istration, &resumeType); + TestThrowOnFailure(hr, L"Failed to read suspend resume type."); + + Assert::Equal((int)BOOTSTRAPPER_RESUME_TYPE_SUSPEND, (int)resumeType); + + // read state back + hr = RegistrationLoadState(®istration, &pbBuffer, &cbBuffer); + TestThrowOnFailure(hr, L"Failed to load state."); + + Assert::Equal((SIZE_T)sizeof(rgbData), cbBuffer); + Assert::True(0 == memcmp(pbBuffer, rgbData, sizeof(rgbData))); + + // write active resume mode + hr = RegistrationSessionResume(®istration, &variables); + TestThrowOnFailure(hr, L"Failed to write active resume mode."); + + // verify that run key was put back + Assert::NotEqual((Object^)nullptr, Registry::GetValue(gcnew String(TEST_RUN_KEY), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}"), nullptr)); + + // end session + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_NONE, BOOTSTRAPPER_APPLY_RESTART_NONE, BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER); + TestThrowOnFailure(hr, L"Failed to unregister bundle."); + + // read resume type after session + hr = RegistrationDetectResumeType(®istration, &resumeType); + TestThrowOnFailure(hr, L"Failed to read resume type."); + + Assert::Equal((int)BOOTSTRAPPER_RESUME_TYPE_NONE, (int)resumeType); + } + finally + { + ReleaseStr(sczCurrentProcess); + ReleaseObject(pixeBundle); + UserExperienceUninitialize(&userExperience); + RegistrationUninitialize(®istration); + VariablesUninitialize(&variables); + + Registry::CurrentUser->DeleteSubKeyTree(gcnew String(ROOT_PATH)); + if (Directory::Exists(cacheDirectory)) + { + Directory::Delete(cacheDirectory, true); + } + + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + //BOOTSTRAPPER_RESUME_TYPE_NONE, + //BOOTSTRAPPER_RESUME_TYPE_INVALID, // resume information is present but invalid + //BOOTSTRAPPER_RESUME_TYPE_UNEXPECTED, // relaunched after an unexpected interruption + //BOOTSTRAPPER_RESUME_TYPE_REBOOT_PENDING, // reboot has not taken place yet + //BOOTSTRAPPER_RESUME_TYPE_REBOOT, // relaunched after reboot + //BOOTSTRAPPER_RESUME_TYPE_SUSPEND, // relaunched after suspend + //BOOTSTRAPPER_RESUME_TYPE_ARP, // launched from ARP + }; +} +} +} +} +} + + +static LSTATUS APIENTRY RegistrationTest_RegCreateKeyExW( + __in HKEY hKey, + __in LPCWSTR lpSubKey, + __reserved DWORD Reserved, + __in_opt LPWSTR lpClass, + __in DWORD dwOptions, + __in REGSAM samDesired, + __in_opt CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes, + __out PHKEY phkResult, + __out_opt LPDWORD lpdwDisposition + ) +{ + LSTATUS ls = ERROR_SUCCESS; + LPCWSTR wzRoot = NULL; + HKEY hkRoot = NULL; + + if (HKEY_LOCAL_MACHINE == hKey) + { + wzRoot = HKLM_PATH; + } + else if (HKEY_CURRENT_USER == hKey) + { + wzRoot = HKCU_PATH; + } + else + { + hkRoot = hKey; + } + + if (wzRoot) + { + ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, wzRoot, 0, KEY_WRITE, &hkRoot); + if (ERROR_SUCCESS != ls) + { + ExitFunction(); + } + } + + ls = ::RegCreateKeyExW(hkRoot, lpSubKey, Reserved, lpClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition); + +LExit: + ReleaseRegKey(hkRoot); + + return ls; +} + +static LSTATUS APIENTRY RegistrationTest_RegOpenKeyExW( + __in HKEY hKey, + __in_opt LPCWSTR lpSubKey, + __reserved DWORD ulOptions, + __in REGSAM samDesired, + __out PHKEY phkResult + ) +{ + LSTATUS ls = ERROR_SUCCESS; + LPCWSTR wzRoot = NULL; + HKEY hkRoot = NULL; + + if (HKEY_LOCAL_MACHINE == hKey) + { + wzRoot = HKLM_PATH; + } + else if (HKEY_CURRENT_USER == hKey) + { + wzRoot = HKCU_PATH; + } + else + { + hkRoot = hKey; + } + + if (wzRoot) + { + ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, wzRoot, 0, KEY_WRITE, &hkRoot); + if (ERROR_SUCCESS != ls) + { + ExitFunction(); + } + } + + ls = ::RegOpenKeyExW(hkRoot, lpSubKey, ulOptions, samDesired, phkResult); + +LExit: + ReleaseRegKey(hkRoot); + + return ls; +} + +static LSTATUS APIENTRY RegistrationTest_RegDeleteKeyExW( + __in HKEY hKey, + __in LPCWSTR lpSubKey, + __in REGSAM samDesired, + __reserved DWORD Reserved + ) +{ + LSTATUS ls = ERROR_SUCCESS; + LPCWSTR wzRoot = NULL; + HKEY hkRoot = NULL; + + if (HKEY_LOCAL_MACHINE == hKey) + { + wzRoot = HKLM_PATH; + } + else if (HKEY_CURRENT_USER == hKey) + { + wzRoot = HKCU_PATH; + } + else + { + hkRoot = hKey; + } + + if (wzRoot) + { + ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, wzRoot, 0, KEY_WRITE | samDesired, &hkRoot); + if (ERROR_SUCCESS != ls) + { + ExitFunction(); + } + } + + ls = ::RegDeleteKeyExW(hkRoot, lpSubKey, samDesired, Reserved); + +LExit: + ReleaseRegKey(hkRoot); + + return ls; +} diff --git a/src/burn/test/BurnUnitTest/SearchTest.cpp b/src/burn/test/BurnUnitTest/SearchTest.cpp new file mode 100644 index 00000000..eca01f5f --- /dev/null +++ b/src/burn/test/BurnUnitTest/SearchTest.cpp @@ -0,0 +1,815 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +static INSTALLSTATE WINAPI MsiComponentSearchTest_MsiGetComponentPathW( + __in LPCWSTR szProduct, + __in LPCWSTR szComponent, + __out_ecount_opt(*pcchBuf) LPWSTR lpPathBuf, + __inout_opt LPDWORD pcchBuf + ); +static INSTALLSTATE WINAPI MsiComponentSearchTest_MsiLocateComponentW( + __in LPCWSTR szComponent, + __out_ecount_opt(*pcchBuf) LPWSTR lpPathBuf, + __inout_opt LPDWORD pcchBuf + ); +static UINT WINAPI MsiProductSearchTest_MsiGetProductInfoW( + __in LPCWSTR szProductCode, + __in LPCWSTR szProperty, + __out_ecount_opt(*pcchValue) LPWSTR szValue, + __inout_opt LPDWORD pcchValue + ); +static UINT WINAPI MsiProductSearchTest_MsiGetProductInfoExW( + __in LPCWSTR szProductCode, + __in_opt LPCWSTR szUserSid, + __in MSIINSTALLCONTEXT dwContext, + __in LPCWSTR szProperty, + __out_ecount_opt(*pcchValue) LPWSTR szValue, + __inout_opt LPDWORD pcchValue + ); + +using namespace System; +using namespace Xunit; +using namespace Microsoft::Win32; + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + public ref class SearchTest : BurnUnitTest + { + public: + SearchTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture) + { + } + + [Fact] + void DirectorySearchTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BURN_SEARCHES searches = { }; + BURN_EXTENSIONS burnExtensions = { }; + try + { + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + pin_ptr wzDirectory1 = PtrToStringChars(this->TestContext->TestDirectory); + pin_ptr wzDirectory2 = PtrToStringChars(System::IO::Path::Combine(this->TestContext->TestDirectory, gcnew String(L"none"))); + + VariableSetStringHelper(&variables, L"Directory1", wzDirectory1, FALSE); + VariableSetStringHelper(&variables, L"Directory2", wzDirectory2, FALSE); + + LPCWSTR wzDocument = + L"" + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = SearchesParseFromXml(&searches, &burnExtensions, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // execute searches + hr = SearchesExecute(&searches, &variables); + TestThrowOnFailure(hr, L"Failed to execute searches."); + + // check variable values + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable1")); + Assert::Equal(0ll, VariableGetNumericHelper(&variables, L"Variable2")); + } + finally + { + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + SearchesUninitialize(&searches); + } + } + + [Fact(Skip = "Currently fails")] + void FileSearchTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BURN_SEARCHES searches = { }; + BURN_EXTENSIONS burnExtensions = { }; + ULARGE_INTEGER uliVersion = { }; + VERUTIL_VERSION* pVersion = NULL; + try + { + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + pin_ptr wzFile1 = PtrToStringChars(System::IO::Path::Combine(this->TestContext->TestDirectory, gcnew String(L"none.txt"))); + pin_ptr wzFile2 = PtrToStringChars(System::Reflection::Assembly::GetExecutingAssembly()->Location); + + hr = FileVersion(wzFile2, &uliVersion.HighPart, &uliVersion.LowPart); + TestThrowOnFailure(hr, L"Failed to get DLL version."); + + hr = VerVersionFromQword(uliVersion.QuadPart, &pVersion); + NativeAssert::Succeeded(hr, "Failed to create version."); + + VariableSetStringHelper(&variables, L"File1", wzFile1, FALSE); + VariableSetStringHelper(&variables, L"File2", wzFile2, FALSE); + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = SearchesParseFromXml(&searches, &burnExtensions, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // execute searches + hr = SearchesExecute(&searches, &variables); + TestThrowOnFailure(hr, L"Failed to execute searches."); + + // check variable values + Assert::Equal(0ll, VariableGetNumericHelper(&variables, L"Variable1")); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable2")); + Assert::Equal(gcnew String(pVersion->sczVersion), VariableGetVersionHelper(&variables, L"Variable3")); + } + finally + { + ReleaseVerutilVersion(pVersion); + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + SearchesUninitialize(&searches); + } + } + + [Fact] + void RegistrySearchTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BURN_SEARCHES searches = { }; + BURN_EXTENSIONS burnExtensions = { }; + HKEY hkey32 = NULL; + HKEY hkey64 = NULL; + BOOL f64bitMachine = (nullptr != Environment::GetEnvironmentVariable("ProgramFiles(x86)")); + + try + { + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + Registry::SetValue(gcnew String(L"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Value"), gcnew String(L"String"), gcnew String(L"String1 %TEMP%"), RegistryValueKind::String); + Registry::SetValue(gcnew String(L"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Value"), gcnew String(L"StringExpand"), gcnew String(L"String1 %TEMP%"), RegistryValueKind::ExpandString); + Registry::SetValue(gcnew String(L"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Value"), gcnew String(L"DWord"), 1, RegistryValueKind::DWord); + Registry::SetValue(gcnew String(L"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Value"), gcnew String(L"QWord"), 1ll, RegistryValueKind::QWord); + Registry::SetValue(gcnew String(L"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Value"), gcnew String(L"VersionString"), gcnew String(L"1.1.1.1"), RegistryValueKind::String); + Registry::SetValue(gcnew String(L"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Value"), gcnew String(L"VersionQWord"), MAKEQWORDVERSION(1,1,1,1), RegistryValueKind::QWord); + Registry::SetValue(gcnew String(L"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\String"), nullptr, gcnew String(L"String1"), RegistryValueKind::String); + Registry::SetValue(gcnew String(L"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Numeric"), nullptr, 1ll, RegistryValueKind::DWord); + + if (f64bitMachine) + { + hr = RegCreate(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\CLSID\\WiX_Burn_UnitTest\\Bitness\\", KEY_WRITE | KEY_WOW64_32KEY, &hkey32); + Assert::True(SUCCEEDED(hr)); + + hr = RegCreate(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\CLSID\\WiX_Burn_UnitTest\\Bitness\\", KEY_WRITE | KEY_WOW64_64KEY, &hkey64); + Assert::True(SUCCEEDED(hr)); + + hr = RegWriteString(hkey64, L"TestStringSpecificToBitness", L"64-bit"); + Assert::True(SUCCEEDED(hr)); + + hr = RegWriteString(hkey32, L"TestStringSpecificToBitness", L"32-bit"); + Assert::True(SUCCEEDED(hr)); + } + + VariableSetStringHelper(&variables, L"MyKey", L"SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Value", FALSE); + VariableSetStringHelper(&variables, L"MyValue", L"String", FALSE); + VariableSetStringHelper(&variables, L"Variable27", L"Default27", FALSE); + VariableSetStringHelper(&variables, L"Variable28", L"Default28", FALSE); + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = SearchesParseFromXml(&searches, &burnExtensions, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // execute searches + hr = SearchesExecute(&searches, &variables); + TestThrowOnFailure(hr, L"Failed to execute searches."); + + // check variable values + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable1")); + Assert::Equal(0ll, VariableGetNumericHelper(&variables, L"Variable2")); + Assert::Equal(0ll, VariableGetNumericHelper(&variables, L"Variable3")); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable4")); + Assert::Equal(gcnew String(L"String1 %TEMP%"), VariableGetStringHelper(&variables, L"Variable5")); + Assert::Equal(gcnew String(L"String1 %TEMP%"), VariableGetStringHelper(&variables, L"Variable6")); + Assert::Equal(gcnew String(L"String1 %TEMP%"), VariableGetStringHelper(&variables, L"Variable7")); + Assert::Equal(gcnew String(L"String1 %TEMP%"), VariableGetStringHelper(&variables, L"Variable8")); + Assert::Equal(gcnew String(L"String1 %TEMP%"), VariableGetStringHelper(&variables, L"Variable9")); + Assert::NotEqual(gcnew String(L"String1 %TEMP%"), VariableGetStringHelper(&variables, L"Variable10")); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable11")); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable12")); + Assert::Equal(gcnew String(L"1.1.1.1"), VariableGetVersionHelper(&variables, L"Variable13")); + Assert::Equal(gcnew String(L"1.1.1.1"), VariableGetVersionHelper(&variables, L"Variable14")); + Assert::Equal(gcnew String(L"String1"), VariableGetStringHelper(&variables, L"Variable15")); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable16")); + Assert::False(VariableExistsHelper(&variables, L"Variable17")); + Assert::False(VariableExistsHelper(&variables, L"Variable18")); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable19")); + Assert::Equal(gcnew String(L"String1 %TEMP%"), VariableGetStringHelper(&variables, L"Variable20")); + if (f64bitMachine) + { + Assert::Equal(gcnew String(L"32-bit"), VariableGetStringHelper(&variables, L"Variable21")); + Assert::Equal(gcnew String(L"64-bit"), VariableGetStringHelper(&variables, L"Variable22")); + } + + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable23")); + Assert::Equal(0ll, VariableGetNumericHelper(&variables, L"Variable24")); + Assert::Equal(gcnew String(L"Msi.Package"), VariableGetStringHelper(&variables, L"Variable25")); + Assert::Equal(gcnew String(L"Msi.Package"), VariableGetStringHelper(&variables, L"Variable26")); + Assert::Equal(gcnew String(L"Default27"), VariableGetStringHelper(&variables, L"Variable27")); + Assert::Equal(gcnew String(L"Default28"), VariableGetStringHelper(&variables, L"Variable28")); + } + finally + { + ReleaseRegKey(hkey32); + ReleaseRegKey(hkey64); + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + SearchesUninitialize(&searches); + + Registry::CurrentUser->DeleteSubKeyTree(gcnew String(L"SOFTWARE\\Microsoft\\WiX_Burn_UnitTest")); + if (f64bitMachine) + { + RegDelete(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\CLSID\\WiX_Burn_UnitTest\\Bitness", REG_KEY_32BIT, FALSE); + RegDelete(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\CLSID\\WiX_Burn_UnitTest", REG_KEY_32BIT, FALSE); + RegDelete(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\CLSID\\WiX_Burn_UnitTest\\Bitness", REG_KEY_64BIT, FALSE); + RegDelete(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\CLSID\\WiX_Burn_UnitTest", REG_KEY_64BIT, FALSE); + } + } + } + + [Fact] + void MsiComponentSearchTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BURN_SEARCHES searches = { }; + BURN_EXTENSIONS burnExtensions = { }; + try + { + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // set mock API's + WiuFunctionOverride(NULL, MsiComponentSearchTest_MsiGetComponentPathW, MsiComponentSearchTest_MsiLocateComponentW, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " // todo: value key path + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = SearchesParseFromXml(&searches, &burnExtensions, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // execute searches + hr = SearchesExecute(&searches, &variables); + TestThrowOnFailure(hr, L"Failed to execute searches."); + + // check variable values + Assert::Equal(2ll, VariableGetNumericHelper(&variables, L"Variable1")); + Assert::Equal(2ll, VariableGetNumericHelper(&variables, L"Variable2")); + Assert::Equal(2ll, VariableGetNumericHelper(&variables, L"Variable3")); + Assert::Equal(gcnew String(L"C:\\directory\\file1.txt"), VariableGetStringHelper(&variables, L"Variable4")); + Assert::Equal(gcnew String(L"C:\\directory\\file2.txt"), VariableGetStringHelper(&variables, L"Variable5")); + Assert::Equal(gcnew String(L"C:\\directory\\file3.txt"), VariableGetStringHelper(&variables, L"Variable6")); + Assert::Equal(gcnew String(L"C:\\directory\\file4.txt"), VariableGetStringHelper(&variables, L"Variable7")); + Assert::Equal(gcnew String(L"02:\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\"), VariableGetStringHelper(&variables, L"Variable8")); + Assert::Equal(gcnew String(L"02:\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Value"), VariableGetStringHelper(&variables, L"Variable9")); + Assert::Equal(3ll, VariableGetNumericHelper(&variables, L"Variable10")); + Assert::Equal(3ll, VariableGetNumericHelper(&variables, L"Variable11")); + Assert::Equal(4ll, VariableGetNumericHelper(&variables, L"Variable12")); + Assert::Equal(4ll, VariableGetNumericHelper(&variables, L"Variable13")); + Assert::Equal(2ll, VariableGetNumericHelper(&variables, L"Variable14")); + Assert::Equal(gcnew String(L"C:\\directory\\"), VariableGetStringHelper(&variables, L"Variable15")); + Assert::Equal(gcnew String(L"C:\\directory\\"), VariableGetStringHelper(&variables, L"Variable16")); + Assert::Equal(gcnew String(L"C:\\directory\\"), VariableGetStringHelper(&variables, L"Variable17")); + Assert::Equal(gcnew String(L"C:\\directory\\"), VariableGetStringHelper(&variables, L"Variable18")); + Assert::Equal(gcnew String(L"C:\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\file5.txt"), VariableGetStringHelper(&variables, L"Variable19")); + } + finally + { + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + SearchesUninitialize(&searches); + } + } + + [Fact] + void MsiProductSearchTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BURN_SEARCHES searches = { }; + BURN_EXTENSIONS burnExtensions = { }; + try + { + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // set mock API's + WiuFunctionOverride(NULL, NULL, NULL, NULL, MsiProductSearchTest_MsiGetProductInfoW, MsiProductSearchTest_MsiGetProductInfoExW, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = SearchesParseFromXml(&searches, &burnExtensions, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // execute searches + hr = SearchesExecute(&searches, &variables); + TestThrowOnFailure(hr, L"Failed to execute searches."); + + // check variable values + Assert::Equal(2ll, VariableGetNumericHelper(&variables, L"Variable1")); + Assert::Equal(gcnew String(L"1.0.0.0"), VariableGetVersionHelper(&variables, L"Variable2")); + Assert::Equal(1033ll, VariableGetNumericHelper(&variables, L"Variable3")); + Assert::Equal(5ll, VariableGetNumericHelper(&variables, L"Variable4")); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable5")); + Assert::Equal(gcnew String(L"1.0.0.0"), VariableGetVersionHelper(&variables, L"Variable6")); + } + finally + { + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + SearchesUninitialize(&searches); + } + } + + [Fact] + void MsiFeatureSearchTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BURN_SEARCHES searches = { }; + BURN_EXTENSIONS burnExtensions = { }; + try + { + LPCWSTR wzDocument = + L"" + L" " + L""; + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = SearchesParseFromXml(&searches, &burnExtensions, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // execute searches + hr = SearchesExecute(&searches, &variables); + TestThrowOnFailure(hr, L"Failed to execute searches."); + } + finally + { + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + SearchesUninitialize(&searches); + } + } + + [Fact] + void ConditionalSearchTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BURN_SEARCHES searches = { }; + BURN_EXTENSIONS burnExtensions = { }; + try + { + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L""; + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = SearchesParseFromXml(&searches, &burnExtensions, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // execute searches + hr = SearchesExecute(&searches, &variables); + TestThrowOnFailure(hr, L"Failed to execute searches."); + + // check variable values + Assert::False(VariableExistsHelper(&variables, L"Variable1")); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable2")); + Assert::False(VariableExistsHelper(&variables, L"Variable3")); + } + finally + { + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + SearchesUninitialize(&searches); + } + } + [Fact] + void NoSearchesTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BURN_SEARCHES searches = { }; + BURN_EXTENSIONS burnExtensions = { }; + try + { + LPCWSTR wzDocument = + L"" + L""; + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = SearchesParseFromXml(&searches, &burnExtensions, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // execute searches + hr = SearchesExecute(&searches, &variables); + TestThrowOnFailure(hr, L"Failed to execute searches."); + } + finally + { + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + SearchesUninitialize(&searches); + } + } + + [Fact] + void SetVariableSearchTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BURN_SEARCHES searches = { }; + BURN_EXTENSIONS burnExtensions = { }; + try + { + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // set variables + VariableSetStringHelper(&variables, L"OVERWRITTEN_STRING", L"ORIGINAL", FALSE); + VariableSetNumericHelper(&variables, L"OVERWRITTEN_NUMBER", 5); + VariableSetNumericHelper(&variables, L"REMOVED_NUMBER", 22); + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = SearchesParseFromXml(&searches, &burnExtensions, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse searches from XML."); + + // execute searches + hr = SearchesExecute(&searches, &variables); + TestThrowOnFailure(hr, L"Failed to execute searches."); + + // check variable values + Assert::Equal(gcnew String(L"VAL1"), VariableGetStringHelper(&variables, L"PROP1")); + Assert::Equal(2ll, VariableGetNumericHelper(&variables, L"PROP2")); + Assert::Equal(gcnew String(L"2"), VariableGetStringHelper(&variables, L"PROP2")); + Assert::Equal(gcnew String(L"VAL3"), VariableGetStringHelper(&variables, L"PROP3")); + Assert::Equal(gcnew String(L"VAL4"), VariableGetStringHelper(&variables, L"PROP4")); + Assert::Equal(gcnew String(L"VAL5"), VariableGetStringHelper(&variables, L"PROP5")); + Assert::Equal(gcnew String(L"VAL6"), VariableGetStringHelper(&variables, L"PROP6")); + Assert::Equal(7ll, VariableGetNumericHelper(&variables, L"PROP7")); + Assert::Equal(gcnew String(L"1.1.0.0"), VariableGetVersionHelper(&variables, L"PROP8")); + Assert::Equal(gcnew String(L"1.1.0.0"), VariableGetStringHelper(&variables, L"PROP8")); + Assert::Equal(gcnew String(L"[VAL9]"), VariableGetStringHelper(&variables, L"PROP9")); + + Assert::Equal(42ll, VariableGetNumericHelper(&variables, L"OVERWRITTEN_STRING")); + Assert::Equal(gcnew String(L"NEW"), VariableGetStringHelper(&variables, L"OVERWRITTEN_NUMBER")); + Assert::Equal((int)BURN_VARIANT_TYPE_NONE, VariableGetTypeHelper(&variables, L"REMOVED_NUMBER")); + } + finally + { + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + SearchesUninitialize(&searches); + } + } + }; +} +} +} +} +} + + +static INSTALLSTATE WINAPI MsiComponentSearchTest_MsiGetComponentPathW( + __in LPCWSTR szProduct, + __in LPCWSTR szComponent, + __out_ecount_opt(*pcchBuf) LPWSTR lpPathBuf, + __inout_opt LPDWORD pcchBuf + ) +{ + INSTALLSTATE is = INSTALLSTATE_INVALIDARG; + String^ product = gcnew String(szProduct); + + if (String::Equals(product, gcnew String(L"{BAD00000-0000-0000-0000-000000000000}"))) + { + is = INSTALLSTATE_UNKNOWN; + } + else if (String::Equals(product, gcnew String(L"{600D0000-0000-0000-0000-000000000000}"))) + { + is = MsiComponentSearchTest_MsiLocateComponentW(szComponent, lpPathBuf, pcchBuf); + } + + return is; +} + +static INSTALLSTATE WINAPI MsiComponentSearchTest_MsiLocateComponentW( + __in LPCWSTR szComponent, + __out_ecount_opt(*pcchBuf) LPWSTR lpPathBuf, + __inout_opt LPDWORD pcchBuf + ) +{ + HRESULT hr = S_OK; + INSTALLSTATE is = INSTALLSTATE_INVALIDARG; + String^ component = gcnew String(szComponent); + LPCWSTR wzValue = NULL; + + if (String::Equals(component, gcnew String(L"{BAD00000-1000-0000-0000-000000000000}"))) + { + is = INSTALLSTATE_UNKNOWN; + } + else if (String::Equals(component, gcnew String(L"{600D0000-1000-1000-0000-000000000000}"))) + { + wzValue = L"C:\\directory\\file1.txt"; + is = INSTALLSTATE_LOCAL; + } + else if (String::Equals(component, gcnew String(L"{600D0000-1000-2000-0000-000000000000}"))) + { + wzValue = L"C:\\directory\\file2.txt"; + is = INSTALLSTATE_SOURCE; + } + else if (String::Equals(component, gcnew String(L"{600D0000-1000-3000-0000-000000000000}"))) + { + wzValue = L"C:\\directory\\file3.txt"; + is = INSTALLSTATE_SOURCEABSENT; + } + else if (String::Equals(component, gcnew String(L"{600D0000-1000-4000-0000-000000000000}"))) + { + wzValue = L"C:\\directory\\file4.txt"; + is = INSTALLSTATE_ABSENT; + } + else if (String::Equals(component, gcnew String(L"{600D0000-1000-5000-0000-000000000000}"))) + { + wzValue = L"02:\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\"; + is = INSTALLSTATE_LOCAL; + } + else if (String::Equals(component, gcnew String(L"{600D0000-1000-6000-0000-000000000000}"))) + { + wzValue = L"02:\\SOFTWARE\\Microsoft\\WiX_Burn_UnitTest\\Value"; + is = INSTALLSTATE_LOCAL; + } + else if (String::Equals(component, gcnew String(L"{600D0000-1000-7000-0000-000000000000}"))) + { + wzValue = L"C:\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\directory\\file5.txt"; + is = INSTALLSTATE_ABSENT; + } + + if (wzValue && lpPathBuf) + { + hr = ::StringCchCopyW(lpPathBuf, *pcchBuf, wzValue); + if (STRSAFE_E_INSUFFICIENT_BUFFER == hr) + { + *pcchBuf = lstrlenW(wzValue); + is = INSTALLSTATE_MOREDATA; + } + else if (FAILED(hr)) + { + is = INSTALLSTATE_INVALIDARG; + } + } + + return is; +} + +static UINT WINAPI MsiProductSearchTest_MsiGetProductInfoW( + __in LPCWSTR szProductCode, + __in LPCWSTR szProperty, + __out_ecount_opt(*pcchValue) LPWSTR szValue, + __inout_opt LPDWORD pcchValue + ) +{ + if (String::Equals(gcnew String(szProductCode), gcnew String(L"{600D0000-0000-0000-0000-000000000000}")) && + String::Equals(gcnew String(szProperty), gcnew String(INSTALLPROPERTY_PRODUCTSTATE))) + { + // force call to WiuGetProductInfoEx + return ERROR_UNKNOWN_PROPERTY; + } + + UINT er = MsiProductSearchTest_MsiGetProductInfoExW(szProductCode, NULL, MSIINSTALLCONTEXT_MACHINE, szProperty, szValue, pcchValue); + return er; +} + +static UINT WINAPI MsiProductSearchTest_MsiGetProductInfoExW( + __in LPCWSTR szProductCode, + __in_opt LPCWSTR /*szUserSid*/, + __in MSIINSTALLCONTEXT dwContext, + __in LPCWSTR szProperty, + __out_ecount_opt(*pcchValue) LPWSTR szValue, + __inout_opt LPDWORD pcchValue + ) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_FUNCTION_FAILED; + LPCWSTR wzValue = NULL; + + String^ productCode = gcnew String(szProductCode); + String^ _property = gcnew String(szProperty); + switch (dwContext) + { + case MSIINSTALLCONTEXT_USERMANAGED: + er = ERROR_UNKNOWN_PRODUCT; + break; + case MSIINSTALLCONTEXT_USERUNMANAGED: + if (String::Equals(productCode, gcnew String(L"{600D0000-0000-0000-0000-000000000000}"))) + { + if (String::Equals(_property, gcnew String(INSTALLPROPERTY_PRODUCTSTATE))) + { + wzValue = L"5"; + } + } + break; + case MSIINSTALLCONTEXT_MACHINE: + if (String::Equals(productCode, gcnew String(L"{BAD00000-0000-0000-0000-000000000000}"))) + { + er = ERROR_UNKNOWN_PRODUCT; + } + else if (String::Equals(productCode, gcnew String(L"{600D0000-0000-0000-0000-000000000000}"))) + { + if (String::Equals(_property, gcnew String(INSTALLPROPERTY_VERSIONSTRING))) + { + wzValue = L"1.0.0.0"; + } + else if (String::Equals(_property, gcnew String(INSTALLPROPERTY_LANGUAGE))) + { + wzValue = L"1033"; + } + else if (String::Equals(_property, gcnew String(INSTALLPROPERTY_ASSIGNMENTTYPE))) + { + wzValue = L"1"; + } + else if (String::Equals(_property, gcnew String(INSTALLPROPERTY_PRODUCTSTATE))) + { + // try again in per-user context + er = ERROR_UNKNOWN_PRODUCT; + } + } + else if (String::Equals(productCode, gcnew String(L"{600D0000-1000-0000-0000-000000000000}"))) + { + static BOOL fFlipp = FALSE; + if (fFlipp) + { + if (String::Equals(_property, gcnew String(INSTALLPROPERTY_VERSIONSTRING))) + { + wzValue = L"1.0.0.0"; + } + } + else + { + *pcchValue = MAX_PATH * 2; + er = ERROR_MORE_DATA; + } + fFlipp = !fFlipp; + } + break; + } + + if (wzValue) + { + hr = ::StringCchCopyW(szValue, *pcchValue, wzValue); + if (STRSAFE_E_INSUFFICIENT_BUFFER == hr) + { + *pcchValue = lstrlenW(wzValue); + er = ERROR_MORE_DATA; + } + else if (SUCCEEDED(hr)) + { + er = ERROR_SUCCESS; + } + } + + return er; +} diff --git a/src/burn/test/BurnUnitTest/TestData/CacheTest/CacheSignatureTest.File b/src/burn/test/BurnUnitTest/TestData/CacheTest/CacheSignatureTest.File new file mode 100644 index 00000000..896ac017 --- /dev/null +++ b/src/burn/test/BurnUnitTest/TestData/CacheTest/CacheSignatureTest.File @@ -0,0 +1 @@ +This file has a known hash. \ No newline at end of file diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/BasicFunctionality_BundleA_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/BasicFunctionality_BundleA_manifest.xml new file mode 100644 index 00000000..65e3c63d --- /dev/null +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/BasicFunctionality_BundleA_manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/MsiTransaction_BundleAv1_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/MsiTransaction_BundleAv1_manifest.xml new file mode 100644 index 00000000..cca9a982 --- /dev/null +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/MsiTransaction_BundleAv1_manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_manifest.xml new file mode 100644 index 00000000..996976b2 --- /dev/null +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/burn/test/BurnUnitTest/VariableHelpers.cpp b/src/burn/test/BurnUnitTest/VariableHelpers.cpp new file mode 100644 index 00000000..40f958f8 --- /dev/null +++ b/src/burn/test/BurnUnitTest/VariableHelpers.cpp @@ -0,0 +1,217 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +using namespace System; +using namespace Xunit; + + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + void VariableSetStringHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable, LPCWSTR wzValue, BOOL fFormatted) + { + HRESULT hr = S_OK; + + hr = VariableSetString(pVariables, wzVariable, wzValue, FALSE, fFormatted); + TestThrowOnFailure2(hr, L"Failed to set %s to: %s", wzVariable, wzValue); + } + + void VariableSetNumericHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable, LONGLONG llValue) + { + HRESULT hr = S_OK; + + hr = VariableSetNumeric(pVariables, wzVariable, llValue, FALSE); + TestThrowOnFailure2(hr, L"Failed to set %s to: %I64d", wzVariable, llValue); + } + + void VariableSetVersionHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable, LPCWSTR wzValue) + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion = NULL; + + try + { + hr = VerParseVersion(wzValue, 0, FALSE, &pVersion); + TestThrowOnFailure1(hr, L"Failed to parse version '%ls'", wzValue); + + hr = VariableSetVersion(pVariables, wzVariable, pVersion, FALSE); + TestThrowOnFailure2(hr, L"Failed to set %s to: '%ls'", wzVariable, wzValue); + } + finally + { + ReleaseVerutilVersion(pVersion); + } + } + + String^ VariableGetStringHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable) + { + HRESULT hr = S_OK; + LPWSTR scz = NULL; + try + { + hr = VariableGetString(pVariables, wzVariable, &scz); + TestThrowOnFailure1(hr, L"Failed to get: %s", wzVariable); + + return gcnew String(scz); + } + finally + { + ReleaseStr(scz); + } + } + + __int64 VariableGetNumericHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable) + { + HRESULT hr = S_OK; + LONGLONG llValue = 0; + + hr = VariableGetNumeric(pVariables, wzVariable, &llValue); + TestThrowOnFailure1(hr, L"Failed to get: %s", wzVariable); + + return llValue; + } + + String^ VariableGetVersionHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable) + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pValue = NULL; + + try + { + hr = VariableGetVersion(pVariables, wzVariable, &pValue); + TestThrowOnFailure1(hr, L"Failed to get: %s", wzVariable); + + return gcnew String(pValue->sczVersion); + } + finally + { + ReleaseVerutilVersion(pValue); + } + } + + String^ VariableGetFormattedHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable, BOOL* pfContainsHiddenVariable) + { + HRESULT hr = S_OK; + LPWSTR scz = NULL; + try + { + hr = VariableGetFormatted(pVariables, wzVariable, &scz, pfContainsHiddenVariable); + TestThrowOnFailure1(hr, L"Failed to get formatted: %s", wzVariable); + + return gcnew String(scz); + } + finally + { + ReleaseStr(scz); + } + } + + String^ VariableFormatStringHelper(BURN_VARIABLES* pVariables, LPCWSTR wzIn) + { + HRESULT hr = S_OK; + LPWSTR scz = NULL; + try + { + hr = VariableFormatString(pVariables, wzIn, &scz, NULL); + TestThrowOnFailure1(hr, L"Failed to format string: '%s'", wzIn); + + return gcnew String(scz); + } + finally + { + ReleaseStr(scz); + } + } + + String^ VariableEscapeStringHelper(LPCWSTR wzIn) + { + HRESULT hr = S_OK; + LPWSTR scz = NULL; + try + { + hr = VariableEscapeString(wzIn, &scz); + TestThrowOnFailure1(hr, L"Failed to escape string: '%s'", wzIn); + + return gcnew String(scz); + } + finally + { + ReleaseStr(scz); + } + } + + bool EvaluateConditionHelper(BURN_VARIABLES* pVariables, LPCWSTR wzCondition) + { + HRESULT hr = S_OK; + BOOL f = FALSE; + + hr = ConditionEvaluate(pVariables, wzCondition, &f); + TestThrowOnFailure1(hr, L"Failed to evaluate condition: '%s'", wzCondition); + + return f ? true : false; + } + + bool EvaluateFailureConditionHelper(BURN_VARIABLES* pVariables, LPCWSTR wzCondition) + { + HRESULT hr = S_OK; + BOOL f = FALSE; + + hr = ConditionEvaluate(pVariables, wzCondition, &f); + return E_INVALIDDATA == hr ? true : false; + } + + bool VariableExistsHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable) + { + HRESULT hr = S_OK; + BURN_VARIANT value = { }; + + try + { + hr = VariableGetVariant(pVariables, wzVariable, &value); + if (E_NOTFOUND == hr || value.Type == BURN_VARIANT_TYPE_NONE) + { + return false; + } + else + { + TestThrowOnFailure1(hr, L"Failed to find variable: '%s'", wzVariable); + return true; + } + } + finally + { + BVariantUninitialize(&value); + } + } + + int VariableGetTypeHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable) + { + HRESULT hr = S_OK; + BURN_VARIANT value = { }; + + try + { + hr = VariableGetVariant(pVariables, wzVariable, &value); + TestThrowOnFailure1(hr, L"Failed to find variable: '%s'", wzVariable); + + return (int)value.Type; + } + finally + { + BVariantUninitialize(&value); + } + } +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/VariableHelpers.h b/src/burn/test/BurnUnitTest/VariableHelpers.h new file mode 100644 index 00000000..d460c60f --- /dev/null +++ b/src/burn/test/BurnUnitTest/VariableHelpers.h @@ -0,0 +1,36 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + + +void VariableSetStringHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable, LPCWSTR wzValue, BOOL fFormatted); +void VariableSetNumericHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable, LONGLONG llValue); +void VariableSetVersionHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable, LPCWSTR wzValue); +System::String^ VariableGetStringHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable); +__int64 VariableGetNumericHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable); +System::String^ VariableGetVersionHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable); +System::String^ VariableGetFormattedHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable, BOOL* pfContainsHiddenVariable); +System::String^ VariableFormatStringHelper(BURN_VARIABLES* pVariables, LPCWSTR wzIn); +System::String^ VariableEscapeStringHelper(LPCWSTR wzIn); +bool EvaluateConditionHelper(BURN_VARIABLES* pVariables, LPCWSTR wzCondition); +bool EvaluateFailureConditionHelper(BURN_VARIABLES* pVariables, LPCWSTR wzCondition); +bool VariableExistsHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable); +int VariableGetTypeHelper(BURN_VARIABLES* pVariables, LPCWSTR wzVariable); + + +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/VariableTest.cpp b/src/burn/test/BurnUnitTest/VariableTest.cpp new file mode 100644 index 00000000..5c9dce03 --- /dev/null +++ b/src/burn/test/BurnUnitTest/VariableTest.cpp @@ -0,0 +1,532 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" +#undef GetTempPath +#undef GetEnvironmentVariable + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace Xunit; + + public ref class VariableTest : BurnUnitTest + { + public: + VariableTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture) + { + } + + [Fact] + void VariablesBasicTest() + { + HRESULT hr = S_OK; + BURN_VARIABLES variables = { }; + try + { + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // set variables + VariableSetStringHelper(&variables, L"PROP1", L"VAL1", FALSE); + VariableSetNumericHelper(&variables, L"PROP2", 2); + VariableSetStringHelper(&variables, L"PROP5", L"VAL5", FALSE); + VariableSetStringHelper(&variables, L"PROP3", L"VAL3", FALSE); + VariableSetStringHelper(&variables, L"PROP4", L"VAL4", FALSE); + VariableSetStringHelper(&variables, L"PROP6", L"VAL6", FALSE); + VariableSetStringHelper(&variables, L"PROP7", L"7", FALSE); + VariableSetVersionHelper(&variables, L"PROP8", L"1.1.0.0"); + VariableSetStringHelper(&variables, L"PROP9", L"[VAL9]", TRUE); + + // set overwritten variables + VariableSetStringHelper(&variables, L"OVERWRITTEN_STRING", L"ORIGINAL", FALSE); + VariableSetNumericHelper(&variables, L"OVERWRITTEN_STRING", 42); + + VariableSetNumericHelper(&variables, L"OVERWRITTEN_NUMBER", 5); + VariableSetStringHelper(&variables, L"OVERWRITTEN_NUMBER", L"NEW", FALSE); + + // get and verify variable values + Assert::Equal(gcnew String(L"VAL1"), VariableGetStringHelper(&variables, L"PROP1")); + Assert::Equal(2ll, VariableGetNumericHelper(&variables, L"PROP2")); + Assert::Equal(gcnew String(L"2"), VariableGetStringHelper(&variables, L"PROP2")); + Assert::Equal(gcnew String(L"VAL3"), VariableGetStringHelper(&variables, L"PROP3")); + Assert::Equal(gcnew String(L"VAL4"), VariableGetStringHelper(&variables, L"PROP4")); + Assert::Equal(gcnew String(L"VAL5"), VariableGetStringHelper(&variables, L"PROP5")); + Assert::Equal(gcnew String(L"VAL6"), VariableGetStringHelper(&variables, L"PROP6")); + Assert::Equal(7ll, VariableGetNumericHelper(&variables, L"PROP7")); + Assert::Equal(gcnew String(L"1.1.0.0"), VariableGetVersionHelper(&variables, L"PROP8")); + Assert::Equal(gcnew String(L"1.1.0.0"), VariableGetStringHelper(&variables, L"PROP8")); + Assert::Equal(gcnew String(L"[VAL9]"), VariableGetStringHelper(&variables, L"PROP9")); + + Assert::Equal(42ll, VariableGetNumericHelper(&variables, L"OVERWRITTEN_STRING")); + Assert::Equal(gcnew String(L"NEW"), VariableGetStringHelper(&variables, L"OVERWRITTEN_NUMBER")); + } + finally + { + VariablesUninitialize(&variables); + } + } + + [Fact] + void VariablesParseXmlTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_VARIABLES variables = { }; + BOOL fContainsHiddenData = FALSE; + try + { + LPCWSTR wzDocument = + L"" + L" "; + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = VariablesParseFromXml(&variables, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse variables from XML."); + + // get and verify variable values + Assert::Equal((int)BURN_VARIANT_TYPE_NUMERIC, VariableGetTypeHelper(&variables, L"Var1")); + Assert::Equal((int)BURN_VARIANT_TYPE_STRING, VariableGetTypeHelper(&variables, L"Var2")); + Assert::Equal((int)BURN_VARIANT_TYPE_VERSION, VariableGetTypeHelper(&variables, L"Var3")); + Assert::Equal((int)BURN_VARIANT_TYPE_NONE, VariableGetTypeHelper(&variables, L"Var4")); + Assert::Equal((int)BURN_VARIANT_TYPE_FORMATTED, VariableGetTypeHelper(&variables, L"Var6")); + + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Var1")); + Assert::Equal(gcnew String(L"String value."), VariableGetStringHelper(&variables, L"Var2")); + Assert::Equal(gcnew String(L"1.2.3.4"), VariableGetVersionHelper(&variables, L"Var3")); + Assert::Equal(gcnew String(L"[Formatted]"), VariableGetStringHelper(&variables, L"Var6")); + Assert::Equal(gcnew String(L"supersecret"), VariableGetFormattedHelper(&variables, L"Formatted", &fContainsHiddenData)); + Assert::Equal(TRUE, fContainsHiddenData); + Assert::Equal(gcnew String(L"supersecret"), VariableGetFormattedHelper(&variables, L"Var6", &fContainsHiddenData)); + Assert::Equal(TRUE, fContainsHiddenData); + Assert::Equal(gcnew String(L"String value."), VariableGetFormattedHelper(&variables, L"Var2", &fContainsHiddenData)); + Assert::Equal(FALSE, fContainsHiddenData); + } + finally + { + ReleaseObject(pixeBundle); + VariablesUninitialize(&variables); + } + } + + [Fact] + void VariablesFormatTest() + { + HRESULT hr = S_OK; + BURN_VARIABLES variables = { }; + LPWSTR scz = NULL; + SIZE_T cch = 0; + BOOL fContainsHiddenData = FALSE; + try + { + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // set variables + VariableSetStringHelper(&variables, L"PROP1", L"VAL1", FALSE); + VariableSetStringHelper(&variables, L"PROP2", L"VAL2", FALSE); + VariableSetNumericHelper(&variables, L"PROP3", 3); + VariableSetStringHelper(&variables, L"PROP4", L"[PROP1]", FALSE); + VariableSetStringHelper(&variables, L"PROP5", L"[PROP2]", FALSE); + VariableSetStringHelper(&variables, L"PROP6", L"[PROP4]", TRUE); + VariableSetStringHelper(&variables, L"PROP7", L"[PROP5]", TRUE); + + // test string formatting + Assert::Equal(gcnew String(L"NOPROP"), VariableFormatStringHelper(&variables, L"NOPROP")); + Assert::Equal(gcnew String(L"VAL1"), VariableFormatStringHelper(&variables, L"[PROP1]")); + Assert::Equal(gcnew String(L" VAL1 "), VariableFormatStringHelper(&variables, L" [PROP1] ")); + Assert::Equal(gcnew String(L"PRE VAL1"), VariableFormatStringHelper(&variables, L"PRE [PROP1]")); + Assert::Equal(gcnew String(L"VAL1 POST"), VariableFormatStringHelper(&variables, L"[PROP1] POST")); + Assert::Equal(gcnew String(L"PRE VAL1 POST"), VariableFormatStringHelper(&variables, L"PRE [PROP1] POST")); + Assert::Equal(gcnew String(L"VAL1 MID VAL2"), VariableFormatStringHelper(&variables, L"[PROP1] MID [PROP2]")); + Assert::Equal(gcnew String(L""), VariableFormatStringHelper(&variables, L"[NONE]")); + Assert::Equal(gcnew String(L""), VariableFormatStringHelper(&variables, L"[prop1]")); + Assert::Equal(gcnew String(L"["), VariableFormatStringHelper(&variables, L"[\\[]")); + Assert::Equal(gcnew String(L"]"), VariableFormatStringHelper(&variables, L"[\\]]")); + Assert::Equal(gcnew String(L"[]"), VariableFormatStringHelper(&variables, L"[]")); + Assert::Equal(gcnew String(L"[NONE"), VariableFormatStringHelper(&variables, L"[NONE")); + Assert::Equal(gcnew String(L"VAL2"), VariableGetFormattedHelper(&variables, L"PROP2", &fContainsHiddenData)); + Assert::Equal(FALSE, fContainsHiddenData); + Assert::Equal(gcnew String(L"3"), VariableGetFormattedHelper(&variables, L"PROP3", &fContainsHiddenData)); + Assert::Equal(FALSE, fContainsHiddenData); + Assert::Equal(gcnew String(L"[PROP1]"), VariableGetFormattedHelper(&variables, L"PROP4", &fContainsHiddenData)); + Assert::Equal(FALSE, fContainsHiddenData); + Assert::Equal(gcnew String(L"[PROP2]"), VariableGetFormattedHelper(&variables, L"PROP5", &fContainsHiddenData)); + Assert::Equal(FALSE, fContainsHiddenData); + Assert::Equal(gcnew String(L"[PROP1]"), VariableGetFormattedHelper(&variables, L"PROP6", &fContainsHiddenData)); + Assert::Equal(FALSE, fContainsHiddenData); + Assert::Equal(gcnew String(L"[PROP2]"), VariableGetFormattedHelper(&variables, L"PROP7", &fContainsHiddenData)); + Assert::Equal(FALSE, fContainsHiddenData); + + hr = VariableFormatString(&variables, L"PRE [PROP1] POST", &scz, &cch); + TestThrowOnFailure(hr, L"Failed to format string"); + + Assert::Equal((SIZE_T)lstrlenW(scz), cch); + + hr = VariableFormatString(&variables, L"PRE [PROP1] POST", NULL, &cch); + TestThrowOnFailure(hr, L"Failed to format string"); + + Assert::Equal((SIZE_T)lstrlenW(scz), cch); + } + finally + { + VariablesUninitialize(&variables); + ReleaseStr(scz); + } + } + + [Fact] + void VariablesEscapeTest() + { + // test string escaping + Assert::Equal(gcnew String(L"[\\[]"), VariableEscapeStringHelper(L"[")); + Assert::Equal(gcnew String(L"[\\]]"), VariableEscapeStringHelper(L"]")); + Assert::Equal(gcnew String(L" [\\[]TEXT[\\]] "), VariableEscapeStringHelper(L" [TEXT] ")); + } + + [Fact] + void VariablesConditionTest() + { + HRESULT hr = S_OK; + BURN_VARIABLES variables = { }; + try + { + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // set variables + VariableSetStringHelper(&variables, L"PROP1", L"VAL1", FALSE); + VariableSetStringHelper(&variables, L"PROP2", L"VAL2", FALSE); + VariableSetStringHelper(&variables, L"PROP3", L"VAL3", FALSE); + VariableSetStringHelper(&variables, L"PROP4", L"BEGIN MID END", FALSE); + VariableSetNumericHelper(&variables, L"PROP5", 5); + VariableSetNumericHelper(&variables, L"PROP6", 6); + VariableSetStringHelper(&variables, L"PROP7", L"", FALSE); + VariableSetNumericHelper(&variables, L"PROP8", 0); + VariableSetStringHelper(&variables, L"_PROP9", L"VAL9", FALSE); + VariableSetNumericHelper(&variables, L"PROP10", -10); + VariableSetNumericHelper(&variables, L"PROP11", 9223372036854775807ll); + VariableSetNumericHelper(&variables, L"PROP12", -9223372036854775808ll); + VariableSetNumericHelper(&variables, L"PROP13", 0x00010000); + VariableSetNumericHelper(&variables, L"PROP14", 0x00000001); + VariableSetNumericHelper(&variables, L"PROP15", 0x00010001); + VariableSetVersionHelper(&variables, L"PROP16", L"0.0.0.0"); + VariableSetVersionHelper(&variables, L"PROP17", L"1.0.0.0"); + VariableSetVersionHelper(&variables, L"PROP18", L"1.1.0.0"); + VariableSetVersionHelper(&variables, L"PROP19", L"1.1.1.0"); + VariableSetVersionHelper(&variables, L"PROP20", L"1.1.1.1"); + VariableSetNumericHelper(&variables, L"vPROP21", 1); + VariableSetVersionHelper(&variables, L"PROP22", L"65535.65535.65535.65535"); + VariableSetStringHelper(&variables, L"PROP23", L"1.1.1", FALSE); + VariableSetStringHelper(&variables, L"PROP24", L"[PROP1]", TRUE); + VariableSetStringHelper(&variables, L"PROP25", L"[PROP7]", TRUE); + VariableSetStringHelper(&variables, L"PROP26", L"[PROP8]", TRUE); + VariableSetStringHelper(&variables, L"PROP27", L"[PROP16]", TRUE); + + // test conditions + Assert::True(EvaluateConditionHelper(&variables, L"PROP1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP5")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP7")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP8")); + Assert::True(EvaluateConditionHelper(&variables, L"_PROP9")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP16")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP17")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP24=\"VAL1\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP25")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP26")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP27")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 = \"VAL1\"")); + Assert::False(EvaluateConditionHelper(&variables, L"NONE = \"NOT\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 <> \"VAL1\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 ~<> \"VAL1\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 ~<> \"Val1\"")); + Assert::True(EvaluateConditionHelper(&variables, L"NONE <> \"NOT\"")); + Assert::True(EvaluateConditionHelper(&variables, L"NONE ~<> \"NOT\"")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 ~= \"val1\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 = \"val1\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 ~<> \"val1\"")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 <> \"val1\"")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP5 = 5")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP5 = 0")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP5 <> 5")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP5 <> 0")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP10 = -10")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP10 <> -10")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP17 = v1")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP17 = v0")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP17 <> v1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP17 <> v0")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP16 = v0")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP17 = v1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP18 = v1.1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP19 = v1.1.1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP20 = v1.1.1.1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP20 > v1.1.1.1.0")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP20 > v1.1.1.1.1")); + Assert::True(EvaluateConditionHelper(&variables, L"vPROP21 = 1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP23 = v1.1.1")); + Assert::True(EvaluateConditionHelper(&variables, L"v1.1.1 = PROP23")); + Assert::False(EvaluateConditionHelper(&variables, L"v1.1.1<>PROP23")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 <> v1.1.1")); + Assert::True(EvaluateConditionHelper(&variables, L"v1.1.1 <> PROP1")); + + Assert::False(EvaluateConditionHelper(&variables, L"PROP11 = 9223372036854775806")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP11 = 9223372036854775807")); + Assert::True(EvaluateFailureConditionHelper(&variables, L"PROP11 = 9223372036854775808")); + Assert::True(EvaluateFailureConditionHelper(&variables, L"PROP11 = 92233720368547758070000")); + + Assert::False(EvaluateConditionHelper(&variables, L"PROP12 = -9223372036854775807")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP12 = -9223372036854775808")); + Assert::True(EvaluateFailureConditionHelper(&variables, L"PROP12 = -9223372036854775809")); + Assert::True(EvaluateFailureConditionHelper(&variables, L"PROP12 = -92233720368547758080000")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP22 = v65535.65535.65535.65535")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP22 < v65536.65535.65535.65535")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP22 < v65535.655350000.65535.65535")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP5 < 6")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP5 < 5")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP5 > 4")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP5 > 5")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP5 <= 6")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP5 <= 5")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP5 <= 4")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP5 >= 4")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP5 >= 5")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP5 >= 6")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP4 << \"BEGIN\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP4 << \"END\"")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP4 >> \"END\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP4 >> \"BEGIN\"")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP4 >< \"MID\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP4 >< \"NONE\"")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP16 < v1.1")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP16 < v0")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP17 > v0.12")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP17 > v1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP18 >= v1.0")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP18 >= v1.1")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP18 >= v2.1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP19 <= v1.1234.1")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP19 <= v1.1.1")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP19 <= v1.0.123")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP6 = \"6\"")); + Assert::True(EvaluateConditionHelper(&variables, L"\"6\" = PROP6")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP6 = \"ABC\"")); + Assert::False(EvaluateConditionHelper(&variables, L"\"ABC\" = PROP6")); + Assert::False(EvaluateConditionHelper(&variables, L"\"ABC\" = PROP6")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP13 << 1")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP13 << 0")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP14 >> 1")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP14 >> 0")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP15 >< 65537")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP15 >< 0")); + + Assert::False(EvaluateConditionHelper(&variables, L"NOT PROP1")); + Assert::True(EvaluateConditionHelper(&variables, L"NOT (PROP1 <> \"VAL1\")")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 = \"VAL1\" AND PROP2 = \"VAL2\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 = \"VAL1\" AND PROP2 = \"NOT\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 = \"NOT\" AND PROP2 = \"VAL2\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 = \"NOT\" AND PROP2 = \"NOT\"")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 = \"VAL1\" OR PROP2 = \"VAL2\"")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 = \"VAL1\" OR PROP2 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 = \"NOT\" OR PROP2 = \"VAL2\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 = \"NOT\" OR PROP2 = \"NOT\"")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 = \"VAL1\" AND PROP2 = \"VAL2\" OR PROP3 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 = \"VAL1\" AND PROP2 = \"NOT\" OR PROP3 = \"VAL3\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP1 = \"VAL1\" AND PROP2 = \"NOT\" OR PROP3 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP1 = \"VAL1\" AND (PROP2 = \"NOT\" OR PROP3 = \"VAL3\")")); + Assert::True(EvaluateConditionHelper(&variables, L"(PROP1 = \"VAL1\" AND PROP2 = \"VAL2\") OR PROP3 = \"NOT\"")); + + Assert::True(EvaluateConditionHelper(&variables, L"PROP3 = \"NOT\" OR PROP1 = \"VAL1\" AND PROP2 = \"VAL2\"")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP3 = \"VAL3\" OR PROP1 = \"VAL1\" AND PROP2 = \"NOT\"")); + Assert::False(EvaluateConditionHelper(&variables, L"PROP3 = \"NOT\" OR PROP1 = \"VAL1\" AND PROP2 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(&variables, L"(PROP3 = \"NOT\" OR PROP1 = \"VAL1\") AND PROP2 = \"VAL2\"")); + Assert::True(EvaluateConditionHelper(&variables, L"PROP3 = \"NOT\" OR (PROP1 = \"VAL1\" AND PROP2 = \"VAL2\")")); + + Assert::True(EvaluateFailureConditionHelper(&variables, L"=")); + Assert::True(EvaluateFailureConditionHelper(&variables, L"(PROP1")); + Assert::True(EvaluateFailureConditionHelper(&variables, L"(PROP1 = \"")); + Assert::True(EvaluateFailureConditionHelper(&variables, L"1A")); + Assert::True(EvaluateFailureConditionHelper(&variables, L"*")); + + Assert::True(EvaluateFailureConditionHelper(&variables, L"1 == 1")); + } + finally + { + VariablesUninitialize(&variables); + } + } + + [Fact] + void VariablesSerializationTest() + { + HRESULT hr = S_OK; + BYTE* pbBuffer = NULL; + SIZE_T cbBuffer = 0; + SIZE_T iBuffer = 0; + BURN_VARIABLES variables1 = { }; + BURN_VARIABLES variables2 = { }; + try + { + // serialize + hr = VariableInitialize(&variables1); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + VariableSetStringHelper(&variables1, L"PROP1", L"VAL1", FALSE); + VariableSetNumericHelper(&variables1, L"PROP2", 2); + VariableSetVersionHelper(&variables1, L"PROP3", L"1.1.1.1"); + VariableSetStringHelper(&variables1, L"PROP4", L"VAL4", FALSE); + VariableSetStringHelper(&variables1, L"PROP5", L"[PROP1]", TRUE); + + hr = VariableSerialize(&variables1, FALSE, &pbBuffer, &cbBuffer); + TestThrowOnFailure(hr, L"Failed to serialize variables."); + + // deserialize + hr = VariableInitialize(&variables2); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + hr = VariableDeserialize(&variables2, FALSE, pbBuffer, cbBuffer, &iBuffer); + TestThrowOnFailure(hr, L"Failed to deserialize variables."); + + Assert::Equal(gcnew String(L"VAL1"), VariableGetStringHelper(&variables2, L"PROP1")); + Assert::Equal(2ll, VariableGetNumericHelper(&variables2, L"PROP2")); + Assert::Equal(gcnew String(L"1.1.1.1"), VariableGetVersionHelper(&variables2, L"PROP3")); + Assert::Equal(gcnew String(L"VAL4"), VariableGetStringHelper(&variables2, L"PROP4")); + Assert::Equal(gcnew String(L"[PROP1]"), VariableGetStringHelper(&variables2, L"PROP5")); + + Assert::Equal((int)BURN_VARIANT_TYPE_STRING, VariableGetTypeHelper(&variables2, L"PROP1")); + Assert::Equal((int)BURN_VARIANT_TYPE_NUMERIC, VariableGetTypeHelper(&variables2, L"PROP2")); + Assert::Equal((int)BURN_VARIANT_TYPE_VERSION, VariableGetTypeHelper(&variables2, L"PROP3")); + Assert::Equal((int)BURN_VARIANT_TYPE_STRING, VariableGetTypeHelper(&variables2, L"PROP4")); + Assert::Equal((int)BURN_VARIANT_TYPE_FORMATTED, VariableGetTypeHelper(&variables2, L"PROP5")); + } + finally + { + ReleaseBuffer(pbBuffer); + VariablesUninitialize(&variables1); + VariablesUninitialize(&variables2); + } + } + + [Fact] + void VariablesBuiltInTest() + { + HRESULT hr = S_OK; + BURN_VARIABLES variables = { }; + try + { + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + // VersionMsi + Assert::True(EvaluateConditionHelper(&variables, L"VersionMsi >= v1.1")); + + // VersionNT + Assert::True(EvaluateConditionHelper(&variables, L"VersionNT <> v0.0.0.0")); + + // VersionNT64 + if (nullptr == Environment::GetEnvironmentVariable("ProgramFiles(x86)")) + { + Assert::False(EvaluateConditionHelper(&variables, L"VersionNT64")); + } + else + { + Assert::True(EvaluateConditionHelper(&variables, L"VersionNT64")); + } + + // attempt to set a built-in property + hr = VariableSetString(&variables, L"VersionNT", L"VAL", FALSE, FALSE); + Assert::Equal(E_INVALIDARG, hr); + Assert::False(EvaluateConditionHelper(&variables, L"VersionNT = \"VAL\"")); + + VariableGetNumericHelper(&variables, L"NTProductType"); + VariableGetNumericHelper(&variables, L"NTSuiteBackOffice"); + VariableGetNumericHelper(&variables, L"NTSuiteDataCenter"); + VariableGetNumericHelper(&variables, L"NTSuiteEnterprise"); + VariableGetNumericHelper(&variables, L"NTSuitePersonal"); + VariableGetNumericHelper(&variables, L"NTSuiteSmallBusiness"); + VariableGetNumericHelper(&variables, L"NTSuiteSmallBusinessRestricted"); + VariableGetNumericHelper(&variables, L"NTSuiteWebServer"); + VariableGetNumericHelper(&variables, L"CompatibilityMode"); + VariableGetNumericHelper(&variables, L"Privileged"); + VariableGetNumericHelper(&variables, L"SystemLanguageID"); + VariableGetNumericHelper(&variables, L"TerminalServer"); + VariableGetNumericHelper(&variables, L"UserUILanguageID"); + VariableGetNumericHelper(&variables, L"UserLanguageID"); + + // known folders + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::ApplicationData) + "\\", VariableGetStringHelper(&variables, L"AppDataFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::CommonApplicationData) + "\\", VariableGetStringHelper(&variables, L"CommonAppDataFolder")); + + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::ProgramFiles) + "\\", VariableGetStringHelper(&variables, L"ProgramFilesFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::DesktopDirectory) + "\\", VariableGetStringHelper(&variables, L"DesktopFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::Favorites) + "\\", VariableGetStringHelper(&variables, L"FavoritesFolder")); + VariableGetStringHelper(&variables, L"FontsFolder"); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData) + "\\", VariableGetStringHelper(&variables, L"LocalAppDataFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::Personal) + "\\", VariableGetStringHelper(&variables, L"PersonalFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::Programs) + "\\", VariableGetStringHelper(&variables, L"ProgramMenuFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::SendTo) + "\\", VariableGetStringHelper(&variables, L"SendToFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::StartMenu) + "\\", VariableGetStringHelper(&variables, L"StartMenuFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::Startup) + "\\", VariableGetStringHelper(&variables, L"StartupFolder")); + VariableGetStringHelper(&variables, L"SystemFolder"); + VariableGetStringHelper(&variables, L"WindowsFolder"); + VariableGetStringHelper(&variables, L"WindowsVolume"); + + Assert::Equal(System::IO::Path::GetTempPath(), System::IO::Path::GetFullPath(VariableGetStringHelper(&variables, L"TempFolder"))); + + VariableGetStringHelper(&variables, L"AdminToolsFolder"); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::CommonProgramFiles) + "\\", VariableGetStringHelper(&variables, L"CommonFilesFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::MyPictures) + "\\", VariableGetStringHelper(&variables, L"MyPicturesFolder")); + Assert::Equal(Environment::GetFolderPath(Environment::SpecialFolder::Templates) + "\\", VariableGetStringHelper(&variables, L"TemplateFolder")); + + if (Environment::Is64BitOperatingSystem) + { + VariableGetStringHelper(&variables, L"ProgramFiles64Folder"); + VariableGetStringHelper(&variables, L"CommonFiles64Folder"); + VariableGetStringHelper(&variables, L"System64Folder"); + } + } + finally + { + VariablesUninitialize(&variables); + } + } + }; +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/VariantTest.cpp b/src/burn/test/BurnUnitTest/VariantTest.cpp new file mode 100644 index 00000000..43899a2b --- /dev/null +++ b/src/burn/test/BurnUnitTest/VariantTest.cpp @@ -0,0 +1,221 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace Xunit; + + public ref class VariantTest : BurnUnitTest + { + public: + VariantTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture) + { + } + + [Fact] + void VariantBasicTest() + { + BURN_VARIANT expectedVariants[10]; + BURN_VARIANT actualVariants[10]; + for (DWORD i = 0; i < 10; i++) + { + BVariantUninitialize(expectedVariants + i); + BVariantUninitialize(actualVariants + i); + } + + try + { + InitNumericValue(expectedVariants + 0, 2, FALSE, L"PROP1", actualVariants + 0); + InitStringValue(expectedVariants + 1, L"VAL2", FALSE, L"PROP2", actualVariants + 1); + InitVersionValue(expectedVariants + 2, L"1.1.0.0", FALSE, L"PROP3", actualVariants + 2); + InitNoneValue(expectedVariants + 3, FALSE, L"PROP4", actualVariants + 3); + InitNoneValue(expectedVariants + 4, TRUE, L"PROP5", actualVariants + 4); + InitVersionValue(expectedVariants + 5, L"1.1.1.0", TRUE, L"PROP6", actualVariants + 5); + InitStringValue(expectedVariants + 6, L"7", TRUE, L"PROP7", actualVariants + 6); + InitNumericValue(expectedVariants + 7, 11, TRUE, L"PROP8", actualVariants + 7); + InitFormattedValue(expectedVariants + 8, L"VAL9", FALSE, L"PROP9", actualVariants + 8); + InitFormattedValue(expectedVariants + 9, L"VAL10", TRUE, L"PROP10", actualVariants + 9); + + VerifyNumericValue(expectedVariants + 0, actualVariants + 0); + VerifyStringValue(expectedVariants + 1, actualVariants + 1); + VerifyVersionValue(expectedVariants + 2, actualVariants + 2); + VerifyNoneValue(expectedVariants + 3, actualVariants + 3); + VerifyNoneValue(expectedVariants + 4, actualVariants + 4); + VerifyVersionValue(expectedVariants + 5, actualVariants + 5); + VerifyStringValue(expectedVariants + 6, actualVariants + 6); + VerifyNumericValue(expectedVariants + 7, actualVariants + 7); + VerifyFormattedValue(expectedVariants + 8, actualVariants + 8); + VerifyFormattedValue(expectedVariants + 9, actualVariants + 9); + } + finally + { + for (DWORD i = 0; i < 10; i++) + { + BVariantUninitialize(expectedVariants + i); + BVariantUninitialize(actualVariants + i); + } + } + } + + private: + void InitFormattedValue(BURN_VARIANT* pValue, LPWSTR wzValue, BOOL /*fHidden*/, LPCWSTR wz, BURN_VARIANT* pActualValue) + { + HRESULT hr = S_OK; + pValue->Type = BURN_VARIANT_TYPE_FORMATTED; + + hr = StrAllocString(&pValue->sczValue, wzValue, 0); + NativeAssert::Succeeded(hr, "Failed to alloc string: {0}", wzValue); + + hr = BVariantCopy(pValue, pActualValue); + NativeAssert::Succeeded(hr, "Failed to copy variant {0}", wz); + } + + void InitNoneValue(BURN_VARIANT* pValue, BOOL /*fHidden*/, LPCWSTR wz, BURN_VARIANT* pActualValue) + { + HRESULT hr = S_OK; + pValue->Type = BURN_VARIANT_TYPE_NONE; + + hr = BVariantCopy(pValue, pActualValue); + NativeAssert::Succeeded(hr, "Failed to copy variant {0}", wz); + } + + void InitNumericValue(BURN_VARIANT* pValue, LONGLONG llValue, BOOL /*fHidden*/, LPCWSTR wz, BURN_VARIANT* pActualValue) + { + HRESULT hr = S_OK; + pValue->Type = BURN_VARIANT_TYPE_NUMERIC; + pValue->llValue = llValue; + + hr = BVariantCopy(pValue, pActualValue); + NativeAssert::Succeeded(hr, "Failed to copy variant {0}", wz); + } + + void InitStringValue(BURN_VARIANT* pValue, LPWSTR wzValue, BOOL /*fHidden*/, LPCWSTR wz, BURN_VARIANT* pActualValue) + { + HRESULT hr = S_OK; + pValue->Type = BURN_VARIANT_TYPE_STRING; + + hr = StrAllocString(&pValue->sczValue, wzValue, 0); + NativeAssert::Succeeded(hr, "Failed to alloc string: {0}", wzValue); + + hr = BVariantCopy(pValue, pActualValue); + NativeAssert::Succeeded(hr, "Failed to copy variant {0}", wz); + } + + void InitVersionValue(BURN_VARIANT* pValue, LPCWSTR wzValue, BOOL /*fHidden*/, LPCWSTR wz, BURN_VARIANT* pActualValue) + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion = NULL; + + try + { + hr = VerParseVersion(wzValue, 0, FALSE, &pVersion); + NativeAssert::Succeeded(hr, "Failed to parse version {0}", wzValue); + + pValue->Type = BURN_VARIANT_TYPE_VERSION; + pValue->pValue = pVersion; + pVersion = NULL; + + hr = BVariantCopy(pValue, pActualValue); + NativeAssert::Succeeded(hr, "Failed to copy variant {0}", wz); + } + finally + { + ReleaseVerutilVersion(pVersion); + } + } + + void VerifyFormattedValue(BURN_VARIANT* pExpectedValue, BURN_VARIANT* pActualValue) + { + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + NativeAssert::Equal(BURN_VARIANT_TYPE_FORMATTED, pExpectedValue->Type); + NativeAssert::Equal(BURN_VARIANT_TYPE_FORMATTED, pActualValue->Type); + + try + { + hr = BVariantGetString(pActualValue, &sczValue); + NativeAssert::Succeeded(hr, "Failed to get string value"); + + NativeAssert::StringEqual(pExpectedValue->sczValue, sczValue); + } + finally + { + ReleaseStr(sczValue); + } + } + + void VerifyNumericValue(BURN_VARIANT* pExpectedValue, BURN_VARIANT* pActualValue) + { + HRESULT hr = S_OK; + LONGLONG llValue = 0; + NativeAssert::Equal(BURN_VARIANT_TYPE_NUMERIC, pExpectedValue->Type); + NativeAssert::Equal(BURN_VARIANT_TYPE_NUMERIC, pActualValue->Type); + + hr = BVariantGetNumeric(pActualValue, &llValue); + NativeAssert::Succeeded(hr, "Failed to get numeric value"); + + NativeAssert::Equal(pExpectedValue->llValue, llValue); + } + + void VerifyNoneValue(BURN_VARIANT* pExpectedValue, BURN_VARIANT* pActualValue) + { + NativeAssert::Equal(BURN_VARIANT_TYPE_NONE, pExpectedValue->Type); + NativeAssert::Equal(BURN_VARIANT_TYPE_NONE, pActualValue->Type); + NativeAssert::Equal(pExpectedValue->llValue, pActualValue->llValue); + } + + void VerifyStringValue(BURN_VARIANT* pExpectedValue, BURN_VARIANT* pActualValue) + { + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + NativeAssert::Equal(BURN_VARIANT_TYPE_STRING, pExpectedValue->Type); + NativeAssert::Equal(BURN_VARIANT_TYPE_STRING, pActualValue->Type); + + try + { + hr = BVariantGetString(pActualValue, &sczValue); + NativeAssert::Succeeded(hr, "Failed to get string value"); + + NativeAssert::StringEqual(pExpectedValue->sczValue, sczValue); + } + finally + { + ReleaseStr(sczValue); + } + } + + void VerifyVersionValue(BURN_VARIANT* pExpectedValue, BURN_VARIANT* pActualValue) + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pValue = NULL; + NativeAssert::Equal(BURN_VARIANT_TYPE_VERSION, pExpectedValue->Type); + NativeAssert::Equal(BURN_VARIANT_TYPE_VERSION, pActualValue->Type); + + try + { + hr = BVariantGetVersion(pActualValue, &pValue); + NativeAssert::Succeeded(hr, "Failed to get version value"); + + NativeAssert::StringEqual(pExpectedValue->pValue->sczVersion, pActualValue->pValue->sczVersion); + } + finally + { + ReleaseVerutilVersion(pValue); + } + } + }; +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/packages.config b/src/burn/test/BurnUnitTest/packages.config new file mode 100644 index 00000000..1d36c387 --- /dev/null +++ b/src/burn/test/BurnUnitTest/packages.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/burn/test/BurnUnitTest/precomp.cpp b/src/burn/test/BurnUnitTest/precomp.cpp new file mode 100644 index 00000000..37664a1c --- /dev/null +++ b/src/burn/test/BurnUnitTest/precomp.cpp @@ -0,0 +1,3 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" diff --git a/src/burn/test/BurnUnitTest/precomp.h b/src/burn/test/BurnUnitTest/precomp.h new file mode 100644 index 00000000..d2b57d61 --- /dev/null +++ b/src/burn/test/BurnUnitTest/precomp.h @@ -0,0 +1,79 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "wininet.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "BootstrapperEngine.h" +#include "BootstrapperApplication.h" +#include "BundleExtensionEngine.h" +#include "BundleExtension.h" + +#include "platform.h" +#include "variant.h" +#include "variable.h" +#include "condition.h" +#include "section.h" +#include "approvedexe.h" +#include "container.h" +#include "payload.h" +#include "cabextract.h" +#include "burnextension.h" +#include "search.h" +#include "userexperience.h" +#include "package.h" +#include "update.h" +#include "pseudobundle.h" +#include "registration.h" +#include "plan.h" +#include "pipe.h" +#include "logging.h" +#include "core.h" +#include "cache.h" +#include "apply.h" +#include "exeengine.h" +#include "msiengine.h" +#include "mspengine.h" +#include "msuengine.h" +#include "dependency.h" +#include "elevation.h" +#include "embedded.h" +#include "manifest.h" +#include "splashscreen.h" +#include "detect.h" + +#pragma managed +#include + +#include "BurnTestException.h" +#include "BurnTestFixture.h" +#include "BurnUnitTest.h" +#include "VariableHelpers.h" +#include "ManifestHelpers.h" -- cgit v1.2.3-55-g6feb