[Libguestfs] [PATCH 2/2] v2v:windows: prevent conflicts with PnP on firstboot

Roman Kagan rkagan at virtuozzo.com
Wed Aug 17 17:59:44 UTC 2016


When put on new virtual hardware Windows will start driver installation
for newly discovered devices.

The problem is that it happens asynchronously and concurrently with
other activities, in particular, the firstboot scripts.  This may result
in conflicts (because a firstboot script may want to install or
uninstall a driver, etc.) and eventually in the system left in
inconsistent state.

In order to prevent it, add another firstboot script which calls a
special utility, pnp_wait, whose sole purpose is to wait until all
PnP-related activities are finished.  (It does so via a WinAPI call
which isn't available from a script so it has to be a compiled .exe.
The source code for it can be found in the corresponding directory in
https://github.com/rwmjones/rhsrvany).  This firstboot script is put
first, so that other firstboot scripts do not have to worry about
interactions with PnP manager any more.

One caveat is that on Windows XP and Windows 2003 the PnP manager always
fires the "Found New Hardware" wizard, which doesn't allow PnP to make
any progress until the user closes it.  As a result, this firstboot
script would never finish.

To work it around, follow the Microsoft KB
(https://support.microsoft.com/en-us/kb/938596) and set up a registry
key to make the the PnP manager not fire the wizard; the key is restored
to its initial state upon PnP completion.  Unfortunately this only works
on systems having the mentioned update installed; otherwise the user
will have to interact with the UI.

Signed-off-by: Roman Kagan <rkagan at virtuozzo.com>
---
 v2v/convert_windows.ml | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 92 insertions(+)

diff --git a/v2v/convert_windows.ml b/v2v/convert_windows.ml
index 751a6ab..10a6d31 100644
--- a/v2v/convert_windows.ml
+++ b/v2v/convert_windows.ml
@@ -43,6 +43,18 @@ let convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
     try Sys.getenv "VIRT_TOOLS_DATA_DIR"
     with Not_found -> Guestfs_config.datadir // "virt-tools" in
 
+  let pnp_wait_exe = virt_tools_data_dir // "pnp_wait.exe" in
+  let pnp_wait_exe =
+    try
+      let chan = open_in pnp_wait_exe in
+      close_in chan;
+      Some pnp_wait_exe
+    with
+      Sys_error msg ->
+        warning (f_"'%s' is missing.  Firstboot scripts may conflict with PnP.  Original error: %s")
+          pnp_wait_exe msg;
+        None in
+
   (* Check if either RHEV-APT or VMDP exists.  This is optional. *)
   let tools = [`RhevApt, "rhev-apt.exe"; `VmdpExe, "vmdp.exe"] in
   let installer =
@@ -218,6 +230,7 @@ let convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
     sprintf "ControlSet%03Ld" value in
 
   let rec configure_firstboot () =
+    wait_pnp ();
     (match installer with
      | None -> ()
      | Some (`RhevApt, tool_path) -> configure_rhev_apt tool_path
@@ -226,6 +239,85 @@ let convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
     unconfigure_xenpv ();
     unconfigure_prltools ()
 
+  and set_reg_val_dword_1 root key_path name =
+    (* set reg value to REG_DWORD 1, creating intermediate keys if needed *)
+    let node =
+      let rec loop parent = function
+        | [] -> parent
+        | x :: xs ->
+          let node =
+            match g#hivex_node_get_child parent x with
+            | 0L -> g#hivex_node_add_child parent x (* not found, create *)
+            | node -> node in
+          loop node xs
+      in
+      loop root key_path in
+    let valueh = g#hivex_node_get_value node name in
+    let value =
+      match valueh with
+      | 0L -> None
+      | _ -> Some (int_of_le32 (g#hivex_value_value valueh)) in
+    g#hivex_node_set_value node name 4_L (le32_of_int 1_L);
+    value
+
+  and reg_restore key name value =
+    let strkey = String.concat "\\" key in
+    match value with
+    | Some value -> sprintf "\
+reg add \"%s\" /v %s /t REG_DWORD /d %Ld /f" strkey name value
+    | None -> sprintf "\
+reg delete \"%s\" /v %s /f" strkey name
+
+  and wait_pnp () =
+    (* prevent destructive interactions of firstboot with PnP *)
+    match pnp_wait_exe with
+    | None -> ()
+    | Some pnp_wait_exe ->
+      let pnp_wait_path = [""; "Program Files"; "Guestfs"; "Firstboot";
+                           "pnp_wait.exe"] in
+      (* suppress "New Hardware Wizard" until PnP settles (see
+       * https://support.microsoft.com/en-us/kb/938596) and restore it
+       * afterwards *)
+      let reg_restore_str =
+        match inspect.i_major_version, inspect.i_minor_version with
+        (* WinXP 32bit *)
+        | 5, 1 ->
+          let key_path = ["Policies"; "Microsoft"; "Windows"; "DeviceInstall";
+                          "Settings"] in
+          let name = "SuppressNewHWUI" in
+          let value = Windows.with_hive_write g software_hive_filename (
+            fun root ->
+              set_reg_val_dword_1 root key_path name
+          ) in
+          reg_restore ("HKLM\\Software" :: key_path) name value
+
+        (* WinXP 64bit / Win2k3 *)
+        | 5, 2 ->
+          let key_path = ["Services"; "PlugPlay"; "Parameters"] in
+          let name = "SuppressUI" in
+          let value = Windows.with_hive_write g system_hive_filename (
+            fun root ->
+              let current_cs = get_current_cs root in
+              set_reg_val_dword_1 root (current_cs :: key_path) name
+          ) in
+          reg_restore ("HKLM\\SYSTEM\\CurrentControlSet" :: key_path) name
+                      value
+
+        (* any later Windows *)
+        | _ -> "" in
+
+      let fb_script = sprintf "\
+ at echo off
+
+echo Wait for PnP to complete
+\"%s\" >\"%%~dpn0.log\" 2>&1
+%s" (String.concat "\\" pnp_wait_path) reg_restore_str in
+
+      Firstboot.add_firstboot_script g inspect.i_root "wait pnp" fb_script;
+      (* add_firstboot_script has created the path already *)
+      g#upload pnp_wait_exe (g#case_sensitive_path
+                              (String.concat "/" pnp_wait_path))
+
   and configure_rhev_apt tool_path =
     (* Configure RHEV-APT (the RHEV guest agent).  However if it doesn't
      * exist just warn about it and continue.
-- 
2.7.4




More information about the Libguestfs mailing list