| 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 | namespace WixToolset.Dtf.WindowsInstaller |
| 4 | { |
| 5 | using System; |
| 6 | using System.Collections.Generic; |
| 7 | using System.Diagnostics.CodeAnalysis; |
| 8 | using System.Runtime.InteropServices; |
| 9 | using System.Threading; |
| 10 | |
| 11 | /// <summary> |
| 12 | /// [MSI 4.5] Handle to a multi-session install transaction. |
| 13 | /// </summary> |
| 14 | /// <remarks><p> |
| 15 | /// Win32 MSI APIs: |
| 16 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msibegintransaction.asp">MsiBeginTransaction</a> |
| 17 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msijointransaction.asp">MsiJoinTransaction</a> |
| 18 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a> |
| 19 | /// </p></remarks> |
| 20 | public class Transaction : InstallerHandle |
| 21 | { |
| 22 | private string name; |
| 23 | private IntPtr ownerChangeEvent; |
| 24 | private IList<EventHandler<EventArgs>> ownerChangeListeners; |
| 25 | |
| 26 | /// <summary> |
| 27 | /// [MSI 4.5] Begins transaction processing of a multi-package installation. |
| 28 | /// </summary> |
| 29 | /// <param name="name">Name of the multi-package installation.</param> |
| 30 | /// <param name="attributes">Select optional behavior when beginning the transaction.</param> |
| 31 | /// <exception cref="InstallerException">The transaction could not be initialized.</exception> |
| 32 | /// <remarks><p> |
| 33 | /// Win32 MSI API: |
| 34 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msibegintransaction.asp">MsiBeginTransaction</a> |
| 35 | /// </p></remarks> |
| 36 | public Transaction(string name, TransactionAttributes attributes) |
| 37 | : this(name, Transaction.Begin(name, attributes), true) |
| 38 | { |
| 39 | } |
| 40 | |
| 41 | /// <summary> |
| 42 | /// Internal constructor. |
| 43 | /// </summary> |
| 44 | /// <remarks> |
| 45 | /// The second parameter is an array in order to receive multiple values from the initialization method. |
| 46 | /// </remarks> |
| 47 | private Transaction(string name, IntPtr[] handles, bool ownsHandle) |
| 48 | : base(handles[0], ownsHandle) |
| 49 | { |
| 50 | this.name = name; |
| 51 | this.ownerChangeEvent = handles[1]; |
| 52 | this.ownerChangeListeners = new List<EventHandler<EventArgs>>(); |
| 53 | } |
| 54 | |
| 55 | /// <summary> |
| 56 | /// Creates a new Transaction object from an integer handle. |
| 57 | /// </summary> |
| 58 | /// <param name="handle">Integer transaction handle</param> |
| 59 | /// <param name="ownsHandle">true to close the handle when this object is disposed</param> |
| 60 | public static Transaction FromHandle(IntPtr handle, bool ownsHandle) |
| 61 | { |
| 62 | return new Transaction(handle.ToString(), new IntPtr[] { handle, IntPtr.Zero }, ownsHandle); |
| 63 | } |
| 64 | |
| 65 | /// <summary> |
| 66 | /// Gets the name of the transaction. |
| 67 | /// </summary> |
| 68 | public string Name |
| 69 | { |
| 70 | get |
| 71 | { |
| 72 | return name; |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | /// <summary> |
| 77 | /// Notifies listeners when the process that owns the transaction changed. |
| 78 | /// </summary> |
| 79 | public event EventHandler<EventArgs> OwnerChanged |
| 80 | { |
| 81 | add |
| 82 | { |
| 83 | this.ownerChangeListeners.Add(value); |
| 84 | |
| 85 | if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count == 1) |
| 86 | { |
| 87 | new Thread(this.WaitForOwnerChange).Start(); |
| 88 | } |
| 89 | } |
| 90 | remove |
| 91 | { |
| 92 | this.ownerChangeListeners.Remove(value); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | private void OnOwnerChanged() |
| 97 | { |
| 98 | EventArgs e = new EventArgs(); |
| 99 | foreach (EventHandler<EventArgs> handler in this.ownerChangeListeners) |
| 100 | { |
| 101 | handler(this, e); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | private void WaitForOwnerChange() |
| 106 | { |
| 107 | int ret = NativeMethods.WaitForSingleObject(this.ownerChangeEvent, -1); |
| 108 | if (ret == 0) |
| 109 | { |
| 110 | this.OnOwnerChanged(); |
| 111 | } |
| 112 | else |
| 113 | { |
| 114 | throw new InstallerException(); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | /// <summary> |
| 119 | /// Makes the current process the owner of the multi-package installation transaction. |
| 120 | /// </summary> |
| 121 | /// <param name="attributes">Select optional behavior when joining the transaction.</param> |
| 122 | /// <exception cref="InvalidHandleException">The transaction handle is not valid.</exception> |
| 123 | /// <exception cref="InstallerException">The transaction could not be joined.</exception> |
| 124 | /// <remarks><p> |
| 125 | /// Win32 MSI API: |
| 126 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msijointransaction.asp">MsiJoinTransaction</a> |
| 127 | /// </p></remarks> |
| 128 | public void Join(TransactionAttributes attributes) |
| 129 | { |
| 130 | IntPtr hChangeOfOwnerEvent; |
| 131 | uint ret = NativeMethods.MsiJoinTransaction((int) this.Handle, (int) attributes, out hChangeOfOwnerEvent); |
| 132 | if (ret != 0) |
| 133 | { |
| 134 | throw InstallerException.ExceptionFromReturnCode(ret); |
| 135 | } |
| 136 | |
| 137 | this.ownerChangeEvent = hChangeOfOwnerEvent; |
| 138 | if (this.ownerChangeEvent != IntPtr.Zero && this.ownerChangeListeners.Count >= 1) |
| 139 | { |
| 140 | new Thread(this.WaitForOwnerChange).Start(); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | /// <summary> |
| 145 | /// Ends the install transaction and commits all changes to the system belonging to the transaction. |
| 146 | /// </summary> |
| 147 | /// <exception cref="InstallerException">The transaction could not be committed.</exception> |
| 148 | /// <remarks><p> |
| 149 | /// Runs any Commit Custom Actions and commits to the system any changes to Win32 or common language |
| 150 | /// runtime assemblies. Deletes the rollback script, and after using this option, the transaction's |
| 151 | /// changes can no longer be undone with a Rollback Installation. |
| 152 | /// </p><p> |
| 153 | /// This method can only be called by the current owner of the transaction. |
| 154 | /// </p><p> |
| 155 | /// Win32 MSI API: |
| 156 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a> |
| 157 | /// </p></remarks> |
| 158 | public void Commit() |
| 159 | { |
| 160 | this.End(true); |
| 161 | } |
| 162 | |
| 163 | /// <summary> |
| 164 | /// Ends the install transaction and undoes changes to the system belonging to the transaction. |
| 165 | /// </summary> |
| 166 | /// <exception cref="InstallerException">The transaction could not be rolled back.</exception> |
| 167 | /// <remarks><p> |
| 168 | /// This method can only be called by the current owner of the transaction. |
| 169 | /// </p><p> |
| 170 | /// Win32 MSI API: |
| 171 | /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msiendtransaction.asp">MsiEndTransaction</a> |
| 172 | /// </p></remarks> |
| 173 | public void Rollback() |
| 174 | { |
| 175 | this.End(false); |
| 176 | } |
| 177 | |
| 178 | private static IntPtr[] Begin(string transactionName, TransactionAttributes attributes) |
| 179 | { |
| 180 | int hTransaction; |
| 181 | IntPtr hChangeOfOwnerEvent; |
| 182 | uint ret = NativeMethods.MsiBeginTransaction(transactionName, (int) attributes, out hTransaction, out hChangeOfOwnerEvent); |
| 183 | if (ret != 0) |
| 184 | { |
| 185 | throw InstallerException.ExceptionFromReturnCode(ret); |
| 186 | } |
| 187 | |
| 188 | return new IntPtr[] { (IntPtr) hTransaction, hChangeOfOwnerEvent }; |
| 189 | } |
| 190 | |
| 191 | [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] |
| 192 | private void End(bool commit) |
| 193 | { |
| 194 | uint ret = NativeMethods.MsiEndTransaction(commit ? 1 : 0); |
| 195 | if (ret != 0) |
| 196 | { |
| 197 | throw InstallerException.ExceptionFromReturnCode(ret); |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | } |