3 # Copyright (C) 2010 Red Hat Inc.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 use Sys::Guestfs::Lib qw(open_guest);
25 use Win::Hivex::Regedit qw(reg_import reg_export);
29 use File::Temp qw/tempdir/;
31 use Locale::TextDomain 'libguestfs';
37 virt-win-reg - Export and merge Windows Registry entries from a Windows guest
41 virt-win-reg domname 'HKLM\Path\To\Subkey'
43 virt-win-reg domname 'HKLM\Path\To\Subkey' name
45 virt-win-reg domname 'HKLM\Path\To\Subkey' @
47 virt-win-reg --merge domname [input.reg ...]
49 virt-win-reg [--options] disk.img ... # instead of domname
53 You must I<not> use C<virt-win-reg> with the I<--merge> option on live
54 virtual machines. If you do this, you I<will> get irreversible disk
55 corruption in the VM. C<virt-win-reg> tries to stop you from doing
56 this, but doesn't catch all cases.
58 Modifying the Windows Registry is an inherently risky operation. The format
59 is deliberately obscure and undocumented, and Registry changes
60 can leave the system unbootable. Therefore when using the I<--merge>
61 option, make sure you have a reliable backup first.
65 This program can export and merge Windows Registry entries from a
68 The first parameter is the libvirt guest name or the raw disk image of
71 If I<--merge> is I<not> specified, then the chosen registry
72 key is displayed/exported (recursively). For example:
74 $ virt-win-reg Windows7 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft'
76 You can also display single values from within registry keys,
79 $ cvkey='HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
80 $ virt-win-reg Windows7 $cvkey ProductName
83 With I<--merge>, you can merge a textual regedit file into
86 $ virt-win-reg --merge Windows7 changes.reg
90 This program is only meant for simple access to the registry. If you
91 want to do complicated things with the registry, we suggest you
92 download the Registry hive files from the guest using L<libguestfs(3)>
93 or L<guestfish(1)> and access them locally, eg. using L<hivex(3)>,
94 L<hivexsh(1)> or L<hivexregedit(1)>.
114 Display version number and exit.
122 Enable debugging messages.
128 =item B<--connect URI> | B<-c URI>
130 If using libvirt, connect to the given I<URI>. If omitted, then we
131 connect to the default libvirt hypervisor.
133 If you specify guest block devices directly, then libvirt is not used
140 =item B<--format> raw
142 Specify the format of disk images given on the command line. If this
143 is omitted then the format is autodetected from the content of the
146 If disk images are requested from libvirt, then this program asks
147 libvirt for this information. In this case, the value of the format
148 parameter is ignored.
150 If working with untrusted raw-format guest disk images, you should
151 ensure the format is always specified.
159 In merge mode, this merges a textual regedit file into the Windows
160 Registry of the virtual machine. If this flag is I<not> given then
161 virt-win-reg displays or exports Registry entries instead.
163 Note that I<--merge> is I<unsafe> to use on live virtual machines, and
164 will result in disk corruption. However exporting (without this flag)
171 =item B<--encoding> UTF-16LE|ASCII
173 When merging (only), you may need to specify the encoding for strings
174 to be used in the hive file. This is explained in detail in
175 L<Win::Hivex::Regedit(3)/ENCODING STRINGS>.
177 The default is to use UTF-16LE, which should work with recent versions
182 my $unsafe_printable_strings;
184 =item B<--unsafe-printable-strings>
186 When exporting (only), assume strings are UTF-16LE and print them as
187 strings instead of hex sequences. Remove the final zero codepoint
188 from strings if present.
190 This is unsafe and does not preserve the fidelity of strings in the
191 original Registry for various reasons:
197 Assumes the original encoding is UTF-16LE. ASCII strings and strings
198 in other encodings will be corrupted by this transformation.
202 Assumes that everything which has type 1 or 2 is really a string
203 and that everything else is not a string, but the type field in
204 real Registries is not reliable.
208 Loses information about whether a zero codepoint followed the string
209 in the Registry or not.
213 This all happens because the Registry itself contains no information
214 about how strings are encoded (see
215 L<Win::Hivex::Regedit(3)/ENCODING STRINGS>).
217 You should only use this option for quick hacking and debugging of the
218 Registry contents, and I<never> use it if the output is going to be
219 passed into another program or stored in another Registry.
225 GetOptions ("help|?" => \$help,
226 "version" => \$version,
227 "connect|c=s" => \$uri,
228 "debug|d" => \$debug,
229 "format=s" => \$format,
231 "encoding=s" => \$encoding,
232 "unsafe-printable-strings" => \$unsafe_printable_strings,
234 pod2usage (1) if $help;
236 my $g = Sys::Guestfs->new ();
237 my %h = $g->version ();
238 print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
242 # virt-win-reg only takes a single disk image ...
243 die __"no libvirt domain name or disk image given\n" if @ARGV == 0;
244 my $domname_or_image = shift @ARGV;
246 warn "launching libguestfs ..." if $debug;
248 my @lib_args = ([$domname_or_image]);
249 push @lib_args, address => $uri if $uri;
250 push @lib_args, rw => 1 if $merge;
251 push @lib_args, format => $format if defined $format;
252 my $g = open_guest (@lib_args);
255 warn "inspecting guest ..." if $debug;
257 my @roots = $g->inspect_os ();
259 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",
260 prog => basename ($0));
263 die __x("{prog}: multiboot operating systems are not supported.\n",
264 prog => basename ($0))
266 my %fses = $g->inspect_get_mountpoints ($roots[0]);
267 my @fses = sort { length $a <=> length $b } keys %fses;
268 my $mountopts = $merge ? "" : "ro";
270 $g->mount_options ($mountopts, $fses{$_}, $_);
273 my $systemroot = $g->inspect_get_windows_systemroot ($roots[0]);
275 # Create a working directory to store the downloaded registry files.
276 my $tmpdir = tempdir (CLEANUP => 1);
278 # Used when merging (only) to map from the downloaded hiveshortname to
279 # various properties about the hive. The key is hiveshortname. The
280 # value is a hashref containing {h} (hive handle) and {hivefile} (full
281 # hive path on the Windows side).
284 if (!$merge) { # Export mode.
285 die __"expecting 1 or 2 more parameters, subkey path and optionally the value to export\n"
286 if @ARGV < 1 || @ARGV > 2;
288 my $path = shift @ARGV;
289 my $name = shift @ARGV; # or undef
291 # Map this to the hive name. This function dies on failure.
292 my ($hiveshortname, $hivefile, $prefix);
293 ($hiveshortname, $hivefile, $path, $prefix) = map_path_to_hive ($path);
295 # Download the chosen hive.
296 download_hive ($hivefile, $hiveshortname);
299 my $h = Win::Hivex->open ("$tmpdir/$hiveshortname", debug => $debug);
303 warn "exporting $path from $hiveshortname with prefix $prefix ..."
305 reg_export ($h, $path, \*STDOUT,
307 unsafe_printable_strings => $unsafe_printable_strings);
309 # Export a single key using hivexget.
310 my @args = ("hivexget", "$tmpdir/$hiveshortname", $path, $name);
311 warn "running ", join (" ", @args), " ..." if $debug;
312 system (@args) == 0 or die "hivexget failed: $?"
315 else { # Import mode.
317 reg_import (\*STDIN, \&import_mapper, encoding => $encoding);
320 open my $fh, $_ or die "open: $_: $!";
321 reg_import ($fh, \&import_mapper, encoding => $encoding);
325 # Now we've done importing, commit all the hive handles and
327 foreach (values %hives) {
333 # Upload all the downloaded hives.
334 foreach my $hiveshortname (keys %hives) {
335 upload_hive ($hiveshortname, $hives{$hiveshortname}->{hivefile})
345 # map function passed to reg_import.
350 my ($hiveshortname, $hivefile, $path, $prefix) = map_path_to_hive ($_);
352 # Need to download this hive?
353 unless (-f "$tmpdir/$hiveshortname") {
354 download_hive ($hivefile, $hiveshortname);
356 my $h = Win::Hivex->open ("$tmpdir/$hiveshortname",
357 write => 1, debug => $debug);
358 my %hash = ( h => $h, hivefile => $hivefile );
359 $hives{$hiveshortname} = \%hash;
362 return ($hives{$hiveshortname}->{h}, $path);
365 # Given a path, map that to the name of the hive and the true path
370 my ($hiveshortname, $hivefile, $path, $prefix);
372 if (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SAM(\\.*)?$/i) {
373 $hiveshortname = "sam";
374 $hivefile = "$systemroot/system32/config/$hiveshortname";
375 $path = defined $1 ? $1 : "\\";
376 $prefix = "HKEY_LOCAL_MACHINE\\SAM";
378 elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SECURITY(\\.*)?$/i) {
379 $hiveshortname = "security";
380 $hivefile = "$systemroot/system32/config/$hiveshortname";
381 $path = defined $1 ? $1 : "\\";
382 $prefix = "HKEY_LOCAL_MACHINE\\SECURITY";
384 elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SOFTWARE(\\.*)?$/i) {
385 $hiveshortname = "software";
386 $hivefile = "$systemroot/system32/config/$hiveshortname";
387 $path = defined $1 ? $1 : "\\";
388 $prefix = "HKEY_LOCAL_MACHINE\\SOFTWARE";
390 elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SYSTEM(\\.*)?$/i) {
391 $hiveshortname = "system";
392 $hivefile = "$systemroot/system32/config/$hiveshortname";
393 $path = defined $1 ? $1 : "\\";
394 $prefix = "HKEY_LOCAL_MACHINE\\SYSTEM";
396 elsif (/^\\?(?:HKEY_USERS|HKU)\\.DEFAULT(\\.*)?$/i) {
397 $hiveshortname = "default";
398 $hivefile = "$systemroot/system32/config/$hiveshortname";
399 $path = defined $1 ? $1 : "\\";
400 $prefix = "HKEY_LOCAL_MACHINE\\.DEFAULT";
402 elsif (/^\\?(?:HKEY_USERS|HKU)\\(S-1-5-[-\d]+)(\\.*)?$/i) {
404 $hiveshortname = $sid;
405 $prefix = "HKEY_USERS\\$sid";
406 $path = defined $2 ? $2 : "\\";
407 # This requires a recursive call to download the SOFTWARE hive.
408 $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat";
410 elsif (/^\\?(?:HKEY_USERS|HKU)\\LocalSystem(\\.*)?$/i) {
411 my $sid = "S-1-5-18";
412 $hiveshortname = $sid;
413 $prefix = "HKEY_USERS\\$sid";
414 $path = defined $1 ? $1 : "\\";
415 # This requires a recursive call to download the SOFTWARE hive.
416 $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat";
418 elsif (/^\\?(?:HKEY_USERS|HKU)\\LocalService(\\.*)?$/i) {
419 my $sid = "S-1-5-19";
420 $hiveshortname = $sid;
421 $prefix = "HKEY_USERS\\$sid";
422 $path = defined $1 ? $1 : "\\";
423 # This requires a recursive call to download the SOFTWARE hive.
424 $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat";
426 elsif (/^\\?(?:HKEY_USERS|HKU)\\NetworkService(\\.*)?$/i) {
427 my $sid = "S-1-5-20";
428 $hiveshortname = $sid;
429 $prefix = "HKEY_USERS\\$sid";
430 $path = defined $1 ? $1 : "\\";
431 # This requires a recursive call to download the SOFTWARE hive.
432 $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat";
434 elsif (/^\\?(?:HKEY_USERS|HKU)\\(.*?)(\\.*)?$/i) {
435 $hiveshortname = "user_$1";
436 $prefix = "HKEY_USERS\\$1";
437 $path = defined $2 ? $2 : "\\";
438 # XXX We should probably look this up properly.
439 if (is_dir_nocase ("/Users/$1")) {
440 $hivefile = "/Users/$1/ntuser.dat"
441 } elsif (is_dir_nocase ("/Documents and Settings/$1")) {
442 $hivefile = "/Documents and Settings/$1/ntuser.dat"
444 die __x("virt-win-reg: {p}: cannot find user directory\n",
449 die __x("virt-win-reg: {p}: not a supported Windows Registry path\n",
453 return ($hiveshortname, $hivefile, $path, $prefix);
456 # Given a User SID, consult
457 # HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid
458 # and return the ProfileImagePath value.
459 sub lookup_pip_of_user_sid
465 "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\".
468 my ($hiveshortname, $hivefile, $prefix);
469 ($hiveshortname, $hivefile, $path, $prefix) = map_path_to_hive ($path);
471 download_hive ($hivefile, $hiveshortname)
472 unless -f "$tmpdir/$hiveshortname";
474 my @args = ("$tmpdir/$hiveshortname", $path, "ProfileImagePath");
475 warn "running hivexget ", join (" ", @args), " ..." if $debug;
478 open $fh, "-|", "hivexget", @args
479 or die "hivexget: see earlier errors: $!";
481 close $fh or die "hivexget: see earlier errors: $!";
485 # The contents of the registry are a windows path, possibly
486 # containing %systemroot% and %systemdrive% (on Win XP). Expand
487 # it and remove some other windows-isms. The caller will do
488 # case_sensitive_path for us, so we don't need to do that.
489 s/%systemroot%/$systemroot/i;
503 eval { $windir = $g->case_sensitive_path ($dir); };
507 return $g->is_dir ($windir);
510 # Download a named hive file. Die on failure.
514 my $hivefile = shift;
515 my $hiveshortname = shift;
518 eval { $winfile = $g->case_sensitive_path ($hivefile); };
520 die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
521 p => $hivefile, err => $@);
524 warn "downloading $winfile ..." if $debug;
525 eval { $g->download ($winfile, "$tmpdir/$hiveshortname"); };
527 die __x("virt-win-reg: {p}: could not download registry file: {err}\n",
528 p => $winfile, err => $@);
532 # Upload a named hive file. Die on failure.
536 my $hiveshortname = shift;
537 my $hivefile = shift;
540 eval { $winfile = $g->case_sensitive_path ($hivefile); };
542 die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
543 p => $hivefile, err => $@);
546 warn "uploading $winfile ..." if $debug;
547 eval { $g->upload ("$tmpdir/$hiveshortname", $winfile); };
549 die __x("virt-win-reg: {p}: could not upload registry file: {err}\n",
550 p => $winfile, err => $@);
554 =head1 SUPPORTED SYSTEMS
556 The program currently supports Windows NT-derived guests starting with
557 Windows XP through to at least Windows 7.
559 The following Registry keys are supported:
563 =item C<HKEY_LOCAL_MACHINE\SAM>
565 =item C<HKEY_LOCAL_MACHINE\SECURITY>
567 =item C<HKEY_LOCAL_MACHINE\SOFTWARE>
569 =item C<HKEY_LOCAL_MACHINE\SYSTEM>
571 =item C<HKEY_USERS\.DEFAULT>
573 =item C<HKEY_USERS\I<SID>>
575 where I<SID> is a Windows User SID (eg. C<S-1-5-18>).
577 =item C<HKEY_USERS\I<username>>
579 where I<username> is a local user name (this is a libguestfs extension).
583 You can use C<HKLM> as a shorthand for C<HKEY_LOCAL_MACHINE>, and
584 C<HKU> for C<HKEY_USERS>.
586 The literal keys C<HKEY_USERS\$SID> and C<HKEY_CURRENT_USER> are not
587 supported (there is no "current user").
591 C<virt-win-reg> expects that regedit files have already been reencoded
592 in the local encoding. Usually on Linux hosts, this means UTF-8 with
593 Unix-style line endings. Since Windows regedit files are often in
594 UTF-16LE with Windows-style line endings, you may need to reencode the
595 whole file before or after processing.
597 To reencode a file from Windows format to Linux (before processing it
598 with the I<--merge> option), you would do something like this:
600 iconv -f utf-16le -t utf-8 < win.reg | dos2unix > linux.reg
602 To go in the opposite direction, after exporting and before sending
603 the file to a Windows user, do something like this:
605 unix2dos linux.reg | iconv -f utf-8 -t utf-16le > win.reg
607 For more information about encoding, see L<Win::Hivex::Regedit(3)>.
609 If you are unsure about the current encoding, use the L<file(1)>
610 command. Recent versions of Windows regedit.exe produce a UTF-16LE
611 file with Windows-style (CRLF) line endings, like this:
614 software.reg: Little-endian UTF-16 Unicode text, with very long lines,
615 with CRLF line terminators
617 This file would need conversion before you could I<--merge> it.
619 =head1 CurrentControlSet etc.
621 Registry keys like C<CurrentControlSet> don't really exist in the
622 Windows Registry at the level of the hive file, and therefore you
625 C<CurrentControlSet> is usually an alias for C<ControlSet001>. In
626 some circumstances it might refer to another control set. The way
627 to find out is to look at the C<HKLM\SYSTEM\Select> key:
629 # virt-win-reg WindowsGuest 'HKLM\SYSTEM\Select'
630 [HKEY_LOCAL_MACHINE\SYSTEM\Select]
631 "Current"=dword:00000001
632 "Default"=dword:00000001
633 "Failed"=dword:00000000
634 "LastKnownGood"=dword:00000002
636 "Current" is the one which Windows will choose when it boots.
638 Similarly, other C<Current...> keys in the path may need to
643 Note that some of these tips modify the guest disk image. The guest
644 I<must> be shut off, else you will get disk corruption.
646 =head2 RUNNING A BATCH SCRIPT WHEN A USER LOGS IN
648 Prepare a DOS batch script, VBScript or executable. Upload this using
649 L<guestfish(1)>. For this example the script is called C<test.bat>
650 and it is uploaded into C<C:\>:
652 guestfish -i -d WindowsGuest upload test.bat /test.bat
654 Prepare a regedit file containing the registry change:
656 cat > test.reg <<'EOF'
657 [HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce]
658 "Test"="c:\\test.bat"
661 In this example we use the key C<RunOnce> which means that the script
662 will run precisely once when the first user logs in. If you want it
663 to run every time a user logs in, replace C<RunOnce> with C<Run>.
665 Now update the registry:
667 virt-win-reg --merge WindowsGuest test.reg
669 =head2 INSTALLING A SERVICE
671 This section assumes you are familiar with Windows services, and you
672 either have a program which handles the Windows Service Control
673 Protocol directly or you want to run any program using a service
674 wrapper like SrvAny or the free RHSrvAny.
676 First upload the program and optionally the service wrapper. In this
677 case the test program is called C<test.exe> and we are using the
680 guestfish -i -d WindowsGuest <<EOF
681 upload rhsrvany.exe /rhsrvany.exe
682 upload test.exe /test.exe
685 Prepare a regedit file containing the registry changes. In this
686 example, the first registry change is needed for the service itself or
687 the service wrapper (if used). The second registry change is only
688 needed because I am using the RHSrvAny service wrapper.
690 cat > service.reg <<'EOF'
691 [HKLM\SYSTEM\ControlSet001\services\RHSrvAny]
692 "Type"=dword:00000010
693 "Start"=dword:00000002
694 "ErrorControl"=dword:00000001
695 "ImagePath"="c:\\rhsrvany.exe"
696 "DisplayName"="RHSrvAny"
697 "ObjectName"="NetworkService"
699 [HKLM\SYSTEM\ControlSet001\services\RHSrvAny\Parameters]
700 "CommandLine"="c:\\test.exe"
710 For use of C<ControlSet001> see the section above in this manual page.
711 You may need to adjust this according to the control set that is in
716 C<"ObjectName"> controls the privileges that the service will have.
717 An alternative is C<"ObjectName"="LocalSystem"> which would be the
718 most privileged account.
722 For the meaning of the magic numbers, see this Microsoft KB article:
723 L<http://support.microsoft.com/kb/103000>.
729 virt-win-reg --merge WindowsGuest service.reg
733 Be careful when passing parameters containing C<\> (backslash) in the
734 shell. Usually you will have to use 'single quotes' or double
735 backslashes (but not both) to protect them from the shell.
737 Paths and value names are case-insensitive.
739 Libvirt guest names can contain arbitrary characters, some of which
740 have meaning to the shell such as C<#> and space. You may need to
741 quote or escape these characters on the command line. See the shell
742 manual page L<sh(1)> for details.
753 L<Sys::Guestfs::Lib(3)>,
755 L<Win::Hivex::Regedit(3)>,
757 L<http://libguestfs.org/>.
761 When reporting bugs, please enable debugging and capture the
764 export LIBGUESTFS_DEBUG=1
765 virt-win-reg --debug [... rest ...] > /tmp/virt-win-reg.log 2>&1
767 Attach /tmp/virt-win-reg.log to a new bug report at
768 L<https://bugzilla.redhat.com/>
772 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
776 Copyright (C) 2010 Red Hat Inc.
778 This program is free software; you can redistribute it and/or modify
779 it under the terms of the GNU General Public License as published by
780 the Free Software Foundation; either version 2 of the License, or
781 (at your option) any later version.
783 This program is distributed in the hope that it will be useful,
784 but WITHOUT ANY WARRANTY; without even the implied warranty of
785 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
786 GNU General Public License for more details.
788 You should have received a copy of the GNU General Public License
789 along with this program; if not, write to the Free Software
790 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.