// 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.Dtf.WindowsInstaller { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Threading; /// /// [MSI 4.5] Handle to a multi-session install transaction. /// ///

/// Win32 MSI APIs: /// MsiBeginTransaction /// MsiJoinTransaction /// MsiEndTransaction ///

public class Transaction : InstallerHandle { private string name; private IntPtr ownerChangeEvent; private IList> ownerChangeListeners; /// /// [MSI 4.5] Begins transaction processing of a multi-package installation. /// /// Name of the multi-package installation. /// Select optional behavior when beginning the transaction. /// The transaction could not be initialized. ///

/// Win32 MSI API: /// MsiBeginTransaction ///

public Transaction(string name, TransactionAttributes attributes) : this(name, Transaction.Begin(name, attributes), true) { } /// /// Internal constructor. /// /// /// The second parameter is an array in order to receive multiple values from the initialization method. /// private Transaction(string name, IntPtr[] handles, bool ownsHandle) : base(handles[0], ownsHandle) { this.name = name; this.ownerChangeEvent = handles[1]; this.ownerChangeListeners = new List>(); } /// /// Creates a new Transaction object from an integer handle. /// /// Integer transaction handle /// true to close the handle when this object is disposed public static Transaction FromHandle(IntPtr handle, bool ownsHandle) { return new Transaction(handle.ToString(), new IntPtr[] { handle, IntPtr.Zero }, ownsHandle); } /// /// Gets the name of the transaction. /// public string Name { get { return name; } } /// /// Notifies listeners when the process that owns the transaction changed. /// public event EventHandler OwnerChanged { add { this.ownerChangeListeners.Add(value); if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count == 1) { new Thread(this.WaitForOwnerChange).Start(); } } remove { this.ownerChangeListeners.Remove(value); } } private void OnOwnerChanged() { EventArgs e = new EventArgs(); foreach (EventHandler handler in this.ownerChangeListeners) { handler(this, e); } } private void WaitForOwnerChange() { int ret = NativeMethods.WaitForSingleObject(this.ownerChangeEvent, -1); if (ret == 0) { this.OnOwnerChanged(); } else { throw new InstallerException(); } } /// /// Makes the current process the owner of the multi-package installation transaction. /// /// Select optional behavior when joining the transaction. /// The transaction handle is not valid. /// The transaction could not be joined. ///

/// Win32 MSI API: /// MsiJoinTransaction ///

public void Join(TransactionAttributes attributes) { IntPtr hChangeOfOwnerEvent; uint ret = NativeMethods.MsiJoinTransaction((int) this.Handle, (int) attributes, out hChangeOfOwnerEvent); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } this.ownerChangeEvent = hChangeOfOwnerEvent; if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count >= 1) { new Thread(this.WaitForOwnerChange).Start(); } } /// /// Ends the install transaction and commits all changes to the system belonging to the transaction. /// /// The transaction could not be committed. ///

/// Runs any Commit Custom Actions and commits to the system any changes to Win32 or common language /// runtime assemblies. Deletes the rollback script, and after using this option, the transaction's /// changes can no longer be undone with a Rollback Installation. ///

/// This method can only be called by the current owner of the transaction. ///

/// Win32 MSI API: /// MsiEndTransaction ///

public void Commit() { this.End(true); } /// /// Ends the install transaction and undoes changes to the system belonging to the transaction. /// /// The transaction could not be rolled back. ///

/// This method can only be called by the current owner of the transaction. ///

/// Win32 MSI API: /// MsiEndTransaction ///

public void Rollback() { this.End(false); } private static IntPtr[] Begin(string transactionName, TransactionAttributes attributes) { int hTransaction; IntPtr hChangeOfOwnerEvent; uint ret = NativeMethods.MsiBeginTransaction(transactionName, (int) attributes, out hTransaction, out hChangeOfOwnerEvent); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } return new IntPtr[] { (IntPtr) hTransaction, hChangeOfOwnerEvent }; } [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] private void End(bool commit) { uint ret = NativeMethods.MsiEndTransaction(commit ? 1 : 0); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } } } }