aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2022-08-03 14:55:23 -0500
committerSean Hall <r.sean.hall@gmail.com>2022-08-03 15:57:24 -0500
commit124fef398a26bc8e139e889a2345602d2478590c (patch)
tree002c77e0c1d72b0cc0e46bed3c6f02d4179625fe
parenta896fec453056aa5e1ad803b04a672d2dceda981 (diff)
downloadwix-124fef398a26bc8e139e889a2345602d2478590c.tar.gz
wix-124fef398a26bc8e139e889a2345602d2478590c.tar.bz2
wix-124fef398a26bc8e139e889a2345602d2478590c.zip
Add ability to skip a local path candidate if it failed verification.
Fixes 6818
-rw-r--r--src/burn/engine/apply.cpp137
-rw-r--r--src/burn/engine/cache.cpp70
-rw-r--r--src/burn/engine/cache.h5
-rw-r--r--src/burn/engine/container.cpp1
-rw-r--r--src/burn/engine/container.h4
-rw-r--r--src/burn/engine/elevation.cpp23
-rw-r--r--src/burn/engine/payload.cpp1
-rw-r--r--src/burn/engine/payload.h3
-rw-r--r--src/burn/engine/plan.cpp6
-rw-r--r--src/test/burn/TestBA/TestBA.cs51
-rw-r--r--src/test/burn/TestData/LayoutTests/BundleB/BundleB.wixproj17
-rw-r--r--src/test/burn/TestData/LayoutTests/BundleB/BundleB.wxs10
-rw-r--r--src/test/burn/WixToolsetTest.BurnE2E/LayoutTests.cs93
-rw-r--r--src/test/burn/WixToolsetTest.BurnE2E/Utilities/TestBAController.cs53
14 files changed, 444 insertions, 30 deletions
diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp
index dd99a5cd..31d756a8 100644
--- a/src/burn/engine/apply.cpp
+++ b/src/burn/engine/apply.cpp
@@ -38,7 +38,7 @@ typedef struct _BURN_CACHE_CONTEXT
38 LPWSTR* rgSearchPaths; 38 LPWSTR* rgSearchPaths;
39 DWORD cSearchPaths; 39 DWORD cSearchPaths;
40 DWORD cSearchPathsMax; 40 DWORD cSearchPathsMax;
41 LPWSTR sczLastUsedFolderCandidate; 41 LPWSTR sczLocalAcquisitionSourcePath;
42} BURN_CACHE_CONTEXT; 42} BURN_CACHE_CONTEXT;
43 43
44typedef struct _BURN_CACHE_PROGRESS_CONTEXT 44typedef struct _BURN_CACHE_PROGRESS_CONTEXT
@@ -92,6 +92,16 @@ static HRESULT ApplyCachePackage(
92 __in BURN_CACHE_CONTEXT* pContext, 92 __in BURN_CACHE_CONTEXT* pContext,
93 __in BURN_PACKAGE* pPackage 93 __in BURN_PACKAGE* pPackage
94 ); 94 );
95static void FinalizeContainerAcquisition(
96 __in BURN_CACHE_CONTEXT* pContext,
97 __in BURN_CONTAINER* pContainer,
98 __in BOOL fSuccess
99 );
100static void FinalizePayloadAcquisition(
101 __in BURN_CACHE_CONTEXT* pContext,
102 __in BURN_PAYLOAD* pPayload,
103 __in BOOL fSuccess
104 );
95static HRESULT ApplyExtractContainer( 105static HRESULT ApplyExtractContainer(
96 __in BURN_CACHE_CONTEXT* pContext, 106 __in BURN_CACHE_CONTEXT* pContext,
97 __in BURN_CONTAINER* pContainer 107 __in BURN_CONTAINER* pContainer
@@ -667,7 +677,7 @@ LExit:
667 ReleaseNullStr(cacheContext.rgSearchPaths[i]); 677 ReleaseNullStr(cacheContext.rgSearchPaths[i]);
668 } 678 }
669 ReleaseMem(cacheContext.rgSearchPaths); 679 ReleaseMem(cacheContext.rgSearchPaths);
670 ReleaseStr(cacheContext.sczLastUsedFolderCandidate); 680 ReleaseStr(cacheContext.sczLocalAcquisitionSourcePath);
671 681
672 UserExperienceOnCacheComplete(pUX, hr); 682 UserExperienceOnCacheComplete(pUX, hr);
673 return hr; 683 return hr;
@@ -1038,6 +1048,46 @@ LExit:
1038 return hr; 1048 return hr;
1039} 1049}
1040 1050
1051static void FinalizeContainerAcquisition(
1052 __in BURN_CACHE_CONTEXT* pContext,
1053 __in BURN_CONTAINER* pContainer,
1054 __in BOOL fSuccess
1055 )
1056{
1057 ReleaseNullStr(pContainer->sczFailedLocalAcquisitionPath);
1058
1059 if (fSuccess)
1060 {
1061 ReleaseNullStr(pContext->sczLocalAcquisitionSourcePath);
1062 pContainer->fFailedVerificationFromAcquisition = FALSE;
1063 }
1064 else if (pContext->sczLocalAcquisitionSourcePath)
1065 {
1066 pContainer->sczFailedLocalAcquisitionPath = pContext->sczLocalAcquisitionSourcePath;
1067 pContext->sczLocalAcquisitionSourcePath = NULL;
1068 }
1069}
1070
1071static void FinalizePayloadAcquisition(
1072 __in BURN_CACHE_CONTEXT* pContext,
1073 __in BURN_PAYLOAD* pPayload,
1074 __in BOOL fSuccess
1075 )
1076{
1077 ReleaseNullStr(pPayload->sczFailedLocalAcquisitionPath);
1078
1079 if (fSuccess)
1080 {
1081 ReleaseNullStr(pContext->sczLocalAcquisitionSourcePath);
1082 pPayload->fFailedVerificationFromAcquisition = FALSE;
1083 }
1084 else if (pContext->sczLocalAcquisitionSourcePath)
1085 {
1086 pPayload->sczFailedLocalAcquisitionPath = pContext->sczLocalAcquisitionSourcePath;
1087 pContext->sczLocalAcquisitionSourcePath = NULL;
1088 }
1089}
1090
1041static HRESULT ApplyExtractContainer( 1091static HRESULT ApplyExtractContainer(
1042 __in BURN_CACHE_CONTEXT* pContext, 1092 __in BURN_CACHE_CONTEXT* pContext,
1043 __in BURN_CONTAINER* pContainer 1093 __in BURN_CONTAINER* pContainer
@@ -1066,10 +1116,11 @@ static HRESULT ApplyExtractContainer(
1066 hr = ExtractContainer(pContext, pContainer); 1116 hr = ExtractContainer(pContext, pContainer);
1067 LogExitOnFailure(hr, MSG_FAILED_EXTRACT_CONTAINER, "Failed to extract payloads from container: %ls to working path: %ls", pContainer->sczId, pContainer->sczUnverifiedPath); 1117 LogExitOnFailure(hr, MSG_FAILED_EXTRACT_CONTAINER, "Failed to extract payloads from container: %ls to working path: %ls", pContainer->sczId, pContainer->sczUnverifiedPath);
1068 1118
1069 if (pContext->sczLastUsedFolderCandidate) 1119 if (pContext->sczLocalAcquisitionSourcePath)
1070 { 1120 {
1071 // We successfully copied from a source location, set that as the last used source. 1121 // We successfully copied from a source location, set that as the last used source.
1072 CacheSetLastUsedSource(pContext->pVariables, pContext->sczLastUsedFolderCandidate, pContainer->sczFilePath); 1122 CacheSetLastUsedSource(pContext->pVariables, pContext->sczLocalAcquisitionSourcePath, pContainer->sczFilePath);
1123 ReleaseNullStr(pContext->sczLocalAcquisitionSourcePath);
1073 } 1124 }
1074 1125
1075 if (pContainer->qwExtractSizeTotal < pContainer->qwCommittedExtractProgress) 1126 if (pContainer->qwExtractSizeTotal < pContainer->qwCommittedExtractProgress)
@@ -1086,7 +1137,7 @@ static HRESULT ApplyExtractContainer(
1086 pContainer->qwCommittedExtractProgress = pContainer->qwExtractSizeTotal; 1137 pContainer->qwCommittedExtractProgress = pContainer->qwExtractSizeTotal;
1087 1138
1088LExit: 1139LExit:
1089 ReleaseNullStr(pContext->sczLastUsedFolderCandidate); 1140 FinalizeContainerAcquisition(pContext, pContainer, SUCCEEDED(hr));
1090 1141
1091 return hr; 1142 return hr;
1092} 1143}
@@ -1157,13 +1208,13 @@ static HRESULT ApplyLayoutContainer(
1157 ++cTryAgainAttempts; 1208 ++cTryAgainAttempts;
1158 pContext->qwSuccessfulCacheProgress -= pContainer->qwCommittedCacheProgress; 1209 pContext->qwSuccessfulCacheProgress -= pContainer->qwCommittedCacheProgress;
1159 pContainer->qwCommittedCacheProgress = 0; 1210 pContainer->qwCommittedCacheProgress = 0;
1160 ReleaseNullStr(pContext->sczLastUsedFolderCandidate); 1211 FinalizeContainerAcquisition(pContext, pContainer, FALSE);
1161 LogErrorId(hr, MSG_CACHE_RETRYING_CONTAINER, pContainer->sczId, NULL, NULL); 1212 LogErrorId(hr, MSG_CACHE_RETRYING_CONTAINER, pContainer->sczId, NULL, NULL);
1162 } 1213 }
1163 } 1214 }
1164 1215
1165LExit: 1216LExit:
1166 ReleaseNullStr(pContext->sczLastUsedFolderCandidate); 1217 FinalizeContainerAcquisition(pContext, pContainer, SUCCEEDED(hr));
1167 1218
1168 return hr; 1219 return hr;
1169} 1220}
@@ -1230,13 +1281,13 @@ static HRESULT ApplyProcessPayload(
1230 ++cTryAgainAttempts; 1281 ++cTryAgainAttempts;
1231 pContext->qwSuccessfulCacheProgress -= pPayloadGroupItem->qwCommittedCacheProgress; 1282 pContext->qwSuccessfulCacheProgress -= pPayloadGroupItem->qwCommittedCacheProgress;
1232 pPayloadGroupItem->qwCommittedCacheProgress = 0; 1283 pPayloadGroupItem->qwCommittedCacheProgress = 0;
1233 ReleaseNullStr(pContext->sczLastUsedFolderCandidate); 1284 FinalizePayloadAcquisition(pContext, pPayload, FALSE);
1234 LogErrorId(hr, MSG_CACHE_RETRYING_PAYLOAD, pPayload->sczKey, NULL, NULL); 1285 LogErrorId(hr, MSG_CACHE_RETRYING_PAYLOAD, pPayload->sczKey, NULL, NULL);
1235 } 1286 }
1236 } 1287 }
1237 1288
1238LExit: 1289LExit:
1239 ReleaseNullStr(pContext->sczLastUsedFolderCandidate); 1290 FinalizePayloadAcquisition(pContext, pPayload, SUCCEEDED(hr));
1240 1291
1241 return hr; 1292 return hr;
1242} 1293}
@@ -1570,6 +1621,7 @@ static HRESULT AcquireContainerOrPayload(
1570 BOOL fPreferExtract = FALSE; 1621 BOOL fPreferExtract = FALSE;
1571 DWORD64 qwFileSize = 0; 1622 DWORD64 qwFileSize = 0;
1572 BOOL fMinimumFileSize = FALSE; 1623 BOOL fMinimumFileSize = FALSE;
1624 BOOL fEqual = FALSE;
1573 1625
1574 if (pContainer) 1626 if (pContainer)
1575 { 1627 {
@@ -1615,21 +1667,44 @@ static HRESULT AcquireContainerOrPayload(
1615 // When a payload comes from a container, the container has the highest chance of being correct. 1667 // When a payload comes from a container, the container has the highest chance of being correct.
1616 // But we want to avoid extracting the container multiple times. 1668 // But we want to avoid extracting the container multiple times.
1617 // So only consider the destination path, which means the container was already extracted. 1669 // So only consider the destination path, which means the container was already extracted.
1618 if (IsValidLocalFile(pContext->rgSearchPaths[dwDestinationSearchPath], qwFileSize, fMinimumFileSize)) 1670 if (!pPayload->fFailedVerificationFromAcquisition && IsValidLocalFile(pContext->rgSearchPaths[dwDestinationSearchPath], qwFileSize, fMinimumFileSize))
1619 { 1671 {
1620 fFoundLocal = TRUE; 1672 fFoundLocal = TRUE;
1621 dwChosenSearchPath = dwDestinationSearchPath; 1673 dwChosenSearchPath = dwDestinationSearchPath;
1622 } 1674 }
1623 else // don't prefer the container if extracting it already failed. 1675 else // don't prefer the container if it was already extracted.
1624 { 1676 {
1625 fPreferExtract = SUCCEEDED(pPayload->pContainer->hrExtract); 1677 fPreferExtract = !pPayload->pContainer->fExtracted;
1626 } 1678 }
1627 } 1679 }
1628 1680
1629 if (!fFoundLocal) 1681 if (!fFoundLocal)
1630 { 1682 {
1683 BOOL fFailedVerificationFromAcquisition = pContainer ? pContainer->fFailedVerificationFromAcquisition : pPayload->fFailedVerificationFromAcquisition;
1684 LPCWSTR wzFailedLocalAcquisitionPath = pContainer ? pContainer->sczFailedLocalAcquisitionPath : pPayload->sczFailedLocalAcquisitionPath;
1685
1631 for (DWORD i = 0; i < pContext->cSearchPaths; ++i) 1686 for (DWORD i = 0; i < pContext->cSearchPaths; ++i)
1632 { 1687 {
1688 // If the file failed verification from acquisition then certain paths should not be considered.
1689 if (fFailedVerificationFromAcquisition)
1690 {
1691 if (i == dwDestinationSearchPath)
1692 {
1693 continue;
1694 }
1695
1696 if (wzFailedLocalAcquisitionPath)
1697 {
1698 hr = PathCompareCanonicalized(pContext->rgSearchPaths[i], wzFailedLocalAcquisitionPath, &fEqual);
1699 ExitOnFailure(hr, "Failed to compare '%ls' to '%ls'.", pContext->rgSearchPaths[i], wzFailedLocalAcquisitionPath);
1700
1701 if (fEqual)
1702 {
1703 continue;
1704 }
1705 }
1706 }
1707
1633 // If the file exists locally with the correct size, choose it. 1708 // If the file exists locally with the correct size, choose it.
1634 if (IsValidLocalFile(pContext->rgSearchPaths[i], qwFileSize, fMinimumFileSize)) 1709 if (IsValidLocalFile(pContext->rgSearchPaths[i], qwFileSize, fMinimumFileSize))
1635 { 1710 {
@@ -1703,7 +1778,7 @@ static HRESULT AcquireContainerOrPayload(
1703 ExitOnFailure(hr, "Failed to copy payload: %ls", wzPayloadId); 1778 ExitOnFailure(hr, "Failed to copy payload: %ls", wzPayloadId);
1704 1779
1705 // Store the source path so it can be used as the LastUsedFolder if it passes verification. 1780 // Store the source path so it can be used as the LastUsedFolder if it passes verification.
1706 pContext->sczLastUsedFolderCandidate = pContext->rgSearchPaths[dwChosenSearchPath]; 1781 pContext->sczLocalAcquisitionSourcePath = pContext->rgSearchPaths[dwChosenSearchPath];
1707 pContext->rgSearchPaths[dwChosenSearchPath] = NULL; 1782 pContext->rgSearchPaths[dwChosenSearchPath] = NULL;
1708 } 1783 }
1709 1784
@@ -1731,12 +1806,14 @@ static HRESULT AcquireContainerOrPayload(
1731LExit: 1806LExit:
1732 if (BOOTSTRAPPER_CACHE_OPERATION_EXTRACT == cacheOperation) 1807 if (BOOTSTRAPPER_CACHE_OPERATION_EXTRACT == cacheOperation)
1733 { 1808 {
1734 if (FAILED(hr) && SUCCEEDED(pPayload->pContainer->hrExtract) && 1809 // If this was the first extraction attempt and it failed
1810 // but there was a different method of acquisition available then recommend retrying.
1811 if (FAILED(hr) && !pPayload->pContainer->fExtracted &&
1735 (fFoundLocal || pPayload->downloadSource.sczUrl && *pPayload->downloadSource.sczUrl)) 1812 (fFoundLocal || pPayload->downloadSource.sczUrl && *pPayload->downloadSource.sczUrl))
1736 { 1813 {
1737 *pfRetry = TRUE; 1814 *pfRetry = TRUE;
1738 } 1815 }
1739 pPayload->pContainer->hrExtract = hr; 1816 pPayload->pContainer->fExtracted = TRUE;
1740 } 1817 }
1741 UserExperienceOnCacheAcquireComplete(pContext->pUX, wzPackageOrContainerId, wzPayloadId, hr, pfRetry); 1818 UserExperienceOnCacheAcquireComplete(pContext->pUX, wzPackageOrContainerId, wzPayloadId, hr, pfRetry);
1742 1819
@@ -2103,6 +2180,31 @@ static HRESULT CALLBACK CacheMessageHandler(
2103 hr = UserExperienceOnCacheContainerOrPayloadVerifyComplete(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, hr); 2180 hr = UserExperienceOnCacheContainerOrPayloadVerifyComplete(pProgress->pCacheContext->pUX, wzPackageOrContainerId, wzPayloadId, hr);
2104 break; 2181 break;
2105 } 2182 }
2183 case BURN_CACHE_MESSAGE_FAILURE:
2184 switch (pMessage->failure.cacheStep)
2185 {
2186 case BURN_CACHE_STEP_HASH:
2187 if (pProgress->pContainer)
2188 {
2189 LogStringLine(REPORT_DEBUG, "Verification failed on container: %ls", pProgress->pContainer->sczId);
2190 pProgress->pContainer->fFailedVerificationFromAcquisition = TRUE;
2191 }
2192 else if (pProgress->pPayloadGroupItem)
2193 {
2194 LogStringLine(REPORT_DEBUG, "Verification failed on payload group item: %ls", pProgress->pPayloadGroupItem->pPayload->sczKey);
2195 pProgress->pPayloadGroupItem->pPayload->fFailedVerificationFromAcquisition = TRUE;
2196 }
2197 else if (pProgress->pPayload)
2198 {
2199 LogStringLine(REPORT_DEBUG, "Verification failed on payload: %ls", pProgress->pPayload->sczKey);
2200 pProgress->pPayload->fFailedVerificationFromAcquisition = TRUE;
2201 }
2202 else
2203 {
2204 LogStringLine(REPORT_DEBUG, "Verification failed on unknown item");
2205 }
2206 break;
2207 }
2106 } 2208 }
2107 2209
2108 return hr; 2210 return hr;
@@ -2159,10 +2261,11 @@ static HRESULT CompleteCacheProgress(
2159 pContext->pPayloadGroupItem->qwCommittedCacheProgress += qwFileSize; 2261 pContext->pPayloadGroupItem->qwCommittedCacheProgress += qwFileSize;
2160 } 2262 }
2161 2263
2162 if (BURN_CACHE_PROGRESS_TYPE_FINALIZE == pContext->type && pContext->pCacheContext->sczLastUsedFolderCandidate) 2264 if (BURN_CACHE_PROGRESS_TYPE_FINALIZE == pContext->type && pContext->pCacheContext->sczLocalAcquisitionSourcePath)
2163 { 2265 {
2164 // We successfully copied from a source location, set that as the last used source. 2266 // We successfully copied from a source location, set that as the last used source.
2165 CacheSetLastUsedSource(pContext->pCacheContext->pVariables, pContext->pCacheContext->sczLastUsedFolderCandidate, pContext->pContainer ? pContext->pContainer->sczFilePath : pContext->pPayloadGroupItem->pPayload->sczFilePath); 2267 CacheSetLastUsedSource(pContext->pCacheContext->pVariables, pContext->pCacheContext->sczLocalAcquisitionSourcePath, pContext->pContainer ? pContext->pContainer->sczFilePath : pContext->pPayloadGroupItem->pPayload->sczFilePath);
2268 ReleaseNullStr(pContext->pCacheContext->sczLocalAcquisitionSourcePath);
2166 } 2269 }
2167 } 2270 }
2168 else if (PROGRESS_CANCEL == dwResult) 2271 else if (PROGRESS_CANCEL == dwResult)
diff --git a/src/burn/engine/cache.cpp b/src/burn/engine/cache.cpp
index eb5cc508..a23ce9ed 100644
--- a/src/burn/engine/cache.cpp
+++ b/src/burn/engine/cache.cpp
@@ -12,7 +12,11 @@ static const DWORD FILE_OPERATION_RETRY_WAIT = 2000;
12static HRESULT CacheVerifyPayloadSignature( 12static HRESULT CacheVerifyPayloadSignature(
13 __in BURN_PAYLOAD* pPayload, 13 __in BURN_PAYLOAD* pPayload,
14 __in_z LPCWSTR wzUnverifiedPayloadPath, 14 __in_z LPCWSTR wzUnverifiedPayloadPath,
15 __in HANDLE hFile 15 __in HANDLE hFile,
16 __in BURN_CACHE_STEP cacheStep,
17 __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
18 __in LPPROGRESS_ROUTINE pfnProgress,
19 __in LPVOID pContext
16 ); 20 );
17static HRESULT CalculatePotentialBaseWorkingFolders( 21static HRESULT CalculatePotentialBaseWorkingFolders(
18 __in BURN_CACHE* pCache, 22 __in BURN_CACHE* pCache,
@@ -159,7 +163,11 @@ static HRESULT SendCacheCompleteMessage(
159 __in LPVOID pContext, 163 __in LPVOID pContext,
160 __in HRESULT hrStatus 164 __in HRESULT hrStatus
161 ); 165 );
162 166static HRESULT SendCacheFailureMessage(
167 __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
168 __in LPVOID pContext,
169 __in BURN_CACHE_STEP cacheStep
170 );
163 171
164extern "C" HRESULT CacheInitialize( 172extern "C" HRESULT CacheInitialize(
165 __in BURN_CACHE* pCache, 173 __in BURN_CACHE* pCache,
@@ -1254,11 +1262,16 @@ LExit:
1254static HRESULT CacheVerifyPayloadSignature( 1262static HRESULT CacheVerifyPayloadSignature(
1255 __in BURN_PAYLOAD* pPayload, 1263 __in BURN_PAYLOAD* pPayload,
1256 __in_z LPCWSTR wzUnverifiedPayloadPath, 1264 __in_z LPCWSTR wzUnverifiedPayloadPath,
1257 __in HANDLE hFile 1265 __in HANDLE hFile,
1266 __in BURN_CACHE_STEP cacheStep,
1267 __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
1268 __in LPPROGRESS_ROUTINE /*pfnProgress*/,
1269 __in LPVOID pContext
1258 ) 1270 )
1259{ 1271{
1260 HRESULT hr = S_OK; 1272 HRESULT hr = S_OK;
1261 LONG er = ERROR_SUCCESS; 1273 LONG er = ERROR_SUCCESS;
1274 BOOL fFailedVerification = FALSE;
1262 1275
1263 GUID guidAuthenticode = WINTRUST_ACTION_GENERIC_VERIFY_V2; 1276 GUID guidAuthenticode = WINTRUST_ACTION_GENERIC_VERIFY_V2;
1264 WINTRUST_FILE_INFO wfi = { }; 1277 WINTRUST_FILE_INFO wfi = { };
@@ -1266,6 +1279,11 @@ static HRESULT CacheVerifyPayloadSignature(
1266 CRYPT_PROVIDER_DATA* pProviderData = NULL; 1279 CRYPT_PROVIDER_DATA* pProviderData = NULL;
1267 CRYPT_PROVIDER_SGNR* pSigner = NULL; 1280 CRYPT_PROVIDER_SGNR* pSigner = NULL;
1268 1281
1282 hr = SendCacheBeginMessage(pfnCacheMessageHandler, pContext, cacheStep);
1283 ExitOnFailure(hr, "Aborted cache verify payload signature begin.");
1284
1285 fFailedVerification = TRUE;
1286
1269 // Verify the payload assuming online. 1287 // Verify the payload assuming online.
1270 wfi.cbStruct = sizeof(wfi); 1288 wfi.cbStruct = sizeof(wfi);
1271 wfi.pcwszFilePath = wzUnverifiedPayloadPath; 1289 wfi.pcwszFilePath = wzUnverifiedPayloadPath;
@@ -1297,7 +1315,19 @@ static HRESULT CacheVerifyPayloadSignature(
1297 hr = VerifyPayloadAgainstCertChain(pPayload, pSigner->pChainContext); 1315 hr = VerifyPayloadAgainstCertChain(pPayload, pSigner->pChainContext);
1298 ExitOnFailure(hr, "Failed to verify expected payload against actual certificate chain."); 1316 ExitOnFailure(hr, "Failed to verify expected payload against actual certificate chain.");
1299 1317
1318 fFailedVerification = FALSE;
1319
1320 hr = SendCacheSuccessMessage(pfnCacheMessageHandler, pContext, pPayload->qwFileSize);
1321
1300LExit: 1322LExit:
1323 if (fFailedVerification)
1324 {
1325 // Make sure the BA process marks this payload as having failed verification.
1326 SendCacheFailureMessage(pfnCacheMessageHandler, pContext, cacheStep);
1327 }
1328
1329 SendCacheCompleteMessage(pfnCacheMessageHandler, pContext, hr);
1330
1301 return hr; 1331 return hr;
1302} 1332}
1303 1333
@@ -1744,7 +1774,7 @@ static HRESULT VerifyThenTransferPayload(
1744 switch (pPayload->verification) 1774 switch (pPayload->verification)
1745 { 1775 {
1746 case BURN_PAYLOAD_VERIFICATION_AUTHENTICODE: 1776 case BURN_PAYLOAD_VERIFICATION_AUTHENTICODE:
1747 hr = CacheVerifyPayloadSignature(pPayload, wzUnverifiedPayloadPath, hFile); 1777 hr = CacheVerifyPayloadSignature(pPayload, wzUnverifiedPayloadPath, hFile, BURN_CACHE_STEP_HASH, pfnCacheMessageHandler, pfnProgress, pContext);
1748 ExitOnFailure(hr, "Failed to verify payload signature: %ls", wzCachedPath); 1778 ExitOnFailure(hr, "Failed to verify payload signature: %ls", wzCachedPath);
1749 break; 1779 break;
1750 case BURN_PAYLOAD_VERIFICATION_HASH: 1780 case BURN_PAYLOAD_VERIFICATION_HASH:
@@ -1890,7 +1920,7 @@ static HRESULT VerifyFileAgainstPayload(
1890 switch (pPayload->verification) 1920 switch (pPayload->verification)
1891 { 1921 {
1892 case BURN_PAYLOAD_VERIFICATION_AUTHENTICODE: 1922 case BURN_PAYLOAD_VERIFICATION_AUTHENTICODE:
1893 hr = CacheVerifyPayloadSignature(pPayload, wzVerifyPath, hFile); 1923 hr = CacheVerifyPayloadSignature(pPayload, wzVerifyPath, hFile, cacheStep, pfnCacheMessageHandler, pfnProgress, pContext);
1894 ExitOnFailure(hr, "Failed to verify signature of payload: %ls", pPayload->sczKey); 1924 ExitOnFailure(hr, "Failed to verify signature of payload: %ls", pPayload->sczKey);
1895 break; 1925 break;
1896 case BURN_PAYLOAD_VERIFICATION_HASH: 1926 case BURN_PAYLOAD_VERIFICATION_HASH:
@@ -2285,17 +2315,18 @@ static HRESULT VerifyHash(
2285 __in LPVOID pContext 2315 __in LPVOID pContext
2286 ) 2316 )
2287{ 2317{
2288 UNREFERENCED_PARAMETER(wzUnverifiedPayloadPath);
2289
2290 HRESULT hr = S_OK; 2318 HRESULT hr = S_OK;
2291 BYTE rgbActualHash[SHA512_HASH_LEN] = { }; 2319 BYTE rgbActualHash[SHA512_HASH_LEN] = { };
2292 DWORD64 qwHashedBytes = 0; 2320 DWORD64 qwHashedBytes = 0;
2293 LPWSTR pszExpected = NULL; 2321 LPWSTR pszExpected = NULL;
2294 LPWSTR pszActual = NULL; 2322 LPWSTR pszActual = NULL;
2323 BOOL fFailedVerification = FALSE;
2295 2324
2296 hr = SendCacheBeginMessage(pfnCacheMessageHandler, pContext, cacheStep); 2325 hr = SendCacheBeginMessage(pfnCacheMessageHandler, pContext, cacheStep);
2297 ExitOnFailure(hr, "Aborted cache verify hash begin."); 2326 ExitOnFailure(hr, "Aborted cache verify hash begin.");
2298 2327
2328 fFailedVerification = TRUE;
2329
2299 if (fVerifyFileSize) 2330 if (fVerifyFileSize)
2300 { 2331 {
2301 hr = VerifyFileSize(hFile, qwFileSize, wzUnverifiedPayloadPath); 2332 hr = VerifyFileSize(hFile, qwFileSize, wzUnverifiedPayloadPath);
@@ -2323,9 +2354,17 @@ static HRESULT VerifyHash(
2323 } 2354 }
2324 } 2355 }
2325 2356
2357 fFailedVerification = FALSE;
2358
2326 hr = SendCacheSuccessMessage(pfnCacheMessageHandler, pContext, qwFileSize); 2359 hr = SendCacheSuccessMessage(pfnCacheMessageHandler, pContext, qwFileSize);
2327 2360
2328LExit: 2361LExit:
2362 if (fFailedVerification)
2363 {
2364 // Make sure the BA process marks this container or payload as having failed verification.
2365 SendCacheFailureMessage(pfnCacheMessageHandler, pContext, cacheStep);
2366 }
2367
2329 SendCacheCompleteMessage(pfnCacheMessageHandler, pContext, hr); 2368 SendCacheCompleteMessage(pfnCacheMessageHandler, pContext, hr);
2330 2369
2331 ReleaseStr(pszActual); 2370 ReleaseStr(pszActual);
@@ -2448,3 +2487,20 @@ static HRESULT SendCacheCompleteMessage(
2448 2487
2449 return hr; 2488 return hr;
2450} 2489}
2490
2491static HRESULT SendCacheFailureMessage(
2492 __in PFN_BURNCACHEMESSAGEHANDLER pfnCacheMessageHandler,
2493 __in LPVOID pContext,
2494 __in BURN_CACHE_STEP cacheStep
2495 )
2496{
2497 HRESULT hr = S_OK;
2498 BURN_CACHE_MESSAGE message = { };
2499
2500 message.type = BURN_CACHE_MESSAGE_FAILURE;
2501 message.failure.cacheStep = cacheStep;
2502
2503 hr = pfnCacheMessageHandler(&message, pContext);
2504
2505 return hr;
2506}
diff --git a/src/burn/engine/cache.h b/src/burn/engine/cache.h
index 9c698b4b..cc28166e 100644
--- a/src/burn/engine/cache.h
+++ b/src/burn/engine/cache.h
@@ -13,6 +13,7 @@ enum BURN_CACHE_MESSAGE_TYPE
13 BURN_CACHE_MESSAGE_BEGIN, 13 BURN_CACHE_MESSAGE_BEGIN,
14 BURN_CACHE_MESSAGE_SUCCESS, 14 BURN_CACHE_MESSAGE_SUCCESS,
15 BURN_CACHE_MESSAGE_COMPLETE, 15 BURN_CACHE_MESSAGE_COMPLETE,
16 BURN_CACHE_MESSAGE_FAILURE,
16}; 17};
17 18
18enum BURN_CACHE_STEP 19enum BURN_CACHE_STEP
@@ -68,6 +69,10 @@ typedef struct _BURN_CACHE_MESSAGE
68 { 69 {
69 HRESULT hrStatus; 70 HRESULT hrStatus;
70 } complete; 71 } complete;
72 struct
73 {
74 BURN_CACHE_STEP cacheStep;
75 } failure;
71 }; 76 };
72} BURN_CACHE_MESSAGE; 77} BURN_CACHE_MESSAGE;
73 78
diff --git a/src/burn/engine/container.cpp b/src/burn/engine/container.cpp
index 2f21baa8..e6b91532 100644
--- a/src/burn/engine/container.cpp
+++ b/src/burn/engine/container.cpp
@@ -173,6 +173,7 @@ extern "C" void ContainersUninitialize(
173 ReleaseStr(pContainer->downloadSource.sczUser); 173 ReleaseStr(pContainer->downloadSource.sczUser);
174 ReleaseStr(pContainer->downloadSource.sczPassword); 174 ReleaseStr(pContainer->downloadSource.sczPassword);
175 ReleaseStr(pContainer->sczUnverifiedPath); 175 ReleaseStr(pContainer->sczUnverifiedPath);
176 ReleaseStr(pContainer->sczFailedLocalAcquisitionPath);
176 ReleaseDict(pContainer->sdhPayloads); 177 ReleaseDict(pContainer->sdhPayloads);
177 } 178 }
178 MemFree(pContainers->rgContainers); 179 MemFree(pContainers->rgContainers);
diff --git a/src/burn/engine/container.h b/src/burn/engine/container.h
index f35f1da5..a38afa90 100644
--- a/src/burn/engine/container.h
+++ b/src/burn/engine/container.h
@@ -88,7 +88,9 @@ typedef struct _BURN_CONTAINER
88 DWORD64 qwExtractSizeTotal; 88 DWORD64 qwExtractSizeTotal;
89 DWORD64 qwCommittedCacheProgress; 89 DWORD64 qwCommittedCacheProgress;
90 DWORD64 qwCommittedExtractProgress; 90 DWORD64 qwCommittedExtractProgress;
91 HRESULT hrExtract; 91 BOOL fExtracted;
92 BOOL fFailedVerificationFromAcquisition;
93 LPWSTR sczFailedLocalAcquisitionPath;
92} BURN_CONTAINER; 94} BURN_CONTAINER;
93 95
94typedef struct _BURN_CONTAINERS 96typedef struct _BURN_CONTAINERS
diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp
index e165a257..ef9c6e97 100644
--- a/src/burn/engine/elevation.cpp
+++ b/src/burn/engine/elevation.cpp
@@ -42,6 +42,7 @@ typedef enum _BURN_ELEVATION_MESSAGE_TYPE
42 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_BEGIN, 42 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_BEGIN,
43 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE, 43 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE,
44 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS, 44 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS,
45 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_FAILURE,
45 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS, 46 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS,
46 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL, 47 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL,
47 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_STARTED, 48 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_STARTED,
@@ -1792,11 +1793,19 @@ static HRESULT ProcessBurnCacheMessages(
1792 case BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS: 1793 case BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS:
1793 // read message parameters 1794 // read message parameters
1794 hr = BuffReadNumber64((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.success.qwFileSize); 1795 hr = BuffReadNumber64((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.success.qwFileSize);
1795 ExitOnFailure(hr, "Failed to read begin cache step."); 1796 ExitOnFailure(hr, "Failed to read success file size.");
1796 1797
1797 message.type = BURN_CACHE_MESSAGE_SUCCESS; 1798 message.type = BURN_CACHE_MESSAGE_SUCCESS;
1798 break; 1799 break;
1799 1800
1801 case BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_FAILURE:
1802 // read message parameters
1803 hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, reinterpret_cast<DWORD*>(&message.failure.cacheStep));
1804 ExitOnFailure(hr, "Failed to read failure cache step.");
1805
1806 message.type = BURN_CACHE_MESSAGE_FAILURE;
1807 break;
1808
1800 case BURN_ELEVATION_MESSAGE_TYPE_PROGRESS_ROUTINE: 1809 case BURN_ELEVATION_MESSAGE_TYPE_PROGRESS_ROUTINE:
1801 fProgressRoutine = TRUE; 1810 fProgressRoutine = TRUE;
1802 break; 1811 break;
@@ -3511,7 +3520,7 @@ static HRESULT CALLBACK BurnCacheMessageHandler(
3511 case BURN_CACHE_MESSAGE_BEGIN: 3520 case BURN_CACHE_MESSAGE_BEGIN:
3512 // serialize message data 3521 // serialize message data
3513 hr = BuffWriteNumber(&pbData, &cbData, pMessage->begin.cacheStep); 3522 hr = BuffWriteNumber(&pbData, &cbData, pMessage->begin.cacheStep);
3514 ExitOnFailure(hr, "Failed to write progress percentage to message buffer."); 3523 ExitOnFailure(hr, "Failed to write cache step to message buffer.");
3515 3524
3516 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_BEGIN; 3525 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_BEGIN;
3517 break; 3526 break;
@@ -3526,10 +3535,18 @@ static HRESULT CALLBACK BurnCacheMessageHandler(
3526 3535
3527 case BURN_CACHE_MESSAGE_SUCCESS: 3536 case BURN_CACHE_MESSAGE_SUCCESS:
3528 hr = BuffWriteNumber64(&pbData, &cbData, pMessage->success.qwFileSize); 3537 hr = BuffWriteNumber64(&pbData, &cbData, pMessage->success.qwFileSize);
3529 ExitOnFailure(hr, "Failed to count of files in use to message buffer."); 3538 ExitOnFailure(hr, "Failed to write file size to message buffer.");
3530 3539
3531 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS; 3540 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS;
3532 break; 3541 break;
3542
3543 case BURN_CACHE_MESSAGE_FAILURE:
3544 // serialize message data
3545 hr = BuffWriteNumber(&pbData, &cbData, pMessage->failure.cacheStep);
3546 ExitOnFailure(hr, "Failed to write cache step to message buffer.");
3547
3548 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_FAILURE;
3549 break;
3533 } 3550 }
3534 3551
3535 // send message 3552 // send message
diff --git a/src/burn/engine/payload.cpp b/src/burn/engine/payload.cpp
index a4450bf7..fe3d673e 100644
--- a/src/burn/engine/payload.cpp
+++ b/src/burn/engine/payload.cpp
@@ -240,6 +240,7 @@ extern "C" void PayloadUninitialize(
240 ReleaseMem(pPayload->pbCertificateRootPublicKeyIdentifier); 240 ReleaseMem(pPayload->pbCertificateRootPublicKeyIdentifier);
241 ReleaseStr(pPayload->sczSourcePath); 241 ReleaseStr(pPayload->sczSourcePath);
242 ReleaseStr(pPayload->sczLocalFilePath); 242 ReleaseStr(pPayload->sczLocalFilePath);
243 ReleaseStr(pPayload->sczFailedLocalAcquisitionPath);
243 ReleaseStr(pPayload->downloadSource.sczUrl); 244 ReleaseStr(pPayload->downloadSource.sczUrl);
244 ReleaseStr(pPayload->downloadSource.sczUser); 245 ReleaseStr(pPayload->downloadSource.sczUser);
245 ReleaseStr(pPayload->downloadSource.sczPassword); 246 ReleaseStr(pPayload->downloadSource.sczPassword);
diff --git a/src/burn/engine/payload.h b/src/burn/engine/payload.h
index 14738506..351fd058 100644
--- a/src/burn/engine/payload.h
+++ b/src/burn/engine/payload.h
@@ -60,6 +60,9 @@ typedef struct _BURN_PAYLOAD
60 60
61 LPWSTR sczUnverifiedPath; 61 LPWSTR sczUnverifiedPath;
62 DWORD cRemainingInstances; 62 DWORD cRemainingInstances;
63
64 BOOL fFailedVerificationFromAcquisition;
65 LPWSTR sczFailedLocalAcquisitionPath;
63} BURN_PAYLOAD; 66} BURN_PAYLOAD;
64 67
65typedef struct _BURN_PAYLOADS 68typedef struct _BURN_PAYLOADS
diff --git a/src/burn/engine/plan.cpp b/src/burn/engine/plan.cpp
index 5c9ebe08..e6997b64 100644
--- a/src/burn/engine/plan.cpp
+++ b/src/burn/engine/plan.cpp
@@ -2105,7 +2105,9 @@ static void ResetPlannedContainerState(
2105 pContainer->qwExtractSizeTotal = 0; 2105 pContainer->qwExtractSizeTotal = 0;
2106 pContainer->qwCommittedCacheProgress = 0; 2106 pContainer->qwCommittedCacheProgress = 0;
2107 pContainer->qwCommittedExtractProgress = 0; 2107 pContainer->qwCommittedExtractProgress = 0;
2108 pContainer->hrExtract = S_OK; 2108 pContainer->fExtracted = FALSE;
2109 pContainer->fFailedVerificationFromAcquisition = FALSE;
2110 ReleaseNullStr(pContainer->sczFailedLocalAcquisitionPath);
2109} 2111}
2110 2112
2111static void ResetPlannedPayloadsState( 2113static void ResetPlannedPayloadsState(
@@ -2118,7 +2120,9 @@ static void ResetPlannedPayloadsState(
2118 2120
2119 pPayload->cRemainingInstances = 0; 2121 pPayload->cRemainingInstances = 0;
2120 pPayload->state = BURN_PAYLOAD_STATE_NONE; 2122 pPayload->state = BURN_PAYLOAD_STATE_NONE;
2123 pPayload->fFailedVerificationFromAcquisition = FALSE;
2121 ReleaseNullStr(pPayload->sczLocalFilePath); 2124 ReleaseNullStr(pPayload->sczLocalFilePath);
2125 ReleaseNullStr(pPayload->sczFailedLocalAcquisitionPath);
2122 } 2126 }
2123} 2127}
2124 2128
diff --git a/src/test/burn/TestBA/TestBA.cs b/src/test/burn/TestBA/TestBA.cs
index 7e3d2623..e73c907e 100644
--- a/src/test/burn/TestBA/TestBA.cs
+++ b/src/test/burn/TestBA/TestBA.cs
@@ -155,6 +155,9 @@ namespace WixToolset.Test.BA
155 this.quitAfterDetect = false; 155 this.quitAfterDetect = false;
156 } 156 }
157 157
158 this.ImportContainerSources();
159 this.ImportPayloadSources();
160
158 this.wait.WaitOne(); 161 this.wait.WaitOne();
159 162
160 if (this.action == LaunchAction.Help) 163 if (this.action == LaunchAction.Help)
@@ -657,6 +660,54 @@ namespace WixToolset.Test.BA
657 this.Engine.Log(LogLevel.Standard, String.Concat("TESTBA", relation, ": ", message)); 660 this.Engine.Log(LogLevel.Standard, String.Concat("TESTBA", relation, ": ", message));
658 } 661 }
659 662
663 private void ImportContainerSources()
664 {
665 string testName = this.Engine.GetVariableString("TestGroupName");
666 using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\container", testName)))
667 {
668 if (testKey == null)
669 {
670 return;
671 }
672
673 foreach (var containerId in testKey.GetSubKeyNames())
674 {
675 using (RegistryKey subkey = testKey.OpenSubKey(containerId))
676 {
677 string initialSource = subkey == null ? null : subkey.GetValue("InitialLocalSource") as string;
678 if (initialSource != null)
679 {
680 this.Engine.SetLocalSource(containerId, null, initialSource);
681 }
682 }
683 }
684 }
685 }
686
687 private void ImportPayloadSources()
688 {
689 string testName = this.Engine.GetVariableString("TestGroupName");
690 using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\payload", testName)))
691 {
692 if (testKey == null)
693 {
694 return;
695 }
696
697 foreach (var payloadId in testKey.GetSubKeyNames())
698 {
699 using (RegistryKey subkey = testKey.OpenSubKey(payloadId))
700 {
701 string initialSource = subkey == null ? null : subkey.GetValue("InitialLocalSource") as string;
702 if (initialSource != null)
703 {
704 this.Engine.SetLocalSource(null, payloadId, initialSource);
705 }
706 }
707 }
708 }
709 }
710
660 private List<string> ReadVerifyArguments() 711 private List<string> ReadVerifyArguments()
661 { 712 {
662 string testName = this.Engine.GetVariableString("TestGroupName"); 713 string testName = this.Engine.GetVariableString("TestGroupName");
diff --git a/src/test/burn/TestData/LayoutTests/BundleB/BundleB.wixproj b/src/test/burn/TestData/LayoutTests/BundleB/BundleB.wixproj
new file mode 100644
index 00000000..2ce22ea1
--- /dev/null
+++ b/src/test/burn/TestData/LayoutTests/BundleB/BundleB.wixproj
@@ -0,0 +1,17 @@
1<!-- 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. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <BA>hyperlinkLicense</BA>
5 <OutputType>Bundle</OutputType>
6 <UpgradeCode>{CBF22B7D-C6C0-408A-9F29-81FE8610559C}</UpgradeCode>
7 </PropertyGroup>
8 <ItemGroup>
9 <Compile Include="..\..\Templates\Bundle.wxs" Link="Bundle.wxs" />
10 </ItemGroup>
11 <ItemGroup>
12 <ProjectReference Include="..\PackageA\PackageA.wixproj" />
13 </ItemGroup>
14 <ItemGroup>
15 <PackageReference Include="WixToolset.Bal.wixext" />
16 </ItemGroup>
17</Project> \ No newline at end of file
diff --git a/src/test/burn/TestData/LayoutTests/BundleB/BundleB.wxs b/src/test/burn/TestData/LayoutTests/BundleB/BundleB.wxs
new file mode 100644
index 00000000..fb6eadb9
--- /dev/null
+++ b/src/test/burn/TestData/LayoutTests/BundleB/BundleB.wxs
@@ -0,0 +1,10 @@
1<!-- 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. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
5 <Fragment>
6 <PackageGroup Id="BundlePackages">
7 <MsiPackage Id="PackageA" SourceFile="$(var.PackageA.TargetPath)" Compressed="no" DownloadUrl="$(var.WebServerBaseUrl)BundleB/{2}" />
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/test/burn/WixToolsetTest.BurnE2E/LayoutTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/LayoutTests.cs
index 47a42750..14c3cd5d 100644
--- a/src/test/burn/WixToolsetTest.BurnE2E/LayoutTests.cs
+++ b/src/test/burn/WixToolsetTest.BurnE2E/LayoutTests.cs
@@ -65,5 +65,98 @@ namespace WixToolsetTest.BurnE2E
65 Assert.True(File.Exists(Path.Combine(layoutDirectory, "packages.cab"))); 65 Assert.True(File.Exists(Path.Combine(layoutDirectory, "packages.cab")));
66 Assert.True(File.Exists(Path.Combine(layoutDirectory, "BundleA.wxs"))); 66 Assert.True(File.Exists(Path.Combine(layoutDirectory, "BundleA.wxs")));
67 } 67 }
68
69 [RuntimeFact]
70 public void CanSkipOverCorruptLocalFileForDownloadableFile()
71 {
72 var bundleB = this.CreateBundleInstaller("BundleB");
73 var webServer = this.CreateWebServer();
74
75 webServer.AddFiles(new Dictionary<string, string>
76 {
77 { "/BundleB/PackageA.msi", Path.Combine(this.TestContext.TestDataFolder, "PackageA.msi") },
78 });
79 webServer.Start();
80
81 using var dfs = new DisposableFileSystem();
82 var baseDirectory = dfs.GetFolder(true);
83 var sourceDirectory = Path.Combine(baseDirectory, "source");
84 Directory.CreateDirectory(sourceDirectory);
85 var layoutDirectory = Path.Combine(baseDirectory, "layout");
86 Directory.CreateDirectory(layoutDirectory);
87
88 // Manually copy bundle to empty directory and then run from there so it can't find the uncorrupted file.
89 var bundleBFileInfo = new FileInfo(bundleB.Bundle);
90 var bundleBCopiedPath = Path.Combine(sourceDirectory, bundleBFileInfo.Name);
91 bundleBFileInfo.CopyTo(bundleBCopiedPath);
92
93 // Copy a corrupted version of PackageA.msi next to the bundle.
94 var packageAFileInfo = new FileInfo(Path.Combine(bundleBFileInfo.DirectoryName, "PackageA.msi"));
95 var packageACorruptedFileInfo = new FileInfo(Path.Combine(sourceDirectory, packageAFileInfo.Name));
96 packageAFileInfo.CopyTo(packageACorruptedFileInfo.FullName);
97 SubtlyCorruptFile(packageACorruptedFileInfo);
98
99 var layoutLogPath = bundleB.Layout(bundleBCopiedPath, layoutDirectory);
100 bundleB.VerifyUnregisteredAndRemovedFromPackageCache();
101
102 Assert.True(File.Exists(Path.Combine(layoutDirectory, bundleBFileInfo.Name)));
103 Assert.True(File.Exists(Path.Combine(layoutDirectory, packageAFileInfo.Name)));
104 Assert.True(LogVerifier.MessageInLogFile(layoutLogPath, "Verification failed on payload group item: PackageA"));
105 }
106
107 [RuntimeFact]
108 public void CanSkipOverCorruptLocalFileForOtherLocalFile()
109 {
110 var bundleA = this.CreateBundleInstaller("BundleA");
111 var testBAController = this.CreateTestBAController();
112
113 using var dfs = new DisposableFileSystem();
114 var baseDirectory = dfs.GetFolder(true);
115 var sourceDirectory = Path.Combine(baseDirectory, "source");
116 Directory.CreateDirectory(sourceDirectory);
117 var layoutDirectory = Path.Combine(baseDirectory, "layout");
118 Directory.CreateDirectory(layoutDirectory);
119
120 // Copy a corrupted version of packages.cab and BundleA.wxs.
121 var bundleAFileInfo = new FileInfo(bundleA.Bundle);
122 var packagesCabFileInfo = new FileInfo(Path.Combine(bundleAFileInfo.DirectoryName, "packages.cab"));
123 var packagesCabCorruptedFileInfo = new FileInfo(Path.Combine(sourceDirectory, packagesCabFileInfo.Name));
124 packagesCabFileInfo.CopyTo(packagesCabCorruptedFileInfo.FullName);
125 SubtlyCorruptFile(packagesCabCorruptedFileInfo);
126
127 var layoutOnlyPayloadFileInfo = new FileInfo(Path.Combine(bundleAFileInfo.DirectoryName, "BundleA.wxs"));
128 var layoutOnlyPayloadCorruptedFileInfo = new FileInfo(Path.Combine(sourceDirectory, layoutOnlyPayloadFileInfo.Name));
129 layoutOnlyPayloadFileInfo.CopyTo(layoutOnlyPayloadCorruptedFileInfo.FullName);
130 SubtlyCorruptFile(layoutOnlyPayloadCorruptedFileInfo);
131
132 // Set the source to absolute path so the engine tries the corrupted files first.
133 testBAController.SetContainerInitialLocalSource("PackagesContainer", packagesCabCorruptedFileInfo.FullName);
134 testBAController.SetPayloadInitialLocalSource("LayoutOnlyPayload", layoutOnlyPayloadCorruptedFileInfo.FullName);
135
136 var layoutLogPath = bundleA.Layout(layoutDirectory);
137 bundleA.VerifyUnregisteredAndRemovedFromPackageCache();
138
139 Assert.True(File.Exists(Path.Combine(layoutDirectory, bundleAFileInfo.Name)));
140 Assert.True(File.Exists(Path.Combine(layoutDirectory, packagesCabFileInfo.Name)));
141 Assert.True(File.Exists(Path.Combine(layoutDirectory, "BundleA.wxs")));
142 Assert.True(LogVerifier.MessageInLogFile(layoutLogPath, "Verification failed on container: PackagesContainer"));
143 Assert.True(LogVerifier.MessageInLogFile(layoutLogPath, "Verification failed on payload group item: LayoutOnlyPayload"));
144 }
145
146 private static void SubtlyCorruptFile(FileInfo fileInfo)
147 {
148 using (var v = fileInfo.Open(FileMode.Open, FileAccess.ReadWrite))
149 {
150 // Change one byte of information in the middle of the file to corrupt it.
151 // Hopefully this ensures that these tests will continue to work even if optimizations are added later,
152 // such as checking the file header, product code, or product version during acquisition.
153 var bytePosition = v.Length / 2;
154 v.Position = bytePosition;
155 var byteValue = v.ReadByte();
156 byteValue = byteValue == 0 ? 1 : 0;
157 v.Position = bytePosition;
158 v.WriteByte((byte)byteValue);
159 }
160 }
68 } 161 }
69} 162}
diff --git a/src/test/burn/WixToolsetTest.BurnE2E/Utilities/TestBAController.cs b/src/test/burn/WixToolsetTest.BurnE2E/Utilities/TestBAController.cs
index 3f9d76b8..115f3b54 100644
--- a/src/test/burn/WixToolsetTest.BurnE2E/Utilities/TestBAController.cs
+++ b/src/test/burn/WixToolsetTest.BurnE2E/Utilities/TestBAController.cs
@@ -183,6 +183,26 @@ namespace WixToolsetTest.BurnE2E
183 } 183 }
184 184
185 /// <summary> 185 /// <summary>
186 /// At startup, set the payload's source path to the given path.
187 /// </summary>
188 /// <param name="payloadId"></param>
189 /// <param name="sourcePath"></param>
190 public void SetPayloadInitialLocalSource(string payloadId, string sourcePath)
191 {
192 this.SetPayloadState(payloadId, "InitialLocalSource", sourcePath);
193 }
194
195 /// <summary>
196 /// At startup, set the container's source path to the given path.
197 /// </summary>
198 /// <param name="containerId"></param>
199 /// <param name="sourcePath"></param>
200 public void SetContainerInitialLocalSource(string containerId, string sourcePath)
201 {
202 this.SetContainerState(containerId, "InitialLocalSource", sourcePath);
203 }
204
205 /// <summary>
186 /// Sets the number of times to re-run the Detect phase. 206 /// Sets the number of times to re-run the Detect phase.
187 /// </summary> 207 /// </summary>
188 /// <param name="state">Number of times to run Detect (after the first, normal, Detect).</param> 208 /// <param name="state">Number of times to run Detect (after the first, normal, Detect).</param>
@@ -204,7 +224,6 @@ namespace WixToolsetTest.BurnE2E
204 public void SetVerifyArguments(string verifyArguments) 224 public void SetVerifyArguments(string verifyArguments)
205 { 225 {
206 this.SetBurnTestValue("VerifyArguments", verifyArguments); 226 this.SetBurnTestValue("VerifyArguments", verifyArguments);
207
208 } 227 }
209 228
210 private void SetPackageState(string packageId, string name, string value) 229 private void SetPackageState(string packageId, string name, string value)
@@ -223,6 +242,38 @@ namespace WixToolsetTest.BurnE2E
223 } 242 }
224 } 243 }
225 244
245 private void SetContainerState(string containerId, string name, string value)
246 {
247 var key = String.Format(@"{0}\container\{1}", this.TestBaseRegKeyPath, containerId);
248 using (var containerKey = Registry.LocalMachine.CreateSubKey(key))
249 {
250 if (String.IsNullOrEmpty(value))
251 {
252 containerKey.DeleteValue(name, false);
253 }
254 else
255 {
256 containerKey.SetValue(name, value);
257 }
258 }
259 }
260
261 private void SetPayloadState(string payloadId, string name, string value)
262 {
263 var key = String.Format(@"{0}\payload\{1}", this.TestBaseRegKeyPath, payloadId ?? String.Empty);
264 using (var payloadKey = Registry.LocalMachine.CreateSubKey(key))
265 {
266 if (String.IsNullOrEmpty(value))
267 {
268 payloadKey.DeleteValue(name, false);
269 }
270 else
271 {
272 payloadKey.SetValue(name, value);
273 }
274 }
275 }
276
226 public void Dispose() 277 public void Dispose()
227 { 278 {
228 Registry.LocalMachine.DeleteSubKeyTree($@"{this.BaseRegKeyPath}\{this.TestGroupName}", false); 279 Registry.LocalMachine.DeleteSubKeyTree($@"{this.BaseRegKeyPath}\{this.TestGroupName}", false);