Thursday, December 14, 2006 1:12 PM bart

Windows Vista - Introducing TxR in C# (Part 1)

Introduction

Last month I blogged about TxF in Windows Vista and how to use it from your own application to perform transactional file operations. You can find both posts over here:

Today we'll take a look at TxR brother, TxR which stands for Transactional Registry, a brand new feature in Windows Vista (and Longhorn Server) that makes it possible to perform registry operations in a transactional manner. A typical usage scenario might be a setup application that's performing a bunch of file operations (using TxF) and registry operations (using TxR) which can either be committed all together or rolled back in case something went wrong (or the user cancelled the installer).

The next two paragraphs are borrowed from my orginal TxF posts, but adapted for TxR and to reflect the situation in Windows Vista RTM.

Your new friends are *Transacted

Probably you know about the Reg* functions in the Windows API, like RegDeleteKey, RegDeleteKeyEx, RegCreateKeyEx, amongst many others (take a look at the MSDN documentation for a full list). All these functions remain unchanged in Windows Vista, with the same semantics.

However, if you want to perform transactional registry operations, a new set of functions is in place to help you out: Reg*Transacted, for example RegDeleteKeyTransacted and RegOpenKeyTransacted (which makes all subsequent operations performed against that key - with classic functions to set values and stuff). All of the transactional functions rely on the Kernel Transaction Manager (KTM) which manages transactions in the operating system. As an example, consider the following RegDeleteKeyTransacted function which has a pretty simple signature:

LONG RegDeleteKeyTransacted( HKEY hKey, LPCTSTR lpSubKey, REGSAM samDesired, DWORD Reserved, HANDLE hTransaction, PVOID pExtendedParameter );

The last two parameters are the ones used to make the operation transactional: hTransaction passes a handle to the KTM transaction, the pExtendedParameter is currently reserved and has to be set to NULL.

The KTM in a few words

On to the KTM stuff right now. As mentioned earlier, KTM stands for Kernel Transaction Manager. Don't get confused by the word kernel, it only refers to the fact the KTM's transaction engine is in the kernel. This doesn't imply the transaction can only run in kernel mode (i.e. KTM-transactions can be used in kernel and user mode) nor does it mean the transaction would be machine-local (i.e. the transaction is DTC-able). On Vista and Longhorn Server, the KTM is in charge of TxF (Transactional NTFS) and TxR (Transactional Registry).

Important remark: Today we walk the low-level path, interacting directly with the KTM. This is not the ideal way but just a mind-setter for follow-up posts. Later on, we'll bring System.Transactions on stage to interact with the KTM transaction.

Warning: Do read the Windows SDK documentation carefully before working with TxR operations, especially the RegOpenKeyTransacted function information concerning the pick-up of the active transaction by other functions subsequently.

A demo

Let's start by showing you the code right away:

