diff --git a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BundleExtension.h b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BundleExtension.h index be76a1a50..d8aa7680c 100644 --- a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BundleExtension.h +++ b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BundleExtension.h @@ -9,6 +9,13 @@ extern "C" { enum BUNDLE_EXTENSION_MESSAGE { BUNDLE_EXTENSION_MESSAGE_SEARCH, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_OPEN, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_OPEN_ATTACHED, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_NEXT_STREAM, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_FILE, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_BUFFER, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_SKIP_STREAM, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_CLOSE, }; typedef struct _BUNDLE_EXTENSION_SEARCH_ARGS @@ -23,6 +30,97 @@ typedef struct _BUNDLE_EXTENSION_SEARCH_RESULTS DWORD cbSize; } BUNDLE_EXTENSION_SEARCH_RESULTS; + +// Container ops arguments +typedef struct _BUNDLE_EXTENSION_CONTAINER_OPEN_ARGS +{ + DWORD cbSize; + LPCWSTR wzContainerId; + LPCWSTR wzFilePath; +} BUNDLE_EXTENSION_CONTAINER_OPEN_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_OPEN_RESULTS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_OPEN_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_OPEN_ATTACHED_ARGS +{ + DWORD cbSize; + LPCWSTR wzContainerId; + HANDLE hBundle; + DWORD64 qwContainerStartPos; + DWORD64 qwContainerSize; +} BUNDLE_EXTENSION_CONTAINER_OPEN_ATTACHED_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_OPEN_ATTACHED_RESULTS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_OPEN_ATTACHED_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_ARGS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_RESULTS +{ + DWORD cbSize; + // String allocated using SysAllocString on input, expected to be allocated using same method on return + BSTR *psczStreamName; +} BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_ARGS +{ + DWORD cbSize; + LPVOID pContext; + LPCWSTR wzFileName; +} BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_RESULTS +{ + DWORD cbSize; +} BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS +{ + DWORD cbSize; + // Buffer must be allocated with CoTaskMemAlloc() + LPBYTE *ppbBuffer; + SIZE_T *pcbBuffer; +} BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_ARGS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_RESULTS +{ + DWORD cbSize; +} BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_CLOSE_ARGS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_CLOSE_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_CLOSE_RESULTS +{ + DWORD cbSize; +} BUNDLE_EXTENSION_CONTAINER_CLOSE_RESULTS; + extern "C" typedef HRESULT(WINAPI *PFN_BUNDLE_EXTENSION_PROC)( __in BUNDLE_EXTENSION_MESSAGE message, __in const LPVOID pvArgs, diff --git a/src/api/burn/bextutil/inc/BextBaseBundleExtension.h b/src/api/burn/bextutil/inc/BextBaseBundleExtension.h index a302702ea..34e17a684 100644 --- a/src/api/burn/bextutil/inc/BextBaseBundleExtension.h +++ b/src/api/burn/bextutil/inc/BextBaseBundleExtension.h @@ -67,6 +67,68 @@ class CBextBaseBundleExtension : public IBundleExtension return E_NOTIMPL; } + virtual STDMETHODIMP ContainerOpen( + __in LPCWSTR /*wzContainerId*/, + __in LPCWSTR /*wzFilePath*/, + __out LPVOID* /*pContext*/ + ) + { + return E_NOTIMPL; + } + + virtual STDMETHODIMP ContainerOpenAttached( + __in LPCWSTR /*wzContainerId*/, + __in HANDLE /*hBundle*/, + __in DWORD64 /*qwContainerStartPos*/, + __in DWORD64 /*qwContainerSize*/, + __out LPVOID* /*ppContext*/ + ) + { + return E_NOTIMPL; + } + + // Implementor should keep the stream name in the contex, to release it when done + virtual STDMETHODIMP ContainerNextStream( + __in LPVOID /*pContext*/, + __inout_z LPWSTR* /*psczStreamName*/ + ) + { + return E_NOTIMPL; + } + + virtual STDMETHODIMP ContainerStreamToFile( + __in LPVOID /*pContext*/, + __in_z LPCWSTR /*wzFileName*/ + ) + { + return E_NOTIMPL; + } + + // Not really needed because it is only used to read the manifest by the engine, and that is always a cab. + virtual STDMETHODIMP ContainerStreamToBuffer( + __in LPVOID /*pContext*/, + __out BYTE** /*ppbBuffer*/, + __out SIZE_T* /*pcbBuffer*/ + ) + { + return E_NOTIMPL; + } + + virtual STDMETHODIMP ContainerSkipStream( + __in LPVOID /*pContext*/ + ) + { + return E_NOTIMPL; + } + + // Don't forget to release everything in the context + virtual STDMETHODIMP ContainerClose( + __in LPVOID /*pContext*/ + ) + { + return E_NOTIMPL; + } + virtual STDMETHODIMP BundleExtensionProc( __in BUNDLE_EXTENSION_MESSAGE /*message*/, __in const LPVOID /*pvArgs*/, diff --git a/src/api/burn/bextutil/inc/BextBaseBundleExtensionProc.h b/src/api/burn/bextutil/inc/BextBaseBundleExtensionProc.h index f71e3b924..438fcbbc9 100644 --- a/src/api/burn/bextutil/inc/BextBaseBundleExtensionProc.h +++ b/src/api/burn/bextutil/inc/BextBaseBundleExtensionProc.h @@ -18,6 +18,69 @@ static HRESULT BextBaseBEProcSearch( return pBE->Search(pArgs->wzId, pArgs->wzVariable); } +static HRESULT BextBaseBEProcContainerOpen( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_OPEN_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_OPEN_RESULTS* pResults + ) +{ + return pBE->ContainerOpen(pArgs->wzContainerId, pArgs->wzFilePath, &pResults->pContext); +} + +static HRESULT BextBaseBEProcContainerOpenAttached( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_OPEN_ATTACHED_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_OPEN_ATTACHED_RESULTS* pResults + ) +{ + return pBE->ContainerOpenAttached(pArgs->wzContainerId, pArgs->hBundle, pArgs->qwContainerStartPos, pArgs->qwContainerSize, &pResults->pContext); +} + +static HRESULT BextBaseBEProcContainerNextStream( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_RESULTS* pResults + ) +{ + return pBE->ContainerNextStream(pArgs->pContext, pResults->psczStreamName); +} + +static HRESULT BextBaseBEProcContainerStreamToFile( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_RESULTS* /*pResults*/ + ) +{ + return pBE->ContainerStreamToFile(pArgs->pContext, pArgs->wzFileName); +} + +static HRESULT BextBaseBEProcContainerStreamToBuffer( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS* pResults + ) +{ + return pBE->ContainerStreamToBuffer(pArgs->pContext, pResults->ppbBuffer, pResults->pcbBuffer); +} + +static HRESULT BextBaseBEProcContainerSkipStream( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_RESULTS* /*pResults*/ + ) +{ + return pBE->ContainerSkipStream(pArgs->pContext); +} + +static HRESULT BextBaseBEProcContainerClose( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_CLOSE_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_CLOSE_RESULTS* /*pResults*/ + ) +{ + return pBE->ContainerClose(pArgs->pContext); +} + /******************************************************************* BextBaseBundleExtensionProc - requires pvContext to be of type IBundleExtension. Provides a default mapping between the message based @@ -33,7 +96,7 @@ static HRESULT WINAPI BextBaseBundleExtensionProc( { IBundleExtension* pBE = reinterpret_cast(pvContext); HRESULT hr = pBE->BundleExtensionProc(message, pvArgs, pvResults, pvContext); - + if (E_NOTIMPL == hr) { switch (message) @@ -41,6 +104,27 @@ static HRESULT WINAPI BextBaseBundleExtensionProc( case BUNDLE_EXTENSION_MESSAGE_SEARCH: hr = BextBaseBEProcSearch(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_OPEN: + hr = BextBaseBEProcContainerOpen(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_OPEN_ATTACHED: + hr = BextBaseBEProcContainerOpenAttached(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_NEXT_STREAM: + hr = BextBaseBEProcContainerNextStream(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_FILE: + hr = BextBaseBEProcContainerStreamToFile(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_BUFFER: + hr = BextBaseBEProcContainerStreamToBuffer(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_SKIP_STREAM: + hr = BextBaseBEProcContainerSkipStream(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_CLOSE: + hr = BextBaseBEProcContainerClose(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; } } diff --git a/src/api/burn/bextutil/inc/IBundleExtension.h b/src/api/burn/bextutil/inc/IBundleExtension.h index 7516c11be..6af11d4df 100644 --- a/src/api/burn/bextutil/inc/IBundleExtension.h +++ b/src/api/burn/bextutil/inc/IBundleExtension.h @@ -9,6 +9,66 @@ DECLARE_INTERFACE_IID_(IBundleExtension, IUnknown, "93123C9D-796B-4FCD-A507-6EDE __in LPCWSTR wzVariable ) = 0; + /* ContainerOpen + Open a container file + */ + STDMETHOD(ContainerOpen)( + __in LPCWSTR wzContainerId, + __in LPCWSTR wzFilePath, + __out LPVOID *ppContext + ) = 0; + + /* ContainerOpenAttached + Open an attached container + If not implemented, return E_NOTIMPL. In that case, burn will extract the container to a temporary file and call ContainerOpen(). Note that, this may come with substantial performance penalty + */ + STDMETHOD(ContainerOpenAttached)( + __in LPCWSTR wzContainerId, + __in HANDLE hBundle, + __in DWORD64 qwContainerStartPos, + __in DWORD64 qwContainerSize, + __out LPVOID *ppContext + ) = 0; + + /* ContainerNextStream + Return the file name of the subsequent stream + */ + STDMETHOD(ContainerNextStream)( + __in LPVOID pContext, + __inout_z LPWSTR* psczStreamName + ) = 0; + + /* ContainerStreamToFile + Extract the current stream to a file. May be implemented a-synchronically. All extractions must be completed before the call to ContainerClose returns. + */ + STDMETHOD(ContainerStreamToFile)( + __in LPVOID pContext, + __in_z LPCWSTR wzFileName + ) = 0; + + /* ContainerStreamToBuffer + Extract the current stream to a buffer. May not be implemented a-synchronically + */ + STDMETHOD(ContainerStreamToBuffer)( + __in LPVOID pContext, + __out BYTE** ppbBuffer, + __out SIZE_T* pcbBuffer + ) = 0; + + /* ContainerSkipStream + Notifies that the current stream is not needed + */ + STDMETHOD(ContainerSkipStream)( + __in LPVOID pContext + ) = 0; + + /* ContainerClose + Complete all pending file extractions and release the container. + */ + STDMETHOD(ContainerClose)( + __in LPVOID pContext + ) = 0; + // BundleExtensionProc - The PFN_BUNDLE_EXTENSION_PROC can call this method to give the BundleExtension raw access to the callback from the engine. // This might be used to help the BundleExtension support more than one version of the engine. STDMETHOD(BundleExtensionProc)( diff --git a/src/api/wix/WixToolset.Data/ErrorMessages.cs b/src/api/wix/WixToolset.Data/ErrorMessages.cs index d604e94f9..603e0e222 100644 --- a/src/api/wix/WixToolset.Data/ErrorMessages.cs +++ b/src/api/wix/WixToolset.Data/ErrorMessages.cs @@ -2276,6 +2276,21 @@ private static Message Message(SourceLineNumber sourceLineNumber, Ids id, Resour return new Message(sourceLineNumber, MessageLevel.Error, (int)id, resourceManager, resourceName, args); } + public static Message MissingContainerExtension(SourceLineNumber sourceLineNumber, string containerId, string bundleExtensionRef) + { + return Message(sourceLineNumber, Ids.MissingContainerExtension, "Container '{0}' has BundleExtensionRef set to '{1}', which could not be resolved to a container extension.", containerId, bundleExtensionRef); + } + + public static Message ContainerExtractFailed(SourceLineNumber sourceLineNumber, string containerId, string bundleExtensionRef, string errorMessage) + { + return Message(sourceLineNumber, Ids.ContainerExtractFailed, "Container '{0}' with BundleExtensionRef set to '{1}' failed to extract the container. {2}", containerId, bundleExtensionRef, errorMessage); + } + + public static Message InvalidBurnManifestContainers(SourceLineNumber sourceLineNumber, int containersCount, int missingIndex) + { + return Message(sourceLineNumber, Ids.InvalidBurnManifestContainers, "The burn manifest file contains {0} containers, yet container with index {1} was not found.", containersCount, missingIndex); + } + public enum Ids { UnexpectedException = 1, @@ -2667,6 +2682,9 @@ public enum Ids MsiTransactionInvalidPackage2 = 412, ExpectedAttributeOrElementWithOtherAttribute = 413, ExpectedAttributeOrElementWithoutOtherAttribute = 414, + MissingContainerExtension = 415, + ContainerExtractFailed = 416, + InvalidBurnManifestContainers = 417, } } } diff --git a/src/api/wix/WixToolset.Data/Symbols/WixBundleContainerSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixBundleContainerSymbol.cs index 80beda0a0..e969ab18b 100644 --- a/src/api/wix/WixToolset.Data/Symbols/WixBundleContainerSymbol.cs +++ b/src/api/wix/WixToolset.Data/Symbols/WixBundleContainerSymbol.cs @@ -17,6 +17,7 @@ public static partial class SymbolDefinitions new IntermediateFieldDefinition(nameof(WixBundleContainerSymbolFields.Hash), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBundleContainerSymbolFields.AttachedContainerIndex), IntermediateFieldType.Number), new IntermediateFieldDefinition(nameof(WixBundleContainerSymbolFields.WorkingPath), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixBundleContainerSymbolFields.BundleExtensionRef), IntermediateFieldType.String), }, typeof(WixBundleContainerSymbol)); } @@ -35,6 +36,7 @@ public enum WixBundleContainerSymbolFields Hash, AttachedContainerIndex, WorkingPath, + BundleExtensionRef, } /// @@ -99,5 +101,11 @@ public string WorkingPath get => (string)this.Fields[(int)WixBundleContainerSymbolFields.WorkingPath]; set => this.Set((int)WixBundleContainerSymbolFields.WorkingPath, value); } + + public string BundleExtensionRef + { + get => (string)this.Fields[(int)WixBundleContainerSymbolFields.BundleExtensionRef]; + set => this.Set((int)WixBundleContainerSymbolFields.BundleExtensionRef, value); + } } } diff --git a/src/api/wix/WixToolset.Data/WarningMessages.cs b/src/api/wix/WixToolset.Data/WarningMessages.cs index f3d469bf3..b5c7faab1 100644 --- a/src/api/wix/WixToolset.Data/WarningMessages.cs +++ b/src/api/wix/WixToolset.Data/WarningMessages.cs @@ -719,6 +719,11 @@ public static Message VBScriptIsDeprecated(SourceLineNumber sourceLineNumbers) return Message(sourceLineNumbers, Ids.VBScriptIsDeprecated, "VBScript is a deprecated Windows component: https://learn.microsoft.com/en-us/windows/whats-new/deprecated-features. VBScript custom actions might fail on some Windows systems. Rewrite or eliminate VBScript custom actions for best compatibility."); } + public static Message MissingContainerExtension(SourceLineNumber sourceLineNumbers, string containerId, string bundleExtensionRef) + { + return Message(sourceLineNumbers, Ids.MissingContainerExtension, "Container '{0}' has BundleExtensionRef set to '{1}', which could not be resolved to a container extension. To extract this container add the missing extension to the extraction command line", containerId, bundleExtensionRef); + } + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) { return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); @@ -861,6 +866,7 @@ public enum Ids ExePackageDetectInformationRecommended = 1161, InvalidWixVersion = 1162, VBScriptIsDeprecated = 1163, + MissingContainerExtension = 1164, } } } diff --git a/src/api/wix/WixToolset.Extensibility/BaseBurnContainerExtension.cs b/src/api/wix/WixToolset.Extensibility/BaseBurnContainerExtension.cs new file mode 100644 index 000000000..6f666d48d --- /dev/null +++ b/src/api/wix/WixToolset.Extensibility/BaseBurnContainerExtension.cs @@ -0,0 +1,98 @@ +// 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 WixToolset.Extensibility +{ + using System.Collections.Generic; + using System.IO; + using System.Security.Cryptography; + using System.Text; + using System.Xml; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Base class for creating a Burn container extension. + /// + public abstract class BaseBurnContainerExtension : IBurnContainerExtension + { + /// + /// Context for use by the extension. + /// + protected IBindContext Context { get; private set; } + + /// + /// Messaging for use by the extension. + /// + protected IMessaging Messaging { get; private set; } + + /// + /// Backend helper for use by the extension. + /// + protected IBurnBackendHelper BackendHelper { get; private set; } + + /// + /// Collection of bundle extension IDs that this container extension handles. + /// + public abstract IReadOnlyCollection ContainerExtensionIds { get; } + + /// + /// Called at the beginning of the binding phase. + /// + public virtual void PreBackendBind(IBindContext context) + { + this.Context = context; + this.Messaging = context.ServiceProvider.GetService(); + this.BackendHelper = context.ServiceProvider.GetService(); + } + + /// + /// Called during bind phase to create a container + /// Implementors must set to the container file's SHA512, and after creating the container + /// + /// The container symbol. + /// Collection of payloads that should be compressed in the container. + /// SHA512 hash of the container file. + /// File size of the container file. + public abstract void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads, out string sha512, out long size); + + /// + /// Extract the container to a folder. Called on 'burn extract' command. + /// Note that, the PreBackendBind may not be called before calling this method. + /// + /// + /// + /// + /// + public abstract void ExtractContainer(string containerPath, string outputFolder, string containerId, XmlElement extensionDataNode); + + /// + /// Helper method to calculate SHA512 and size of the container + /// + /// + /// + /// + protected void CalculateHashAndSize(string containerPath, out string sha512, out long size) + { + byte[] hashBytes; + + var fileInfo = new FileInfo(containerPath); + using (var managed = new SHA512CryptoServiceProvider()) + { + using (var stream = fileInfo.OpenRead()) + { + hashBytes = managed.ComputeHash(stream); + } + } + + var sb = new StringBuilder(hashBytes.Length * 2); + for (var i = 0; i < hashBytes.Length; i++) + { + sb.AppendFormat("{0:X2}", hashBytes[i]); + } + + sha512 = sb.ToString(); + size = fileInfo.Length; + } + } +} diff --git a/src/api/wix/WixToolset.Extensibility/IBurnContainerExtension.cs b/src/api/wix/WixToolset.Extensibility/IBurnContainerExtension.cs new file mode 100644 index 000000000..1470a577c --- /dev/null +++ b/src/api/wix/WixToolset.Extensibility/IBurnContainerExtension.cs @@ -0,0 +1,45 @@ +// 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 WixToolset.Extensibility +{ + using System.Collections.Generic; + using System.Xml; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + + /// + /// Interface for container extensions. + /// + public interface IBurnContainerExtension + { + /// + /// Collection of bundle extension IDs that this container extension handles. + /// + IReadOnlyCollection ContainerExtensionIds { get; } + + /// + /// Called at the beginning of the binding phase. + /// + void PreBackendBind(IBindContext context); + + /// + /// Called during bind phase to create a container + /// Implementors must set to the container file's SHA512, and after creating the container + /// + /// The container symbol. + /// Collection of payloads that should be compressed in the container. + /// SHA512 hash of the container file. + /// File size of the container file. + void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads, out string sha512, out long size); + + /// + /// Extract the container to a folder. Called on 'burn extract' command. + /// Note that, the PreBackendBind may not be called when calling this method. + /// + /// + /// + /// + /// + void ExtractContainer(string containerPath, string outputFolder, string containerId, XmlElement extensionDataNode); + } +} diff --git a/src/burn/engine/burnextension.cpp b/src/burn/engine/burnextension.cpp index ee4b15422..d8a16359e 100644 --- a/src/burn/engine/burnextension.cpp +++ b/src/burn/engine/burnextension.cpp @@ -250,6 +250,238 @@ EXTERN_C BEEAPI BurnExtensionPerformSearch( return hr; } +EXTERN_C BEEAPI BurnExtensionContainerOpen( + __in BURN_EXTENSION* pExtension, + __in LPCWSTR wzContainerId, + __in LPCWSTR wzFilePath, + __in BURN_CONTAINER_CONTEXT* pContext +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_OPEN_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_OPEN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzContainerId = wzContainerId; + args.wzFilePath = wzFilePath; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_OPEN, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' open container '%ls' failed.", pExtension->sczId, wzFilePath); + + pContext->Bex.pExtensionContext = results.pContext; + +LExit: + return hr; +} + +EXTERN_C BEEAPI BurnExtensionContainerOpenAttached( + __in BURN_EXTENSION* pExtension, + __in LPCWSTR wzContainerId, + __in HANDLE hBundle, + __in DWORD64 qwContainerStartPos, + __in DWORD64 qwContainerSize, + __in BURN_CONTAINER_CONTEXT* pContext +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_OPEN_ATTACHED_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_OPEN_ATTACHED_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzContainerId = wzContainerId; + args.hBundle = hBundle; + args.qwContainerStartPos = qwContainerStartPos; + args.qwContainerSize = qwContainerSize; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_OPEN_ATTACHED, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' open attached container failed.", pExtension->sczId); + + pContext->Bex.pExtensionContext = results.pContext; + +LExit: + return hr; +} + +BEEAPI BurnExtensionContainerNextStream( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __inout_z LPWSTR* psczStreamName +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_RESULTS results = { }; + BSTR sczStreamName = nullptr; + + if (psczStreamName && *psczStreamName) + { + sczStreamName = ::SysAllocString(*psczStreamName); + ExitOnNull(sczStreamName, hr, E_FAIL, "Failed to allocate sys string"); + } + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + + results.cbSize = sizeof(results); + results.psczStreamName = &sczStreamName; + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_NEXT_STREAM, &args, &results); + if (hr != E_NOMOREITEMS) + { + ExitOnFailure(hr, "BundleExtension '%ls' failed to move to next stream.", pExtension->sczId); + + if (psczStreamName) + { + if (sczStreamName) + { + hr = StrAllocString(psczStreamName, sczStreamName, 0); + ExitOnFailure(hr, "Failed to copy string"); + } + else + { + ReleaseNullStr(*psczStreamName); + } + } + } + +LExit: + if (sczStreamName) + { + ::SysFreeString(sczStreamName); + } + + return hr; +} + +BEEAPI BurnExtensionContainerStreamToFile( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __in_z LPCWSTR wzFileName +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + args.wzFileName = wzFileName; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_FILE, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' failed to extract file '%ls'.", pExtension->sczId, wzFileName); + +LExit: + return hr; +} + +BEEAPI BurnExtensionContainerStreamToBuffer( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __inout LPBYTE* ppbBuffer, + __inout SIZE_T* pcbBuffer +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS results = { }; + LPBYTE pbBuffer = nullptr; + SIZE_T cbBuffer = 0; + errno_t err = 0; + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + + results.cbSize = sizeof(results); + results.ppbBuffer = &pbBuffer; + results.pcbBuffer = &cbBuffer; + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_BUFFER, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' failed to extract stream to buffer.", pExtension->sczId); + + if (pbBuffer) + { + if (ppbBuffer && *ppbBuffer) + { + LPVOID pv = MemReAlloc(*ppbBuffer, cbBuffer, FALSE); + ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate memory."); + + *ppbBuffer = (LPBYTE)pv; + *pcbBuffer = cbBuffer; + } + else + { + *ppbBuffer = (LPBYTE)MemAlloc(cbBuffer, FALSE); + ExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate memory."); + + *pcbBuffer = cbBuffer; + } + + err = ::memcpy_s(*ppbBuffer, cbBuffer, pbBuffer, cbBuffer); + ExitOnNull(!err, hr, HRESULT_FROM_WIN32(err), "Failed to copy memory"); + } + else if (ppbBuffer && *ppbBuffer) + { + ReleaseNullMem(*ppbBuffer); + *pcbBuffer = 0; + } + +LExit: + if (pbBuffer) + { + ::CoTaskMemFree(pbBuffer); + } + + return hr; +} + +BEEAPI BurnExtensionContainerSkipStream( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_SKIP_STREAM, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' failed to skip stream.", pExtension->sczId); + +LExit: + return hr; +} + +BEEAPI BurnExtensionContainerClose( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_CLOSE_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_CLOSE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_CLOSE, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' failed to close container.", pExtension->sczId); + +LExit: + return hr; +} + static HRESULT SendRequiredBextMessage( __in BURN_EXTENSION* pExtension, __in BUNDLE_EXTENSION_MESSAGE message, diff --git a/src/burn/engine/burnextension.h b/src/burn/engine/burnextension.h index 3529ef38a..60bc97315 100644 --- a/src/burn/engine/burnextension.h +++ b/src/burn/engine/burnextension.h @@ -55,6 +55,45 @@ BEEAPI BurnExtensionPerformSearch( __in LPWSTR wzSearchId, __in LPWSTR wzVariable ); +BEEAPI BurnExtensionContainerOpen( + __in BURN_EXTENSION* pExtension, + __in LPCWSTR wzContainerId, + __in LPCWSTR wzFilePath, + __in BURN_CONTAINER_CONTEXT* pContext + ); +BEEAPI BurnExtensionContainerOpenAttached( + __in BURN_EXTENSION* pExtension, + __in LPCWSTR wzContainerId, + __in HANDLE hBundle, + __in DWORD64 qwContainerStartPos, + __in DWORD64 qwContainerSize, + __in BURN_CONTAINER_CONTEXT* pContext + ); +BEEAPI BurnExtensionContainerNextStream( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __inout_z LPWSTR *psczStreamName + ); +BEEAPI BurnExtensionContainerStreamToFile( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __in_z LPCWSTR wzFileName + ); +BEEAPI BurnExtensionContainerStreamToBuffer( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __inout LPBYTE * ppbBuffer, + __inout SIZE_T * pcbBuffer + ); +BEEAPI BurnExtensionContainerSkipStream( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext + ); +BEEAPI BurnExtensionContainerClose( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext + ); + #if defined(__cplusplus) } #endif diff --git a/src/burn/engine/container.cpp b/src/burn/engine/container.cpp index e6b915325..278dbc390 100644 --- a/src/burn/engine/container.cpp +++ b/src/burn/engine/container.cpp @@ -7,7 +7,8 @@ extern "C" HRESULT ContainersParseFromXml( __in BURN_CONTAINERS* pContainers, - __in IXMLDOMNode* pixnBundle + __in IXMLDOMNode* pixnBundle, + __in BURN_EXTENSIONS* pBurnExtensions ) { HRESULT hr = S_OK; @@ -44,13 +45,41 @@ extern "C" HRESULT ContainersParseFromXml( 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); ExitOnRequiredXmlQueryFailure(hr, "Failed to get @Id."); + // @Type + pContainer->type = BURN_CONTAINER_TYPE_CABINET; // Default + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get @Type."); + if (fXmlFound && scz && *scz) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"Extension", -1)) + { + pContainer->type = BURN_CONTAINER_TYPE_EXTENSION; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"Cabinet", -1)) + { + pContainer->type = BURN_CONTAINER_TYPE_CABINET; + } + else + { + hr = E_INVALIDDATA; + ExitOnFailure(hr, "Unsupported container type '%ls'.", scz); + } + } + + if (BURN_CONTAINER_TYPE_EXTENSION == pContainer->type) + { + // @ExtensionId + hr = XmlGetAttributeEx(pixnNode, L"ExtensionId", &scz); + ExitOnRequiredXmlQueryFailure(hr, "Failed to get @ExtensionId."); + + hr = BurnExtensionFindById(pBurnExtensions, scz, &pContainer->pExtension); + ExitOnRootFailure(hr, "Failed to find bundle extension '%ls' for container '%ls'", scz, pContainer->sczId); + } + // @Attached hr = XmlGetYesNoAttribute(pixnNode, L"Attached", &pContainer->fAttached); ExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get @Attached."); @@ -139,7 +168,7 @@ extern "C" HRESULT ContainersInitialize( // manifest contained and get the offset to the container. if (pContainer->fAttached) { - hr = SectionGetAttachedContainerInfo(pSection, pContainer->dwAttachedIndex, pContainer->type, &pContainer->qwAttachedOffset, &qwSize, &pContainer->fActuallyAttached); + hr = SectionGetAttachedContainerInfo(pSection, pContainer->dwAttachedIndex, &pContainer->qwAttachedOffset, &qwSize, &pContainer->fActuallyAttached); ExitOnFailure(hr, "Failed to get attached container information."); if (qwSize != pContainer->qwFileSize) @@ -197,9 +226,15 @@ extern "C" HRESULT ContainerOpenUX( container.fAttached = TRUE; container.dwAttachedIndex = 0; - hr = SectionGetAttachedContainerInfo(pSection, container.dwAttachedIndex, container.type, &container.qwAttachedOffset, &container.qwFileSize, &container.fActuallyAttached); + hr = SectionGetAttachedContainerInfo(pSection, container.dwAttachedIndex, &container.qwAttachedOffset, &container.qwFileSize, &container.fActuallyAttached); ExitOnFailure(hr, "Failed to get container information for UX container."); + if (BURN_CONTAINER_TYPE_CABINET != pSection->dwFormat) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ExitOnRootFailure(hr, "Unexpected UX container format %u. UX container is expected to always be a cabinet file", pSection->dwFormat); + } + AssertSz(container.fActuallyAttached, "The BA container must always be found attached."); hr = PathForCurrentProcess(&sczExecutablePath, NULL); @@ -223,6 +258,7 @@ extern "C" HRESULT ContainerOpen( { HRESULT hr = S_OK; LARGE_INTEGER li = { }; + LPWSTR szTempFile = NULL; // initialize context pContext->type = pContainer->type; @@ -260,10 +296,40 @@ extern "C" HRESULT ContainerOpen( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractOpen(pContext, wzFilePath); break; + case BURN_CONTAINER_TYPE_EXTENSION: + + pContext->Bex.pExtension = pContainer->pExtension; + + if (pContainer->fAttached) + { + hr = BurnExtensionContainerOpenAttached(pContainer->pExtension, pContainer->sczId, pContext->hFile, pContext->qwOffset, pContext->qwSize, pContext); + if (FAILED(hr)) + { + LogId(REPORT_STANDARD, MSG_EXT_ATTACHED_CONTAINER_FAILED, pContainer->sczId); + + hr = FileCreateTemp(L"CNTNR", L"dat", &szTempFile, NULL); + ExitOnFailure(hr, "Failed to create temporary container file"); + + hr = FileCopyPartial(pContext->hFile, pContext->qwOffset, pContext->qwSize, szTempFile); + ExitOnFailure(hr, "Failed to write to temporary container file"); + + pContext->Bex.szTempContainerPath = szTempFile; + szTempFile = NULL; + + hr = BurnExtensionContainerOpen(pContainer->pExtension, pContainer->sczId, pContext->Bex.szTempContainerPath, pContext); + } + } + else + { + hr = BurnExtensionContainerOpen(pContainer->pExtension, pContainer->sczId, wzFilePath, pContext); + } + break; } ExitOnFailure(hr, "Failed to open container."); LExit: + ReleaseStr(szTempFile); + return hr; } @@ -279,6 +345,9 @@ extern "C" HRESULT ContainerNextStream( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractNextStream(pContext, psczStreamName); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerNextStream(pContext->Bex.pExtension, pContext, psczStreamName); + break; } //LExit: @@ -297,6 +366,9 @@ extern "C" HRESULT ContainerStreamToFile( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractStreamToFile(pContext, wzFileName); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerStreamToFile(pContext->Bex.pExtension, pContext, wzFileName); + break; } //LExit: @@ -316,6 +388,9 @@ extern "C" HRESULT ContainerStreamToBuffer( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractStreamToBuffer(pContext, ppbBuffer, pcbBuffer); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerStreamToBuffer(pContext->Bex.pExtension, pContext, ppbBuffer, pcbBuffer); + break; default: *ppbBuffer = NULL; @@ -337,6 +412,9 @@ extern "C" HRESULT ContainerSkipStream( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractSkipStream(pContext); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerSkipStream(pContext->Bex.pExtension, pContext); + break; } //LExit: @@ -356,6 +434,15 @@ extern "C" HRESULT ContainerClose( hr = CabExtractClose(pContext); ExitOnFailure(hr, "Failed to close cabinet."); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerClose(pContext->Bex.pExtension, pContext); + if (pContext->Bex.szTempContainerPath && *pContext->Bex.szTempContainerPath) + { + FileEnsureDelete(pContext->Bex.szTempContainerPath); + ReleaseNullStr(pContext->Bex.szTempContainerPath); + } + ExitOnFailure(hr, "Failed to close cabinet."); + break; } LExit: diff --git a/src/burn/engine/container.h b/src/burn/engine/container.h index a38afa90a..314607ace 100644 --- a/src/burn/engine/container.h +++ b/src/burn/engine/container.h @@ -32,6 +32,9 @@ extern "C" { // __in void* pCookie // ); +// Forward declarations +typedef struct _BURN_EXTENSION BURN_EXTENSION; +typedef struct _BURN_EXTENSIONS BURN_EXTENSIONS; // constants @@ -40,6 +43,7 @@ enum BURN_CONTAINER_TYPE BURN_CONTAINER_TYPE_NONE, BURN_CONTAINER_TYPE_CABINET, BURN_CONTAINER_TYPE_SEVENZIP, + BURN_CONTAINER_TYPE_EXTENSION, }; enum BURN_CAB_OPERATION @@ -81,6 +85,8 @@ typedef struct _BURN_CONTAINER DWORD64 qwAttachedOffset; BOOL fActuallyAttached; // indicates whether an attached container is attached or missing. + BURN_EXTENSION* pExtension; + // mutable members BOOL fPlanned; LPWSTR sczSourcePath; @@ -127,6 +133,13 @@ typedef struct _BURN_CONTAINER_CONTEXT_CABINET DWORD cVirtualFilePointers; } BURN_CONTAINER_CONTEXT_CABINET; +typedef struct _BURN_CONTAINER_CONTEXT_BEX +{ + BURN_EXTENSION* pExtension; + LPWSTR szTempContainerPath; + LPVOID pExtensionContext; +} BURN_CONTAINER_CONTEXT_BEX; + typedef struct _BURN_CONTAINER_CONTEXT { HANDLE hFile; @@ -143,6 +156,7 @@ typedef struct _BURN_CONTAINER_CONTEXT union { BURN_CONTAINER_CONTEXT_CABINET Cabinet; + BURN_CONTAINER_CONTEXT_BEX Bex; }; } BURN_CONTAINER_CONTEXT; @@ -152,7 +166,8 @@ typedef struct _BURN_CONTAINER_CONTEXT HRESULT ContainersParseFromXml( __in BURN_CONTAINERS* pContainers, - __in IXMLDOMNode* pixnBundle + __in IXMLDOMNode* pixnBundle, + __in BURN_EXTENSIONS* pBurnExtensions ); HRESULT ContainersInitialize( __in BURN_CONTAINERS* pContainers, diff --git a/src/burn/engine/engine.mc b/src/burn/engine/engine.mc index 39aea60e4..5ab7117f8 100644 --- a/src/burn/engine/engine.mc +++ b/src/burn/engine/engine.mc @@ -1275,3 +1275,9 @@ Language=English Skipping MSI property '%1!ls!' because condition '%2!ls!' evaluates to %3!hs!. . +MessageId=701 +Severity=Warning +SymbolicName=MSG_EXT_ATTACHED_CONTAINER_FAILED +Language=English +Container extension can not open attached container '%1!ls!'. Unloading the container and retrying. +. diff --git a/src/burn/engine/manifest.cpp b/src/burn/engine/manifest.cpp index c0d67c192..01d5490b9 100644 --- a/src/burn/engine/manifest.cpp +++ b/src/burn/engine/manifest.cpp @@ -133,7 +133,7 @@ static HRESULT ParseFromXml( ExitOnFailure(hr, "Failed to parse update."); // parse containers - hr = ContainersParseFromXml(&pEngineState->containers, pixeBundle); + hr = ContainersParseFromXml(&pEngineState->containers, pixeBundle, &pEngineState->extensions); ExitOnFailure(hr, "Failed to parse containers."); // parse payloads diff --git a/src/burn/engine/section.cpp b/src/burn/engine/section.cpp index 989bfb175..b79c36ae3 100644 --- a/src/burn/engine/section.cpp +++ b/src/burn/engine/section.cpp @@ -255,7 +255,6 @@ extern "C" void SectionUninitialize( extern "C" HRESULT SectionGetAttachedContainerInfo( __in BURN_SECTION* pSection, __in DWORD iContainerIndex, - __in DWORD dwExpectedType, __out DWORD64* pqwOffset, __out DWORD64* pqwSize, __out BOOL* pfPresent @@ -269,11 +268,6 @@ extern "C" HRESULT SectionGetAttachedContainerInfo( 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) diff --git a/src/burn/engine/section.h b/src/burn/engine/section.h index 6c62ba444..65af58d80 100644 --- a/src/burn/engine/section.h +++ b/src/burn/engine/section.h @@ -43,7 +43,6 @@ void SectionUninitialize( HRESULT SectionGetAttachedContainerInfo( __in BURN_SECTION* pSection, __in DWORD iContainerIndex, - __in DWORD dwExpectedType, __out DWORD64* pqwOffset, __out DWORD64* pqwSize, __out BOOL* pfPresent diff --git a/src/libs/dutil/WixToolset.DUtil/fileutil.cpp b/src/libs/dutil/WixToolset.DUtil/fileutil.cpp index 31802d29c..70b84b5d0 100644 --- a/src/libs/dutil/WixToolset.DUtil/fileutil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/fileutil.cpp @@ -1760,3 +1760,66 @@ extern "C" HRESULT DAPI FileFromString( return hr; } + +HRESULT DAPI FileCopyPartial( + __in HANDLE hSource, + __in DWORD64 cbStart, + __in DWORD64 cbCopy, + __in_z LPCWSTR wzTarget + ) +{ + HRESULT hr = S_OK; + HANDLE hFile = INVALID_HANDLE_VALUE; + DWORD64 cbCurrPointer = 0; + LARGE_INTEGER liMaxRead = {}; + LARGE_INTEGER liRead = {}; + LARGE_INTEGER cbData = {}; + BYTE* pbData = NULL; + + FileExitOnNull(hSource && (hSource != INVALID_HANDLE_VALUE), hr, E_INVALIDARG, "Invalid argument hSource"); + FileExitOnNull(wzTarget && *wzTarget, hr, E_INVALIDARG, "wzTarget is null"); + + hr = FileSetPointer(hSource, 0, &cbCurrPointer, FILE_CURRENT); + FileExitOnFailure(hr, "Failed to get current file pointer"); + + hr = FileSetPointer(hSource, cbStart, NULL, FILE_BEGIN); + FileExitOnFailure(hr, "Failed to set file pointer"); + + if (!::GetFileSizeEx(hSource, &liMaxRead)) + { + FileExitWithLastError(hr, "Failed to get size of file"); + } + liMaxRead.QuadPart = min(liMaxRead.QuadPart - cbStart, cbCopy); + + hFile = ::CreateFileW(wzTarget, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + FileExitOnInvalidHandleWithLastError(hFile, hr, "Failed to open file: %ls", wzTarget); + + cbData.QuadPart = min(liMaxRead.QuadPart, 1024 * 1024 * 50); // Max 50MB chunks + pbData = (BYTE*)MemAlloc((SIZE_T)cbData.QuadPart, FALSE); + FileExitOnNull(pbData, hr, E_OUTOFMEMORY, "Failed to allocate memory"); + + while (liRead.QuadPart < liMaxRead.QuadPart) + { + LARGE_INTEGER cbRead = {}; + + cbData.QuadPart = min(cbData.QuadPart, liMaxRead.QuadPart - liRead.QuadPart); + if (!::ReadFile(hSource, pbData, cbData.LowPart, &cbRead.LowPart, NULL)) + { + FileExitWithLastError(hr, "Failed to read from file"); + } + FileExitOnNull((cbRead.LowPart == cbData.LowPart), hr, E_FAIL, "Failed to read data from file"); + + hr = FileWriteHandle(hFile, pbData, cbData.LowPart); + FileExitOnFailure(hr, "Failed to write to file"); + + liRead.QuadPart += cbRead.LowPart; + } + +LExit: + FileSetPointer(hSource, cbCurrPointer, NULL, FILE_BEGIN); // Recover initial position + + ReleaseMem(pbData); + ReleaseFile(hFile); + + return hr; +} diff --git a/src/libs/dutil/WixToolset.DUtil/inc/fileutil.h b/src/libs/dutil/WixToolset.DUtil/inc/fileutil.h index 868312dc6..ee526a9d3 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/fileutil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/fileutil.h @@ -140,6 +140,12 @@ HRESULT DAPI FileWriteHandle( __in_bcount_opt(cbData) LPCBYTE pbData, __in SIZE_T cbData ); +HRESULT DAPI FileCopyPartial( + __in HANDLE hSource, + __in DWORD64 cbStart, + __in DWORD64 cbCopy, + __in_z LPCWSTR wzTarget + ); HRESULT DAPI FileCopyUsingHandles( __in HANDLE hSource, __in HANDLE hTarget, diff --git a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 370364d18..0ddb68574 100644 --- a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -24,7 +24,7 @@ namespace WixToolset.Core.Burn /// internal class BindBundleCommand { - public BindBundleCommand(IBindContext context, IEnumerable backedExtensions) + public BindBundleCommand(IBindContext context, IEnumerable backedExtensions, IEnumerable containerExtensions) { this.ServiceProvider = context.ServiceProvider; @@ -44,6 +44,7 @@ public BindBundleCommand(IBindContext context, IEnumerable BackendExtensions { get; } + private IEnumerable ContainerExtensions { get; } + private Intermediate Output { get; } private string OutputPath { get; } @@ -443,7 +446,7 @@ public void Execute() WixBundleContainerSymbol uxContainer; IEnumerable uxPayloads; { - var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); + var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, this.ContainerExtensions, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); command.Execute(); fileTransfers.AddRange(command.FileTransfers); diff --git a/src/wix/WixToolset.Core.Burn/BundleBackend.cs b/src/wix/WixToolset.Core.Burn/BundleBackend.cs index cf1971f6e..160bb2991 100644 --- a/src/wix/WixToolset.Core.Burn/BundleBackend.cs +++ b/src/wix/WixToolset.Core.Burn/BundleBackend.cs @@ -13,13 +13,19 @@ public IBindResult Bind(IBindContext context) var extensionManager = context.ServiceProvider.GetService(); var backendExtensions = extensionManager.GetServices(); + var containerExtensions = extensionManager.GetServices(); foreach (var extension in backendExtensions) { extension.PreBackendBind(context); } - var command = new BindBundleCommand(context, backendExtensions); + foreach (var extension in containerExtensions) + { + extension.PreBackendBind(context); + } + + var command = new BindBundleCommand(context, backendExtensions, containerExtensions); command.Execute(); var result = context.ServiceProvider.GetService(); diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs index 95673ab00..52cef063c 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs @@ -12,7 +12,7 @@ namespace WixToolset.Core.Burn.Bundles /// /// Common functionality for Burn PE Writer & Reader for the WiX toolset. /// - /// This class encapsulates common functionality related to + /// This class encapsulates common functionality related to /// bundled/chained setup packages. /// /// @@ -178,13 +178,13 @@ public void Dispose() /// Input stream. /// Output stream. /// Optional count of bytes to copy. 0 indicates whole input stream from current should be copied. - protected static int CopyStream(Stream input, Stream output, int size) + protected static void CopyStream(Stream input, Stream output, uint size) { var bytes = new byte[4096]; - var total = 0; + var total = 0u; do { - var read = Math.Min(bytes.Length, size - total); + var read = (int)Math.Min(bytes.Length, size - total); read = input.Read(bytes, 0, read); if (0 == read) { @@ -192,10 +192,8 @@ protected static int CopyStream(Stream input, Stream output, int size) } output.Write(bytes, 0, read); - total += read; - } while (0 == size || total < size); - - return total; + total += (uint)read; + } while (0u == size || total < size); } /// @@ -320,7 +318,7 @@ private bool GetWixburnSectionInfo(BinaryReader reader) this.wixburnRawDataSize = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA); - // we need 52 bytes for the manifest header, which is always going to fit in + // we need 52 bytes for the manifest header, which is always going to fit in // the smallest alignment (512 bytes), but just to be paranoid... if (BURN_SECTION_MIN_SIZE > this.wixburnRawDataSize) { diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs index d8e49397c..3cca8fcb3 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs @@ -6,8 +6,12 @@ namespace WixToolset.Core.Burn.Bundles using System.Collections; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Xml; using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Extensibility; using WixToolset.Extensibility.Services; /// @@ -26,7 +30,7 @@ internal class BurnReader : BurnCommon private bool disposed; private BinaryReader binaryReader; - private readonly List attachedContainerPayloadNames; + private readonly Dictionary attachedContainerPayloadNames; private readonly IFileSystem fileSystem; /// @@ -38,7 +42,7 @@ internal class BurnReader : BurnCommon private BurnReader(IMessaging messaging, IFileSystem fileSystem, string fileExe) : base(messaging, fileExe) { - this.attachedContainerPayloadNames = new List(); + this.attachedContainerPayloadNames = new Dictionary(); this.fileSystem = fileSystem; } @@ -85,6 +89,7 @@ public bool ExtractUXContainer(string outputDirectory, string tempDirectory) return false; } + Directory.CreateDirectory(tempDirectory); Directory.CreateDirectory(outputDirectory); var tempCabPath = Path.Combine(tempDirectory, "ux.cab"); var manifestOriginalPath = Path.Combine(outputDirectory, "0"); @@ -94,7 +99,7 @@ public bool ExtractUXContainer(string outputDirectory, string tempDirectory) this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin); using (Stream tempCab = this.fileSystem.OpenFile(null, tempCabPath, FileMode.Create, FileAccess.Write, FileShare.Read)) { - BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)uxContainerSlot.Size); + BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, uxContainerSlot.Size); } var cabinet = new Cabinet(tempCabPath); @@ -135,7 +140,73 @@ public bool ExtractUXContainer(string outputDirectory, string tempDirectory) var sourcePath = sourcePathNode.Value; var destinationPath = Path.Combine(containerNode.Value, filePathNode.Value); - this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath)); + this.attachedContainerPayloadNames[sourcePath] = destinationPath; + } + } + + return true; + } + + /// + /// Extracts detached containers to the output directory. + /// + /// Directory to write extracted files to. + /// Scratch directory. + /// UX extraction folder. If null or empty, a UX folder will be created within tempDirectory + /// Container extensions + /// True if successful, false otherwise + public bool ExtractDetachedContainers(string outputDirectory, string tempDirectory, string uxOutputDirectory, IEnumerable containerExtensions) + { + if (String.IsNullOrEmpty(uxOutputDirectory)) + { + uxOutputDirectory = Path.Combine(tempDirectory, "UX", "Final"); + } + + var manifestPath = Path.Combine(uxOutputDirectory, "manifest.xml"); + if (!File.Exists(manifestPath)) + { + var uxTempDirectory = Path.Combine(tempDirectory, "UX", "Temp"); + if (!this.ExtractUXContainer(uxOutputDirectory, uxTempDirectory)) + { + return false; + } + } + + Directory.CreateDirectory(tempDirectory); + Directory.CreateDirectory(outputDirectory); + + var extensionManifestPath = Path.Combine(uxOutputDirectory, BurnCommon.BundleExtensionDataFileName); + var document = new XmlDocument(); + document.Load(manifestPath); + var nsmgr = new XmlNamespaceManager(document.NameTable); + nsmgr.AddNamespace("burn", BurnCommon.BurnNamespace); + var detachedContainerNodes = document.SelectNodes("/burn:BurnManifest/burn:Container[not(@Attached = 'yes')]", nsmgr); + + var exeDirectory = Path.GetDirectoryName(this.fileExe); + foreach (var node in detachedContainerNodes) + { + var containerElement = (XmlElement)node; + var containerName = containerElement.GetAttribute("FilePath"); + + var containerId = containerElement.GetAttribute("Id"); + var containerPath = Path.Combine(exeDirectory, containerName); + var extractDirectory = Path.Combine(tempDirectory, containerName); + + Directory.CreateDirectory(extractDirectory); + + this.ExtractContainer(containerId, containerPath, extractDirectory, containerElement, containerExtensions, extensionManifestPath); + + var containerPayloadNodes = document.SelectNodes($"/burn:BurnManifest/burn:Payload[@Packaging='embedded' and @Container='{containerId}']", nsmgr); + foreach (var payloadNode in containerPayloadNodes) + { + var payloadElement = (XmlElement)payloadNode; + var srcFileName = payloadElement.GetAttribute("SourcePath"); + var dstFileName = payloadElement.GetAttribute("FilePath"); + + var sourcePath = Path.Combine(extractDirectory, srcFileName); + var destinationPath = Path.Combine(outputDirectory, containerName, dstFileName); + + this.fileSystem.MoveFile(null, sourcePath, destinationPath); } } @@ -147,8 +218,10 @@ public bool ExtractUXContainer(string outputDirectory, string tempDirectory) /// /// Directory to write extracted files to. /// Scratch directory. + /// UX extraction folder. If null or empty, a UX folder will be created within tempDirectory + /// Container extensions /// True if successful, false otherwise - public bool ExtractAttachedContainers(string outputDirectory, string tempDirectory) + public bool ExtractAttachedContainers(string outputDirectory, string tempDirectory, string uxOutputDirectory, IEnumerable containerExtensions) { // No attached containers to extract if (this.AttachedContainers.Count < 2) @@ -161,21 +234,44 @@ public bool ExtractAttachedContainers(string outputDirectory, string tempDirecto return false; } + if (String.IsNullOrEmpty(uxOutputDirectory)) + { + uxOutputDirectory = Path.Combine(tempDirectory, "UX", "Final"); + } + + var uxTempDirectory = Path.Combine(tempDirectory, "UX", "Temp"); + if (!this.ExtractUXContainer(uxOutputDirectory, uxTempDirectory)) + { + return false; + } + + var extensionManifestPath = Path.Combine(uxOutputDirectory, BurnCommon.BundleExtensionDataFileName); + var manifestPath = Path.Combine(uxOutputDirectory, "manifest.xml"); + var document = new XmlDocument(); + document.Load(manifestPath); + var nsmgr = new XmlNamespaceManager(document.NameTable); + nsmgr.AddNamespace("burn", BurnCommon.BurnNamespace); + Directory.CreateDirectory(outputDirectory); var nextAddress = this.EngineSize; - for (var i = 1; i < this.AttachedContainers.Count; i++) + for (var i = 1; i < this.AttachedContainers.Count; ++i) { var cntnr = this.AttachedContainers[i]; - var tempCabPath = Path.Combine(tempDirectory, $"a{i}.cab"); + var tempCabPath = Path.Combine(tempDirectory, $"a{i}.data"); this.binaryReader.BaseStream.Seek(nextAddress, SeekOrigin.Begin); using (Stream tempCab = this.fileSystem.OpenFile(null, tempCabPath, FileMode.Create, FileAccess.Write, FileShare.Read)) { - BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)cntnr.Size); + BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, cntnr.Size); } - var cabinet = new Cabinet(tempCabPath); - cabinet.Extract(outputDirectory); + if (!(document.SelectSingleNode($"/burn:BurnManifest/burn:Container[@Attached = 'yes' and @AttachedIndex = {i}]", nsmgr) is XmlElement containerElement)) + { + this.Messaging.Write(ErrorMessages.InvalidBurnManifestContainers(null, this.AttachedContainers.Count, i)); + return false; + } + var containerId = containerElement.GetAttribute("Id"); + this.ExtractContainer(containerId, tempCabPath, outputDirectory, containerElement, containerExtensions, extensionManifestPath); nextAddress += cntnr.Size; } @@ -191,6 +287,55 @@ public bool ExtractAttachedContainers(string outputDirectory, string tempDirecto return true; } + private void ExtractContainer(string containerId, string containerPath, string outputDirectory, XmlElement containerElement, IEnumerable containerExtensions, string extensionManifestPath) + { + string containerType = containerElement.GetAttribute("Type"); + if (containerType.Equals("Extension")) + { + IBurnContainerExtension containerExtension = null; + string containerExtensionId = containerElement.GetAttribute("ExtensionId"); + if ((containerExtensions != null) && !String.IsNullOrEmpty(containerExtensionId)) + { + foreach (var extension in containerExtensions) + { + if (extension.ContainerExtensionIds.Contains(containerExtensionId)) + { + containerExtension = extension; + break; + } + } + } + + if (containerExtension == null) + { + this.Messaging.Write(WarningMessages.MissingContainerExtension(null, containerId, containerExtensionId)); + return; + } + + // Get extension data from manifest + var extensionManifestDocument = new XmlDocument(); + extensionManifestDocument.Load(extensionManifestPath); + var extensionNsmgr = new XmlNamespaceManager(extensionManifestDocument.NameTable); + extensionNsmgr.AddNamespace("ed", BurnConstants.BundleExtensionDataNamespace); + var extensionDataNode = extensionManifestDocument.SelectSingleNode($"/ed:BundleExtensionData/ed:BundleExtension[@Id='{containerExtensionId}']", extensionNsmgr) as XmlElement; + + try + { + containerExtension.ExtractContainer(containerPath, outputDirectory, containerId, extensionDataNode); + } + catch (Exception ex) + { + this.Messaging.Write(ErrorMessages.ContainerExtractFailed(null, containerId, containerExtensionId, ex.Message)); + return; + } + } + else + { + var cabinet = new Cabinet(containerPath); + cabinet.Extract(outputDirectory); + } + } + /// /// Dispose object. /// diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs index 29664177c..74e31ac48 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs @@ -255,7 +255,7 @@ private bool AppendContainer(Stream containerStream, uint containerSize, uint bu // Append the container to the end of the existing bits. this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End); - BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize); + BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, containerSize); this.binaryWriter.BaseStream.Flush(); return true; diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 0a11ea3aa..80ca6b47f 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -732,6 +732,13 @@ private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string e writer.WriteAttributeString("Attached", "yes"); writer.WriteAttributeString("Primary", "yes"); } + + // Extension container + if (!String.IsNullOrEmpty(container.BundleExtensionRef)) + { + writer.WriteAttributeString("Type", "Extension"); + writer.WriteAttributeString("ExtensionId", container.BundleExtensionRef); + } } private void WriteBurnManifestPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs index 8e83408a6..a2a6dc331 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs @@ -10,12 +10,13 @@ namespace WixToolset.Core.Burn.Bundles using WixToolset.Data; using WixToolset.Data.Burn; using WixToolset.Data.Symbols; + using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; internal class CreateNonUXContainers { - public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable containerSymbols, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) + public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, IEnumerable containerExtensions, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable containerSymbols, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) { this.BackendHelper = backendHelper; this.Messaging = messaging; @@ -25,6 +26,7 @@ public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, this.IntermediateFolder = intermediateFolder; this.LayoutFolder = layoutFolder; this.DefaultCompressionLevel = defaultCompressionLevel; + this.ContainerExtensions = containerExtensions; } public IEnumerable FileTransfers { get; private set; } @@ -41,6 +43,8 @@ public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, private IMessaging Messaging { get; } + private IEnumerable ContainerExtensions { get; } + private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } private Dictionary PayloadSymbols { get; } @@ -137,11 +141,29 @@ public void Execute() private void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads) { - var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); - command.Execute(); + if (String.IsNullOrEmpty(container.BundleExtensionRef)) + { + var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); + command.Execute(); + + container.Hash = command.Hash; + container.Size = command.Size; + return; + } + + container.Hash = null; + container.Size = 0; + IBurnContainerExtension containerExtension = this.ContainerExtensions.FirstOrDefault(ce => (ce.ContainerExtensionIds != null) && ce.ContainerExtensionIds.Contains(container.BundleExtensionRef)); + if (containerExtension == null) + { + this.Messaging.Write(ErrorMessages.MissingContainerExtension(container.SourceLineNumbers, container.Id.Id, container.BundleExtensionRef)); + return; + } + + containerExtension.CreateContainer(container, containerPayloads, out string sha512, out long size); - container.Hash = command.Hash; - container.Size = command.Size; + container.Hash = sha512; + container.Size = size; } } } diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs b/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs index 3b2ecdd7a..15c2301f3 100644 --- a/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs +++ b/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs @@ -26,9 +26,14 @@ public static Message EmptyContainer(SourceLineNumber sourceLineNumbers, string return Message(sourceLineNumbers, Ids.EmptyContainer, "The Container '{0}' is being ignored because it doesn't have any payloads.", containerId); } - public static Message FailedToExtractAttachedContainers(SourceLineNumber sourceLineNumbers) + public static Message FailedToExtractAttachedContainers(SourceLineNumber sourceLineNumbers, string error) { - return Message(sourceLineNumbers, Ids.FailedToExtractAttachedContainers, "Failed to extract attached container. This most often happens when extracting a stripped bundle from the package cache, which is not supported."); + return Message(sourceLineNumbers, Ids.FailedToExtractAttachedContainers, "Failed to extract attached container. This most often happens when extracting a stripped bundle from the package cache, which is not supported. {0}", error); + } + + public static Message FailedToExtractDetachedContainers(SourceLineNumber sourceLineNumbers, string error) + { + return Message(sourceLineNumbers, Ids.FailedToExtractDetachedContainers, "Failed to extract detached containers. {0}", error); } public static Message HiddenBundleNotSupported(SourceLineNumber sourceLineNumbers, string packageId) @@ -67,6 +72,7 @@ public enum Ids HiddenBundleNotSupported = 8506, UnknownMsiPackagePlatform = 8507, CannotParseBundleVersionAsFourPartVersion = 8508, + FailedToExtractDetachedContainers = 8509, } // last available is 8999. 9000 is VerboseMessages. } } diff --git a/src/wix/WixToolset.Core.Burn/CommandLine/ExtractSubcommand.cs b/src/wix/WixToolset.Core.Burn/CommandLine/ExtractSubcommand.cs index a6660a06f..075cee40b 100644 --- a/src/wix/WixToolset.Core.Burn/CommandLine/ExtractSubcommand.cs +++ b/src/wix/WixToolset.Core.Burn/CommandLine/ExtractSubcommand.cs @@ -3,11 +3,13 @@ namespace WixToolset.Core.Burn.CommandLine { using System; + using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using WixToolset.Core.Burn.Bundles; using WixToolset.Data; + using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; @@ -17,12 +19,17 @@ public ExtractSubcommand(IServiceProvider serviceProvider) { this.Messaging = serviceProvider.GetService(); this.FileSystem = serviceProvider.GetService(); + + var extensionManager = serviceProvider.GetService(); + this.ContainerExtensions = extensionManager.GetServices(); } private IMessaging Messaging { get; } private IFileSystem FileSystem { get; } + private IEnumerable ContainerExtensions { get; } + private string InputPath { get; set; } private string IntermediateFolder { get; set; } @@ -35,7 +42,7 @@ public override CommandLineHelp GetCommandLineHelp() { return new CommandLineHelp("Extracts the contents of a bundle.", "burn extract [options] bundle.exe -o outputfolder", new[] { - new CommandLineHelpSwitch("-intermediateFolder", "Optional working folder. If not specified %TMP% will be used."), + new CommandLineHelpSwitch("-intermediateFolder", "Optional working folder. If not specified a subfolder will be created under %TMP% folder."), new CommandLineHelpSwitch("-outba", "-oba", "Folder to extract the bundle bootstrapper application to."), new CommandLineHelpSwitch("-out", "-o", "Folder to extract the bundle contents to."), }); @@ -55,7 +62,8 @@ public override Task ExecuteAsync(CancellationToken cancellationToken) { if (String.IsNullOrEmpty(this.IntermediateFolder)) { - this.IntermediateFolder = Path.GetTempPath(); + this.IntermediateFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(this.IntermediateFolder); } using (var reader = BurnReader.Open(this.Messaging, this.FileSystem, this.InputPath)) @@ -65,16 +73,25 @@ public override Task ExecuteAsync(CancellationToken cancellationToken) reader.ExtractUXContainer(this.ExtractBootstrapperApplicationPath, this.IntermediateFolder); } - try + if (!String.IsNullOrEmpty(this.ExtractContainersPath)) { - if (!String.IsNullOrEmpty(this.ExtractContainersPath)) + try { - reader.ExtractAttachedContainers(this.ExtractContainersPath, this.IntermediateFolder); + reader.ExtractAttachedContainers(this.ExtractContainersPath, this.IntermediateFolder, this.ExtractBootstrapperApplicationPath, this.ContainerExtensions); + } + catch (Exception ex) + { + this.Messaging.Write(BurnBackendWarnings.FailedToExtractAttachedContainers(null, ex.Message)); + } + + try + { + reader.ExtractDetachedContainers(this.ExtractContainersPath, this.IntermediateFolder, this.ExtractBootstrapperApplicationPath, this.ContainerExtensions); + } + catch (Exception ex) + { + this.Messaging.Write(BurnBackendWarnings.FailedToExtractDetachedContainers(null, ex.Message)); } - } - catch - { - this.Messaging.Write(BurnBackendWarnings.FailedToExtractAttachedContainers(new SourceLineNumber(this.ExtractContainersPath))); } } }