#!/usr/bin/perl -w
# virt-win-reg
-# Copyright (C) 2009 Red Hat Inc.
+# Copyright (C) 2010 Red Hat Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
use strict;
use Sys::Guestfs;
-use Sys::Guestfs::Lib qw(open_guest get_partitions resolve_windows_path
- inspect_all_partitions inspect_partition
- inspect_operating_systems mount_operating_system);
+use Sys::Guestfs::Lib qw(open_guest);
+use Win::Hivex;
+use Win::Hivex::Regedit qw(reg_import reg_export);
+
use Pod::Usage;
use Getopt::Long;
use File::Temp qw/tempdir/;
+use File::Basename;
use Locale::TextDomain 'libguestfs';
=encoding utf8
=head1 NAME
-virt-win-reg - Display Windows Registry entries from a Windows guest
+virt-win-reg - Export and merge Windows Registry entries from a Windows guest
=head1 SYNOPSIS
- virt-win-reg [--options] domname '\Path\To\Subkey' name ['\Path'...]
+ virt-win-reg domname 'HKLM\Path\To\Subkey'
- virt-win-reg [--options] domname '\Path\To\Subkey' @ ['\Path'...]
+ virt-win-reg domname 'HKLM\Path\To\Subkey' name
- virt-win-reg [--options] domname '\Path\To\Subkey' ['\Path'...]
+ virt-win-reg domname 'HKLM\Path\To\Subkey' @
- virt-win-reg [--options] disk.img [...] '\Path\To\Subkey' (name|@)
+ virt-win-reg --merge domname [input.reg ...]
-=head1 DESCRIPTION
+ virt-win-reg [--options] disk.img ... # instead of domname
-This program can display Windows Registry entries from a Windows
-guest.
+=head1 WARNING
-The first parameter is the libvirt guest name or the raw disk image of
-the Windows guest.
+You must I<not> use C<virt-win-reg> with the I<--merge> option on live
+virtual machines. If you do this, you I<will> get irreversible disk
+corruption in the VM. C<virt-win-reg> tries to stop you from doing
+this, but doesn't catch all cases.
-Then follow one or more sets of path specifiers. The path must begin
-with a C<\> (backslash) character, and may be something like
-C<'\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion'>.
+Modifying the Windows Registry is an inherently risky operation. The format
+is deliberately obscure and undocumented, and Registry changes
+can leave the system unbootable. Therefore when using the I<--merge>
+option, make sure you have a reliable backup first.
-The next parameter after that is either a value name, the single
-at-character C<@>, or missing.
+=head1 DESCRIPTION
-If it's a value name, then we print the data associated with that
-value. If it's C<@>, then we print the default data associated with
-the subkey. If it's missing, then we print all the data associated
-with the subkey.
+This program can export and merge Windows Registry entries from a
+Windows guest.
-If this is confusing, look at the L</EXAMPLES> section below.
+The first parameter is the libvirt guest name or the raw disk image of
+a Windows guest.
-Usually you should use single quotes to protect backslashes in the
-path from the shell.
+If I<--merge> is I<not> specified, then the chosen registry
+key is displayed/exported (recursively). For example:
-Paths and value names are case-insensitive.
+ $ virt-win-reg Windows7 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft'
-=head2 SUPPORTED SYSTEMS
+You can also display single values from within registry keys,
+for example:
-The program currently supports Windows NT-derived guests starting with
-Windows XP through to at least Windows 7.
+ $ cvkey='HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
+ $ virt-win-reg Windows7 $cvkey ProductName
+ Windows 7 Enterprise
-Registry support is done for C<\HKEY_LOCAL_MACHINE\SAM>,
-C<\HKEY_LOCAL_MACHINE\SECURITY>, C<\HKEY_LOCAL_MACHINE\SOFTWARE>,
-C<\HKEY_LOCAL_MACHINE\SYSTEM> and C<\HKEY_USERS\.DEFAULT>.
+With I<--merge>, you can merge a textual regedit file into
+the Windows Registry:
-C<\HKEY_USERS\$SID> and C<\HKEY_CURRENT_USER> are B<not> supported at
-this time.
+ $ virt-win-reg --merge Windows7 changes.reg
-=head2 NOTES
+=head2 NOTE
This program is only meant for simple access to the registry. If you
want to do complicated things with the registry, we suggest you
-download the Registry hive files from the guest using C<libguestfs(3)>
-or C<guestfish(1)> and access them locally, eg. using C<hivex(3)>,
-C<hivexml(1)> or C<reged(1)>.
-
-=head1 EXAMPLES
-
- $ virt-win-reg MyWinGuest \
- '\HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion' \
- ProductName
- Microsoft Windows Server 2003
-
- $ virt-win-reg MyWinGuest \
- '\HKEY_LOCAL_MACHINE\System\ControlSet001\Control' SystemBootDevice
- multi(0)disk(0)rdisk(0)partition(1)
-
- $ virt-win-reg MyWinGuest \
- '\HKEY_LOCAL_MACHINE\System\ControlSet001\Control'
- "CurrentUser"="USERNAME"
- "WaitToKillServiceTimeout"="20000"
- "SystemStartOptions"="NOEXECUTE=OPTOUT FASTDETECT"
- "SystemBootDevice"="multi(0)disk(0)rdisk(0)partition(1)"
-
-(please suggest some more)
+download the Registry hive files from the guest using L<libguestfs(3)>
+or L<guestfish(1)> and access them locally, eg. using L<hivex(3)>,
+L<hivexsh(1)> or L<hivexregedit(1)>.
=head1 OPTIONS
=cut
+my $debug;
+
+=item B<--debug>
+
+Enable debugging messages.
+
+=cut
+
my $uri;
-=item B<--connect URI> | B<-c URI>
+=item B<-c URI>
+
+=item B<--connect URI>
If using libvirt, connect to the given I<URI>. If omitted, then we
connect to the default libvirt hypervisor.
If you specify guest block devices directly, then libvirt is not used
at all.
+=cut
+
+my $format;
+
+=item B<--format> raw
+
+Specify the format of disk images given on the command line. If this
+is omitted then the format is autodetected from the content of the
+disk image.
+
+If disk images are requested from libvirt, then this program asks
+libvirt for this information. In this case, the value of the format
+parameter is ignored.
+
+If working with untrusted raw-format guest disk images, you should
+ensure the format is always specified.
+
+=cut
+
+my $merge;
+
+=item B<--merge>
+
+In merge mode, this merges a textual regedit file into the Windows
+Registry of the virtual machine. If this flag is I<not> given then
+virt-win-reg displays or exports Registry entries instead.
+
+Note that I<--merge> is I<unsafe> to use on live virtual machines, and
+will result in disk corruption. However exporting (without this flag)
+is always safe.
+
+=cut
+
+my $encoding;
+
+=item B<--encoding> UTF-16LE|ASCII
+
+When merging (only), you may need to specify the encoding for strings
+to be used in the hive file. This is explained in detail in
+L<Win::Hivex::Regedit(3)/ENCODING STRINGS>.
+
+The default is to use UTF-16LE, which should work with recent versions
+of Windows.
+
+=cut
+
+my $unsafe_printable_strings;
+
+=item B<--unsafe-printable-strings>
+
+When exporting (only), assume strings are UTF-16LE and print them as
+strings instead of hex sequences. Remove the final zero codepoint
+from strings if present.
+
+This is unsafe and does not preserve the fidelity of strings in the
+original Registry for various reasons:
+
+=over 4
+
+=item *
+
+Assumes the original encoding is UTF-16LE. ASCII strings and strings
+in other encodings will be corrupted by this transformation.
+
+=item *
+
+Assumes that everything which has type 1 or 2 is really a string
+and that everything else is not a string, but the type field in
+real Registries is not reliable.
+
+=item *
+
+Loses information about whether a zero codepoint followed the string
+in the Registry or not.
+
+=back
+
+This all happens because the Registry itself contains no information
+about how strings are encoded (see
+L<Win::Hivex::Regedit(3)/ENCODING STRINGS>).
+
+You should only use this option for quick hacking and debugging of the
+Registry contents, and I<never> use it if the output is going to be
+passed into another program or stored in another Registry.
+
=back
=cut
GetOptions ("help|?" => \$help,
"version" => \$version,
"connect|c=s" => \$uri,
+ "debug|d" => \$debug,
+ "format=s" => \$format,
+ "merge" => \$merge,
+ "encoding=s" => \$encoding,
+ "unsafe-printable-strings" => \$unsafe_printable_strings,
) or pod2usage (2);
pod2usage (1) if $help;
if ($version) {
exit
}
-# Split the command line at the first path. Paths begin with
-# backslash so this is predictable.
+# virt-win-reg only takes a single disk image ...
+die __"no libvirt domain name or disk image given\n" if @ARGV == 0;
+my $domname_or_image = shift @ARGV;
-my @lib_args;
-my $i;
+warn "launching libguestfs ..." if $debug;
-for ($i = 0; $i < @ARGV; ++$i) {
- if (substr ($ARGV[$i], 0, 1) eq "\\") {
- @lib_args = @ARGV[0 .. ($i-1)];
- @ARGV = @ARGV[$i .. $#ARGV];
- last;
- }
+my @lib_args = ([$domname_or_image]);
+push @lib_args, address => $uri if $uri;
+push @lib_args, rw => 1 if $merge;
+push @lib_args, format => $format if defined $format;
+my $g = open_guest (@lib_args);
+$g->launch ();
+
+warn "inspecting guest ..." if $debug;
+
+my @roots = $g->inspect_os ();
+if (@roots == 0) {
+ die __x("{prog}: No operating system could be detected inside this disk image.\n\nThis may be because the file is not a disk image, or is not a virtual machine\nimage, or because the OS type is not understood by libguestfs.\n\nIf you feel this is an error, please file a bug report including as much\ninformation about the disk image as possible.\n",
+ prog => basename ($0));
+}
+if (@roots > 1) {
+ die __x("{prog}: multiboot operating systems are not supported.\n",
+ prog => basename ($0))
}
+my %fses = $g->inspect_get_mountpoints ($roots[0]);
+my @fses = sort { length $a <=> length $b } keys %fses;
+my $mountopts = $merge ? "" : "ro";
+foreach (@fses) {
+ $g->mount_options ($mountopts, $fses{$_}, $_);
+}
+
+my $systemroot = $g->inspect_get_windows_systemroot ($roots[0]);
+
+# Create a working directory to store the downloaded registry files.
+my $tmpdir = tempdir (CLEANUP => 1);
-pod2usage (__"virt-win-reg: no VM name, disk images or Registry path given") if 0 == @lib_args;
+# Used when merging (only) to map from the downloaded hiveshortname to
+# various properties about the hive. The key is hiveshortname. The
+# value is a hashref containing {h} (hive handle) and {hivefile} (full
+# hive path on the Windows side).
+my %hives;
-my $g;
-if ($uri) {
- $g = open_guest (\@lib_args, address => $uri);
-} else {
- $g = open_guest (\@lib_args);
+if (!$merge) { # Export mode.
+ die __"expecting 1 or 2 more parameters, subkey path and optionally the value to export\n"
+ if @ARGV < 1 || @ARGV > 2;
+
+ my $path = shift @ARGV;
+ my $name = shift @ARGV; # or undef
+
+ # Map this to the hive name. This function dies on failure.
+ my ($hiveshortname, $hivefile, $prefix);
+ ($hiveshortname, $hivefile, $path, $prefix) = map_path_to_hive ($path);
+
+ # Download the chosen hive.
+ download_hive ($hivefile, $hiveshortname);
+
+ # Open it.
+ my $h = Win::Hivex->open ("$tmpdir/$hiveshortname", debug => $debug);
+
+ unless ($name) {
+ # Export it.
+ warn "exporting $path from $hiveshortname with prefix $prefix ..."
+ if $debug;
+ reg_export ($h, $path, \*STDOUT,
+ prefix => $prefix,
+ unsafe_printable_strings => $unsafe_printable_strings);
+ } else {
+ # Export a single key using hivexget.
+ my @args = ("hivexget", "$tmpdir/$hiveshortname", $path, $name);
+ warn "running ", join (" ", @args), " ..." if $debug;
+ system (@args) == 0 or die "hivexget failed: $?"
+ }
}
+else { # Import mode.
+ if (@ARGV == 0) {
+ reg_import (\*STDIN, \&import_mapper, encoding => $encoding);
+ } else {
+ foreach (@ARGV) {
+ open my $fh, $_ or die "open: $_: $!";
+ reg_import ($fh, \&import_mapper, encoding => $encoding);
+ }
+ }
-$g->launch ();
+ # Now we've done importing, commit all the hive handles and
+ # close them all.
+ foreach (values %hives) {
+ my $h = $_->{h};
+ delete $_->{h};
+ $h->commit (undef);
+ }
-# List of possible filesystems.
-my @partitions = get_partitions ($g);
+ # Upload all the downloaded hives.
+ foreach my $hiveshortname (keys %hives) {
+ upload_hive ($hiveshortname, $hives{$hiveshortname}->{hivefile})
+ }
-# Now query each one to build up a picture of what's in it.
-my %fses =
- inspect_all_partitions ($g, \@partitions,
- use_windows_registry => 0);
+ # Sync everything.
+ $g->umount_all ();
+ $g->sync ();
+}
-my $oses = inspect_operating_systems ($g, \%fses);
+exit 0;
-my @roots = keys %$oses;
-die __"no root device found in this operating system image" if @roots == 0;
-die __"multiboot operating systems are not supported by virt-win-reg" if @roots > 1;
-my $root_dev = $roots[0];
+# map function passed to reg_import.
+sub import_mapper
+{
+ local $_ = shift;
-my $os = $oses->{$root_dev};
-mount_operating_system ($g, $os);
+ my ($hiveshortname, $hivefile, $path, $prefix) = map_path_to_hive ($_);
-# Create a working directory to store the downloaded registry files.
-my $tmpdir = tempdir (CLEANUP => 1);
+ # Need to download this hive?
+ unless (-f "$tmpdir/$hiveshortname") {
+ download_hive ($hivefile, $hiveshortname);
-# Now process each request in turn.
-my $winfile;
-my $localhive;
-my $path;
+ my $h = Win::Hivex->open ("$tmpdir/$hiveshortname",
+ write => 1, debug => $debug);
+ my %hash = ( h => $h, hivefile => $hivefile );
+ $hives{$hiveshortname} = \%hash;
+ }
-for ($i = 0; $i < @ARGV; ++$i) {
- $_ = $ARGV[$i];
+ return ($hives{$hiveshortname}->{h}, $path);
+}
- if (/^\\HKEY_LOCAL_MACHINE\\SAM(\\.*)/i) {
- $winfile = "/windows/system32/config/sam";
- $localhive = "$tmpdir/sam";
- $path = $1;
+# Given a path, map that to the name of the hive and the true path
+# within that hive.
+sub map_path_to_hive
+{
+ local $_ = shift;
+ my ($hiveshortname, $hivefile, $path, $prefix);
+
+ if (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SAM(\\.*)?$/i) {
+ $hiveshortname = "sam";
+ $hivefile = "$systemroot/system32/config/$hiveshortname";
+ $path = defined $1 ? $1 : "\\";
+ $prefix = "HKEY_LOCAL_MACHINE\\SAM";
+ }
+ elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SECURITY(\\.*)?$/i) {
+ $hiveshortname = "security";
+ $hivefile = "$systemroot/system32/config/$hiveshortname";
+ $path = defined $1 ? $1 : "\\";
+ $prefix = "HKEY_LOCAL_MACHINE\\SECURITY";
+ }
+ elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SOFTWARE(\\.*)?$/i) {
+ $hiveshortname = "software";
+ $hivefile = "$systemroot/system32/config/$hiveshortname";
+ $path = defined $1 ? $1 : "\\";
+ $prefix = "HKEY_LOCAL_MACHINE\\SOFTWARE";
+ }
+ elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SYSTEM(\\.*)?$/i) {
+ $hiveshortname = "system";
+ $hivefile = "$systemroot/system32/config/$hiveshortname";
+ $path = defined $1 ? $1 : "\\";
+ $prefix = "HKEY_LOCAL_MACHINE\\SYSTEM";
+ }
+ elsif (/^\\?(?:HKEY_USERS|HKU)\\.DEFAULT(\\.*)?$/i) {
+ $hiveshortname = "default";
+ $hivefile = "$systemroot/system32/config/$hiveshortname";
+ $path = defined $1 ? $1 : "\\";
+ $prefix = "HKEY_LOCAL_MACHINE\\.DEFAULT";
}
- elsif (/^\\HKEY_LOCAL_MACHINE\\SECURITY(\\.*)/i) {
- $winfile = "/windows/system32/config/security";
- $localhive = "$tmpdir/security";
- $path = $1;
+ elsif (/^\\?(?:HKEY_USERS|HKU)\\(S-1-5-[-\d]+)(\\.*)?$/i) {
+ my $sid = $1;
+ $hiveshortname = $sid;
+ $prefix = "HKEY_USERS\\$sid";
+ $path = defined $2 ? $2 : "\\";
+ # This requires a recursive call to download the SOFTWARE hive.
+ $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat";
}
- elsif (/^\\HKEY_LOCAL_MACHINE\\SOFTWARE(\\.*)/i) {
- $winfile = "/windows/system32/config/software";
- $localhive = "$tmpdir/software";
- $path = $1;
+ elsif (/^\\?(?:HKEY_USERS|HKU)\\LocalSystem(\\.*)?$/i) {
+ my $sid = "S-1-5-18";
+ $hiveshortname = $sid;
+ $prefix = "HKEY_USERS\\$sid";
+ $path = defined $1 ? $1 : "\\";
+ # This requires a recursive call to download the SOFTWARE hive.
+ $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat";
}
- elsif (/^\\HKEY_LOCAL_MACHINE\\SYSTEM(\\.*)/i) {
- $winfile = "/windows/system32/config/system";
- $localhive = "$tmpdir/system";
- $path = $1;
+ elsif (/^\\?(?:HKEY_USERS|HKU)\\LocalService(\\.*)?$/i) {
+ my $sid = "S-1-5-19";
+ $hiveshortname = $sid;
+ $prefix = "HKEY_USERS\\$sid";
+ $path = defined $1 ? $1 : "\\";
+ # This requires a recursive call to download the SOFTWARE hive.
+ $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat";
}
- elsif (/^\\HKEY_USERS\\.DEFAULT(\\.*)/i) {
- $winfile = "/windows/system32/config/default";
- $localhive = "$tmpdir/default";
- $path = $1;
+ elsif (/^\\?(?:HKEY_USERS|HKU)\\NetworkService(\\.*)?$/i) {
+ my $sid = "S-1-5-20";
+ $hiveshortname = $sid;
+ $prefix = "HKEY_USERS\\$sid";
+ $path = defined $1 ? $1 : "\\";
+ # This requires a recursive call to download the SOFTWARE hive.
+ $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat";
+ }
+ elsif (/^\\?(?:HKEY_USERS|HKU)\\(.*?)(\\.*)?$/i) {
+ $hiveshortname = "user_$1";
+ $prefix = "HKEY_USERS\\$1";
+ $path = defined $2 ? $2 : "\\";
+ # XXX We should probably look this up properly.
+ if (is_dir_nocase ("/Users/$1")) {
+ $hivefile = "/Users/$1/ntuser.dat"
+ } elsif (is_dir_nocase ("/Documents and Settings/$1")) {
+ $hivefile = "/Documents and Settings/$1/ntuser.dat"
+ } else {
+ die __x("virt-win-reg: {p}: cannot find user directory\n",
+ p => $1)
+ }
}
else {
- die "virt-win-reg: $_: not a supported Windows Registry path\n"
+ die __x("virt-win-reg: {p}: not a supported Windows Registry path\n",
+ p => $_)
}
- unless (-f $localhive) {
- # Check the hive file exists and get the real name.
- eval {
- $winfile = $g->case_sensitive_path ($winfile);
- $g->download ($winfile, $localhive);
- };
- if ($@) {
- die "virt-win-reg: $winfile: could not download registry file: $@\n"
- }
+ return ($hiveshortname, $hivefile, $path, $prefix);
+}
+
+# Given a User SID, consult
+# HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid
+# and return the ProfileImagePath value.
+sub lookup_pip_of_user_sid
+{
+ local $_;
+ my $sid = shift;
+
+ my $path =
+ "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\".
+ $sid;
+
+ my ($hiveshortname, $hivefile, $prefix);
+ ($hiveshortname, $hivefile, $path, $prefix) = map_path_to_hive ($path);
+
+ download_hive ($hivefile, $hiveshortname)
+ unless -f "$tmpdir/$hiveshortname";
+
+ my @args = ("$tmpdir/$hiveshortname", $path, "ProfileImagePath");
+ warn "running hivexget ", join (" ", @args), " ..." if $debug;
+
+ my $fh;
+ open $fh, "-|", "hivexget", @args
+ or die "hivexget: see earlier errors: $!";
+ $_ = <$fh>;
+ close $fh or die "hivexget: see earlier errors: $!";
+
+ chomp;
+
+ # The contents of the registry are a windows path, possibly
+ # containing %systemroot% and %systemdrive% (on Win XP). Expand
+ # it and remove some other windows-isms. The caller will do
+ # case_sensitive_path for us, so we don't need to do that.
+ s/%systemroot%/$systemroot/i;
+ s/%systemdrive%//i;
+ s/^c://i;
+ s,\\,/,g;
+
+ $_;
+}
+
+sub is_dir_nocase
+{
+ local $_;
+ my $dir = shift;
+
+ my $windir;
+ eval { $windir = $g->case_sensitive_path ($dir); };
+ if ($@) {
+ return 0;
}
+ return $g->is_dir ($windir);
+}
- # What sort of request is it? Peek at the next arg.
- my $name; # will be: undefined, @ or a name
- if ($i+1 < @ARGV) {
- if (substr ($ARGV[$i+1], 0, 1) ne "\\") {
- $name = $ARGV[$i+1];
- $i++;
- }
+# Download a named hive file. Die on failure.
+sub download_hive
+{
+ local $_;
+ my $hivefile = shift;
+ my $hiveshortname = shift;
+
+ my $winfile;
+ eval { $winfile = $g->case_sensitive_path ($hivefile); };
+ if ($@) {
+ die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
+ p => $hivefile, err => $@);
}
- my @cmd;
- if (defined $name) {
- @cmd = ("hivexget", $localhive, $path, $name);
- } else {
- @cmd = ("hivexget", $localhive, $path);
+ warn "downloading $winfile ..." if $debug;
+ eval { $g->download ($winfile, "$tmpdir/$hiveshortname"); };
+ if ($@) {
+ die __x("virt-win-reg: {p}: could not download registry file: {err}\n",
+ p => $winfile, err => $@);
+ }
+}
+
+# Upload a named hive file. Die on failure.
+sub upload_hive
+{
+ local $_;
+ my $hiveshortname = shift;
+ my $hivefile = shift;
+
+ my $winfile;
+ eval { $winfile = $g->case_sensitive_path ($hivefile); };
+ if ($@) {
+ die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
+ p => $hivefile, err => $@);
}
- system (@cmd) == 0
- or die "hivexget command failed: $?\n";
+ warn "uploading $winfile ..." if $debug;
+ eval { $g->upload ("$tmpdir/$hiveshortname", $winfile); };
+ if ($@) {
+ die __x("virt-win-reg: {p}: could not upload registry file: {err}\n",
+ p => $winfile, err => $@);
+ }
}
+=head1 SUPPORTED SYSTEMS
+
+The program currently supports Windows NT-derived guests starting with
+Windows XP through to at least Windows 7.
+
+The following Registry keys are supported:
+
+=over 4
+
+=item C<HKEY_LOCAL_MACHINE\SAM>
+
+=item C<HKEY_LOCAL_MACHINE\SECURITY>
+
+=item C<HKEY_LOCAL_MACHINE\SOFTWARE>
+
+=item C<HKEY_LOCAL_MACHINE\SYSTEM>
+
+=item C<HKEY_USERS\.DEFAULT>
+
+=item C<HKEY_USERS\I<SID>>
+
+where I<SID> is a Windows User SID (eg. C<S-1-5-18>).
+
+=item C<HKEY_USERS\I<username>>
+
+where I<username> is a local user name (this is a libguestfs extension).
+
+=back
+
+You can use C<HKLM> as a shorthand for C<HKEY_LOCAL_MACHINE>, and
+C<HKU> for C<HKEY_USERS>.
+
+The literal keys C<HKEY_USERS\$SID> and C<HKEY_CURRENT_USER> are not
+supported (there is no "current user").
+
+=head1 ENCODING
+
+C<virt-win-reg> expects that regedit files have already been reencoded
+in the local encoding. Usually on Linux hosts, this means UTF-8 with
+Unix-style line endings. Since Windows regedit files are often in
+UTF-16LE with Windows-style line endings, you may need to reencode the
+whole file before or after processing.
+
+To reencode a file from Windows format to Linux (before processing it
+with the I<--merge> option), you would do something like this:
+
+ iconv -f utf-16le -t utf-8 < win.reg | dos2unix > linux.reg
+
+To go in the opposite direction, after exporting and before sending
+the file to a Windows user, do something like this:
+
+ unix2dos linux.reg | iconv -f utf-8 -t utf-16le > win.reg
+
+For more information about encoding, see L<Win::Hivex::Regedit(3)>.
+
+If you are unsure about the current encoding, use the L<file(1)>
+command. Recent versions of Windows regedit.exe produce a UTF-16LE
+file with Windows-style (CRLF) line endings, like this:
+
+ $ file software.reg
+ software.reg: Little-endian UTF-16 Unicode text, with very long lines,
+ with CRLF line terminators
+
+This file would need conversion before you could I<--merge> it.
+
+=head1 CurrentControlSet etc.
+
+Registry keys like C<CurrentControlSet> don't really exist in the
+Windows Registry at the level of the hive file, and therefore you
+cannot modify these.
+
+C<CurrentControlSet> is usually an alias for C<ControlSet001>. In
+some circumstances it might refer to another control set. The way
+to find out is to look at the C<HKLM\SYSTEM\Select> key:
+
+ # virt-win-reg WindowsGuest 'HKLM\SYSTEM\Select'
+ [HKEY_LOCAL_MACHINE\SYSTEM\Select]
+ "Current"=dword:00000001
+ "Default"=dword:00000001
+ "Failed"=dword:00000000
+ "LastKnownGood"=dword:00000002
+
+"Current" is the one which Windows will choose when it boots.
+
+Similarly, other C<Current...> keys in the path may need to
+be replaced.
+
+=head1 WINDOWS TIPS
+
+Note that some of these tips modify the guest disk image. The guest
+I<must> be shut off, else you will get disk corruption.
+
+=head2 RUNNING A BATCH SCRIPT WHEN A USER LOGS IN
+
+Prepare a DOS batch script, VBScript or executable. Upload this using
+L<guestfish(1)>. For this example the script is called C<test.bat>
+and it is uploaded into C<C:\>:
+
+ guestfish -i -d WindowsGuest upload test.bat /test.bat
+
+Prepare a regedit file containing the registry change:
+
+ cat > test.reg <<'EOF'
+ [HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce]
+ "Test"="c:\\test.bat"
+ EOF
+
+In this example we use the key C<RunOnce> which means that the script
+will run precisely once when the first user logs in. If you want it
+to run every time a user logs in, replace C<RunOnce> with C<Run>.
+
+Now update the registry:
+
+ virt-win-reg --merge WindowsGuest test.reg
+
+=head2 INSTALLING A SERVICE
+
+This section assumes you are familiar with Windows services, and you
+either have a program which handles the Windows Service Control
+Protocol directly or you want to run any program using a service
+wrapper like SrvAny or the free RHSrvAny.
+
+First upload the program and optionally the service wrapper. In this
+case the test program is called C<test.exe> and we are using the
+RHSrvAny wrapper:
+
+ guestfish -i -d WindowsGuest <<EOF
+ upload rhsrvany.exe /rhsrvany.exe
+ upload test.exe /test.exe
+ EOF
+
+Prepare a regedit file containing the registry changes. In this
+example, the first registry change is needed for the service itself or
+the service wrapper (if used). The second registry change is only
+needed because I am using the RHSrvAny service wrapper.
+
+ cat > service.reg <<'EOF'
+ [HKLM\SYSTEM\ControlSet001\services\RHSrvAny]
+ "Type"=dword:00000010
+ "Start"=dword:00000002
+ "ErrorControl"=dword:00000001
+ "ImagePath"="c:\\rhsrvany.exe"
+ "DisplayName"="RHSrvAny"
+ "ObjectName"="NetworkService"
+
+ [HKLM\SYSTEM\ControlSet001\services\RHSrvAny\Parameters]
+ "CommandLine"="c:\\test.exe"
+ "PWD"="c:\\Temp"
+ EOF
+
+Notes:
+
+=over 4
+
+=item *
+
+For use of C<ControlSet001> see the section above in this manual page.
+You may need to adjust this according to the control set that is in
+use by the guest.
+
+=item *
+
+C<"ObjectName"> controls the privileges that the service will have.
+An alternative is C<"ObjectName"="LocalSystem"> which would be the
+most privileged account.
+
+=item *
+
+For the meaning of the magic numbers, see this Microsoft KB article:
+L<http://support.microsoft.com/kb/103000>.
+
+=back
+
+Update the registry:
+
+ virt-win-reg --merge WindowsGuest service.reg
+
+=head1 SHELL QUOTING
+
+Be careful when passing parameters containing C<\> (backslash) in the
+shell. Usually you will have to use 'single quotes' or double
+backslashes (but not both) to protect them from the shell.
+
+Paths and value names are case-insensitive.
+
+Libvirt guest names can contain arbitrary characters, some of which
+have meaning to the shell such as C<#> and space. You may need to
+quote or escape these characters on the command line. See the shell
+manual page L<sh(1)> for details.
+
=head1 SEE ALSO
L<hivex(3)>,
-L<hivexget(1)>,
+L<hivexsh(1)>,
+L<hivexregedit(1)>,
L<guestfs(3)>,
L<guestfish(1)>,
L<virt-cat(1)>,
L<Sys::Guestfs(3)>,
L<Sys::Guestfs::Lib(3)>,
+L<Win::Hivex(3)>,
+L<Win::Hivex::Regedit(3)>,
L<Sys::Virt(3)>,
L<http://libguestfs.org/>.
+=head1 BUGS
+
+When reporting bugs, please enable debugging and capture the
+I<complete> output:
+
+ export LIBGUESTFS_DEBUG=1
+ virt-win-reg --debug [... rest ...] > /tmp/virt-win-reg.log 2>&1
+
+Attach /tmp/virt-win-reg.log to a new bug report at
+L<https://bugzilla.redhat.com/>
+
=head1 AUTHOR
-Richard W.M. Jones L<http://et.redhat.com/~rjones/>
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
=head1 COPYRIGHT
-Copyright (C) 2009 Red Hat Inc.
+Copyright (C) 2010 Red Hat Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by