1 using System; 2 using System.Runtime.InteropServices; 3 using System.IO; 4 using Microsoft.Win32; 5 6 namespace TxR 7 { 8 class Program 9 { 10 [DllImport("advapi32.dll", EntryPoint="RegDeleteKeyTransactedW")] 11 static extern long RegDeleteKeyTransactedW(uint hkey, [MarshalAs(UnmanagedType.LPWStr)]string subkey, RegSam sam, uint reserved, IntPtr transaction, IntPtr reserved2); 12 13 static uint HKEY_CURRENT_USER = 0x80000001; 14 15 [DllImport("Kernel32.dll")] 16 static extern bool CloseHandle(IntPtr handle); 17 18 [DllImport("Ktmw32.dll")] 19 static extern bool CommitTransaction(IntPtr transaction); 20 21 [DllImport("Ktmw32.dll")] 22 static extern bool RollbackTransaction(IntPtr transaction); 23 24 [DllImport("Ktmw32.dll")] 25 static extern IntPtr CreateTransaction(IntPtr securityAttributes, IntPtr guid, int options, int isolationLevel, int isolationFlags, int milliSeconds, string description); 26 27 static void Main(string[] args) 28 { 29 // 30 // Demo setup. 31 // 32 string key1 = "RegDeleteKeyTransactedDemo_01"; 33 string key2 = "RegDeleteKeyTransactedDemo_02"; 34 Registry.CurrentUser.CreateSubKey(key1); 35 Registry.CurrentUser.CreateSubKey(key2); 36 37 // 38 // Start the demo. 39 // 40 Console.WriteLine("Press <ENTER> to start the transaction."); 41 Console.ReadLine(); 42 43 // 44 // Create a kernel transaction. 45 // 46 IntPtr tx = CreateTransaction(IntPtr.Zero, IntPtr.Zero, 0, 0, 0, 0, null); 47 48 // 49 // Delete the keys (transacted). 50 // 51 bool rollback = false; 52 if (RegDeleteKeyTransacted(HKEY_CURRENT_USER, key1, RegSam.KEY_WOW64_32KEY, 0, tx, IntPtr.Zero) != 0) 53 rollback = true; 54 if (RegDeleteKeyTransacted(HKEY_CURRENT_USER, key2, RegSam.KEY_WOW64_32KEY, 0, tx, IntPtr.Zero) != 0) 55 rollback = true; 56 57 // 58 // Commit or rollback? 59 // 60 if (!rollback) 61 { 62 char c; 63 do 64 { 65 Console.WriteLine("{0} {1}.", key1, Registry.CurrentUser.OpenSubKey(key1) != null ? "still exists" : "has vanished"); 66 Console.WriteLine("{0} {1}.", key2, Registry.CurrentUser.OpenSubKey(key2) != null ? "still exists" : "has vanished"); 67 Console.Write("Commit transaction (Y/N)? "); 68 c = (char)Console.Read(); 69 } 70 while (c != 'Y' && c != 'y' && c != 'N' && c != 'n'); 71 72 if (c == 'Y' || c == 'y') 73 CommitTransaction(tx); 74 else 75 RollbackTransaction(tx); 76 } 77 else 78 { 79 Console.WriteLine("Forced rollback!"); 80 RollbackTransaction(tx); 81 } 82 83 // 84 // Close kernel mode transaction handle. 85 // 86 CloseHandle(tx); 87 } 88 89 enum RegSam : uint 90 { 91 KEY_WOW64_32KEY = 0x0200, 92 KEY_WOW64_64KEY = 0x0100 93 } 94 } 95 }

So, what's going on in here? Some explanation:

  • Lines 19, 22 and 25 contain the KTM functions to create a transaction (CreateTransaction), commit a transaction (CommitTransaction) and rollback a transaction (RollbackTransaction). More information can be found in the Windows SDK.
  • On line 11, the RegDeleteKeyTransacted function is declared with the parameters aforementioned in this post. The handle retrieved from CreateTransaction has to be past as the 5th parameter. Notice the EntryPoint property of the DllImport attribute to choose the Unicode version of the function, as well as the LPWStr marshal type for the second parameter. (All of this is done for clarity and explicitness to see the relation between the Windows API and the interop code.)
  • To use the HKCU hive of the registry, we're declaring the symbolic constant HKEY_CURRENT_USER on line 13. This will be the first parameter to our RegDeleteKeyTransacted call subsequently.
  • Next, lines 32 to 35 create two registry keys under HKCU for further demonstration purposes. These are using the non-transactional APIs which have been wrapped in the Microsoft.Win32 managed code namespace's RegistryKey class.
  • One line 46, the real work starts by creating a KTM transaction using a call to CreateTransaction. We keep things simple by passing a lot of default values. For more information, take a look at the SDK.
  • Lines 52 and 54 perform the RegDeleteKeyTransacted calls. If those return an error code (non-zero), we set the rollback boolean value to true, to indicate that not all operations succeeded and the transaction has to be rolled back (if that's the behavior you want if any of the operations fails, you might want to do more error code analysis to make this decision too of course).
    • If the transaction has to be rolled back, line 80 does that work by calling RollbackTransaction.
    • Otherwise, we leave the decision to the user to either commit (CommitTransaction) or rollback.
  • Finally, let's clean up things an close the handle used for the KTM transaction on line 86 by calling CloseHandle.

In action

Let's show a few pictures on a "breakpoint" basis. If the code reaches the specified line, the displayed screenshot (click to enlarge) will reflect the actual situation in the registry:

  • Line 28 - the demo keys haven't been created yet:


  • Line 40 - the demo keys are there:


  • Line 67 - the transactional delete operations have been executed but because of the ACID Isolation property, nothing is visible yet for the outside world:

       
  • Line 73 - the user decided to commit the transaction, now the keys are gone:

What's next?

In part 2 we'll take a look at how we can use the DTC and the System.Transactions namespace to enroll the KTM transaction in a distributed transaction and how to encapsulate the low level plumbing in a little library. Again, we'll focus on the RegDeleteKeyTransacted stuff because of its simplicity. In a later post, we might take a look at other registry operations too, which - however - need a bit more work regarding various parameters and would take the focus off the transactional stuff we're covering. The ultimate solution would be a change to Microsoft.Win32 to incorporate transactional support in Windows Vista and higher.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# Windows Vista - Introducing TxR in C# (Part 2)

Saturday, December 16, 2006 3:05 PM by B# .NET Blog

Due to the availability of Windows Vista RTM at the time of this post's publication, the content will