// 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.Core.Cab { using System; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using WixToolset.Core.Bind; using WixToolset.Core.Native; using WixToolset.Data; /// /// Wrapper class around interop with wixcab.dll to compress files into a cabinet. /// public sealed class WixCreateCab : IDisposable { private static readonly string CompressionLevelVariable = "WIX_COMPRESSION_LEVEL"; private IntPtr handle = IntPtr.Zero; private bool disposed; private int maxSize; /// /// Creates a cabinet. /// /// Name of cabinet to create. /// Directory to create cabinet in. /// Maximum number of files that will be added to cabinet. /// Maximum size of cabinet. /// Maximum threshold for each cabinet. /// Level of compression to apply. public WixCreateCab(string cabName, string cabDir, int maxFiles, int maxSize, int maxThresh, CompressionLevel compressionLevel) { string compressionLevelVariable = Environment.GetEnvironmentVariable(CompressionLevelVariable); this.maxSize = maxSize; try { // Override authored compression level if environment variable is present. if (!String.IsNullOrEmpty(compressionLevelVariable)) { compressionLevel = WixCreateCab.CompressionLevelFromString(compressionLevelVariable); } } catch (WixException) { throw new WixException(WixErrors.IllegalEnvironmentVariable(CompressionLevelVariable, compressionLevelVariable)); } if (String.IsNullOrEmpty(cabDir)) { cabDir = Directory.GetCurrentDirectory(); } try { NativeMethods.CreateCabBegin(cabName, cabDir, (uint)maxFiles, (uint)maxSize, (uint)maxThresh, (uint)compressionLevel, out this.handle); } catch (COMException ce) { // If we get a "the file exists" error, we must have a full temp directory - so report the issue if (0x80070050 == unchecked((uint)ce.ErrorCode)) { throw new WixException(WixErrors.FullTempDirectory("WSC", Path.GetTempPath())); } throw; } } /// /// Destructor for cabinet creation. /// ~WixCreateCab() { this.Dispose(); } /// /// Converts a compression level from its string to its enum value. /// /// Compression level as a string. /// CompressionLevel enum value public static CompressionLevel CompressionLevelFromString(string compressionLevel) { switch (compressionLevel.ToLower(CultureInfo.InvariantCulture)) { case "low": return CompressionLevel.Low; case "medium": return CompressionLevel.Medium; case "high": return CompressionLevel.High; case "none": return CompressionLevel.None; case "mszip": return CompressionLevel.Mszip; default: throw new WixException(WixErrors.IllegalCompressionLevel(compressionLevel)); } } /// /// Adds a file to the cabinet. /// /// The file facade of the file to add. public void AddFile(FileFacade fileFacade) { MsiInterop.MSIFILEHASHINFO hashInterop = new MsiInterop.MSIFILEHASHINFO(); if (null != fileFacade.Hash) { hashInterop.FileHashInfoSize = 20; hashInterop.Data0 = (int)fileFacade.Hash[2]; hashInterop.Data1 = (int)fileFacade.Hash[3]; hashInterop.Data2 = (int)fileFacade.Hash[4]; hashInterop.Data3 = (int)fileFacade.Hash[5]; this.AddFile(fileFacade.WixFile.Source, fileFacade.File.File, hashInterop); } else { this.AddFile(fileFacade.WixFile.Source, fileFacade.File.File); } } /// /// Adds a file to the cabinet. /// /// The file to add. /// The token for the file. public void AddFile(string file, string token) { this.AddFile(file, token, null); } /// /// Adds a file to the cabinet with an optional MSI file hash. /// /// The file to add. /// The token for the file. /// The MSI file hash of the file. private void AddFile(string file, string token, MsiInterop.MSIFILEHASHINFO fileHash) { try { NativeMethods.CreateCabAddFile(file, token, fileHash, this.handle); } catch (COMException ce) { if (0x80004005 == unchecked((uint)ce.ErrorCode)) // E_FAIL { throw new WixException(WixErrors.CreateCabAddFileFailed()); } else if (0x80070070 == unchecked((uint)ce.ErrorCode)) // ERROR_DISK_FULL { throw new WixException(WixErrors.CreateCabInsufficientDiskSpace()); } else { throw; } } catch (DirectoryNotFoundException) { throw new WixFileNotFoundException(file); } catch (FileNotFoundException) { throw new WixFileNotFoundException(file); } } /// /// Complete/commit the cabinet - this must be called before Dispose so that errors will be /// reported on the same thread. /// This Complete should be used with no Cabinet splitting as it has the split cabinet names callback address as Zero /// public void Complete() { this.Complete(IntPtr.Zero); } /// /// Complete/commit the cabinet - this must be called before Dispose so that errors will be /// reported on the same thread. /// /// Address of Binder's callback function for Cabinet Splitting public void Complete(IntPtr newCabNamesCallBackAddress) { if (IntPtr.Zero != this.handle) { try { if (newCabNamesCallBackAddress != IntPtr.Zero && this.maxSize != 0) { NativeMethods.CreateCabFinish(this.handle, newCabNamesCallBackAddress); } else { NativeMethods.CreateCabFinish(this.handle, IntPtr.Zero); } GC.SuppressFinalize(this); this.disposed = true; } catch (COMException ce) { if (0x80004005 == unchecked((uint)ce.ErrorCode)) // E_FAIL { // This error seems to happen, among other situations, when cabbing more than 0xFFFF files throw new WixException(WixErrors.FinishCabFailed()); } else if (0x80070070 == unchecked((uint)ce.ErrorCode)) // ERROR_DISK_FULL { throw new WixException(WixErrors.CreateCabInsufficientDiskSpace()); } else { throw; } } finally { this.handle = IntPtr.Zero; } } } /// /// Cancels ("rolls back") the creation of the cabinet. /// Don't throw WiX errors from here, because we're in a different thread, and they won't be reported correctly. /// public void Dispose() { if (!this.disposed) { if (IntPtr.Zero != this.handle) { NativeMethods.CreateCabCancel(this.handle); this.handle = IntPtr.Zero; } GC.SuppressFinalize(this); this.disposed = true; } } } }