images: Make a better phony Fedora image.
[libguestfs.git] / tools / virt-win-reg
1 #!/usr/bin/perl -w
2 # virt-win-reg
3 # Copyright (C) 2010 Red Hat Inc.
4 #
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.
9 #
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.
14 #
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.
18
19 use warnings;
20 use strict;
21
22 use Sys::Guestfs;
23 use Sys::Guestfs::Lib qw(open_guest);
24 use Win::Hivex;
25 use Win::Hivex::Regedit qw(reg_import reg_export);
26
27 use Pod::Usage;
28 use Getopt::Long;
29 use File::Temp qw/tempdir/;
30 use File::Basename;
31 use Locale::TextDomain 'libguestfs';
32
33 =encoding utf8
34
35 =head1 NAME
36
37 virt-win-reg - Export and merge Windows Registry entries from a Windows guest
38
39 =head1 SYNOPSIS
40
41  virt-win-reg domname 'HKLM\Path\To\Subkey'
42
43  virt-win-reg domname 'HKLM\Path\To\Subkey' name
44
45  virt-win-reg domname 'HKLM\Path\To\Subkey' @
46
47  virt-win-reg --merge domname [input.reg ...]
48
49  virt-win-reg [--options] disk.img ... # instead of domname
50
51 =head1 WARNING
52
53 You must I<not> use C<virt-win-reg> with the C<--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.
57
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 C<--merge>
61 option, make sure you have a reliable backup first.
62
63 =head1 DESCRIPTION
64
65 This program can export and merge Windows Registry entries from a
66 Windows guest.
67
68 The first parameter is the libvirt guest name or the raw disk image of
69 a Windows guest.
70
71 If C<--merge> is I<not> specified, then the chosen registry
72 key is displayed/exported (recursively).  For example:
73
74  $ virt-win-reg Windows7 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft'
75
76 You can also display single values from within registry keys,
77 for example:
78
79  $ cvkey='HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
80  $ virt-win-reg Windows7 $cvkey ProductName
81  Windows 7 Enterprise
82
83 With C<--merge>, you can merge a textual regedit file into
84 the Windows Registry:
85
86  $ virt-win-reg --merge Windows7 changes.reg
87
88 =head2 SUPPORTED SYSTEMS
89
90 The program currently supports Windows NT-derived guests starting with
91 Windows XP through to at least Windows 7.
92
93 Registry support is done for C<HKEY_LOCAL_MACHINE\SAM>,
94 C<HKEY_LOCAL_MACHINE\SECURITY>, C<HKEY_LOCAL_MACHINE\SOFTWARE>,
95 C<HKEY_LOCAL_MACHINE\SYSTEM> and C<HKEY_USERS\.DEFAULT>.
96
97 You can use C<HKLM> as a shorthand for C<HKEY_LOCAL_MACHINE>, and
98 C<HKU> for C<HKEY_USERS>.
99
100 C<HKEY_USERS\$SID> and C<HKEY_CURRENT_USER> are B<not> supported at
101 this time.
102
103 =head2 NOTE
104
105 This program is only meant for simple access to the registry.  If you
106 want to do complicated things with the registry, we suggest you
107 download the Registry hive files from the guest using L<libguestfs(3)>
108 or L<guestfish(1)> and access them locally, eg. using L<hivex(3)>,
109 L<hivexsh(1)> or L<hivexregedit(1)>.
110
111 =head2 ENCODING
112
113 C<virt-win-reg> expects that regedit files have already been reencoded
114 in the local encoding.  Usually on Linux hosts, this means UTF-8 with
115 Unix-style line endings.  Since Windows regedit files are often in
116 UTF-16LE with Windows-style line endings, you may need to reencode the
117 whole file before or after processing.
118
119 To reencode a file from Windows format to Linux (before processing it
120 with the C<--merge> option), you would do something like this:
121
122  iconv -f utf-16le -t utf-8 < win.reg | dos2unix > linux.reg
123
124 To go in the opposite direction, after exporting and before sending
125 the file to a Windows user, do something like this:
126
127  unix2dos linux.reg | iconv -f utf-8 -t utf-16le > win.reg
128
129 For more information about encoding, see L<Win::Hivex::Regedit(3)>.
130
131 If you are unsure about the current encoding, use the L<file(1)>
132 command.  Recent versions of Windows regedit.exe produce a UTF-16LE
133 file with Windows-style (CRLF) line endings, like this:
134
135  $ file software.reg
136  software.reg: Little-endian UTF-16 Unicode text, with very long lines,
137  with CRLF line terminators
138
139 This file would need conversion before you could C<--merge> it.
140
141 =head2 SHELL QUOTING
142
143 Be careful when passing parameters containing C<\> (backslash) in the
144 shell.  Usually you will have to use 'single quotes' or double
145 backslashes (but not both) to protect them from the shell.
146
147 Paths and value names are case-insensitive.
148
149 =head2 CurrentControlSet etc.
150
151 Registry keys like C<CurrentControlSet> don't really exist in the
152 Windows Registry at the level of the hive file, and therefore you
153 cannot modify these.
154
155 C<CurrentControlSet> is usually an alias for C<ControlSet001>.  In
156 some circumstances it might refer to another control set.  The way
157 to find out is to look at the C<HKLM\SYSTEM\Select> key:
158
159  # virt-win-reg WindowsGuest 'HKLM\SYSTEM\Select'
160  [HKEY_LOCAL_MACHINE\SYSTEM\Select]
161  "Current"=dword:00000001
162  "Default"=dword:00000001
163  "Failed"=dword:00000000
164  "LastKnownGood"=dword:00000002
165
166 "Current" is the one which Windows will choose when it boots.
167
168 Similarly, other C<Current...> keys in the path may need to
169 be replaced.
170
171 =head1 OPTIONS
172
173 =over 4
174
175 =cut
176
177 my $help;
178
179 =item B<--help>
180
181 Display brief help.
182
183 =cut
184
185 my $version;
186
187 =item B<--version>
188
189 Display version number and exit.
190
191 =cut
192
193 my $debug;
194
195 =item B<--debug>
196
197 Enable debugging messages.
198
199 =cut
200
201 my $uri;
202
203 =item B<--connect URI> | B<-c URI>
204
205 If using libvirt, connect to the given I<URI>.  If omitted, then we
206 connect to the default libvirt hypervisor.
207
208 If you specify guest block devices directly, then libvirt is not used
209 at all.
210
211 =cut
212
213 my $format;
214
215 =item B<--format> raw
216
217 Specify the format of disk images given on the command line.  If this
218 is omitted then the format is autodetected from the content of the
219 disk image.
220
221 If disk images are requested from libvirt, then this program asks
222 libvirt for this information.  In this case, the value of the format
223 parameter is ignored.
224
225 If working with untrusted raw-format guest disk images, you should
226 ensure the format is always specified.
227
228 =cut
229
230 my $merge;
231
232 =item B<--merge>
233
234 In merge mode, this merges a textual regedit file into the Windows
235 Registry of the virtual machine.  If this flag is I<not> given then
236 virt-win-reg displays or exports Registry entries instead.
237
238 Note that C<--merge> is I<unsafe> to use on live virtual machines, and
239 will result in disk corruption.  However exporting (without this flag)
240 is always safe.
241
242 =cut
243
244 my $encoding;
245
246 =item B<--encoding> UTF-16LE|ASCII
247
248 When merging (only), you may need to specify the encoding for strings
249 to be used in the hive file.  This is explained in detail in
250 L<Win::Hivex::Regedit(3)/ENCODING STRINGS>.
251
252 The default is to use UTF-16LE, which should work with recent versions
253 of Windows.
254
255 =back
256
257 =cut
258
259 GetOptions ("help|?" => \$help,
260             "version" => \$version,
261             "connect|c=s" => \$uri,
262             "debug|d" => \$debug,
263             "format=s" => \$format,
264             "merge" => \$merge,
265             "encoding=s" => \$encoding,
266     ) or pod2usage (2);
267 pod2usage (1) if $help;
268 if ($version) {
269     my $g = Sys::Guestfs->new ();
270     my %h = $g->version ();
271     print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
272     exit
273 }
274
275 # virt-win-reg only takes a single disk image ...
276 die __"no libvirt domain name or disk image given\n" if @ARGV == 0;
277 my $domname_or_image = shift @ARGV;
278
279 warn "launching libguestfs ..." if $debug;
280
281 my @lib_args = ([$domname_or_image]);
282 push @lib_args, address => $uri if $uri;
283 push @lib_args, rw => 1 if $merge;
284 push @lib_args, format => $format if defined $format;
285 my $g = open_guest (@lib_args);
286 $g->launch ();
287
288 warn "inspecting guest ..." if $debug;
289
290 my @roots = $g->inspect_os ();
291 if (@roots == 0) {
292     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",
293             prog => basename ($0));
294 }
295 if (@roots > 1) {
296     die __x("{prog}: multiboot operating systems are not supported.\n",
297             prog => basename ($0))
298 }
299 my %fses = $g->inspect_get_mountpoints ($roots[0]);
300 my @fses = sort { length $a <=> length $b } keys %fses;
301 my $mountopts = $merge ? "" : "ro";
302 foreach (@fses) {
303     $g->mount_options ($mountopts, $fses{$_}, $_);
304 }
305
306 my $systemroot = $g->inspect_get_windows_systemroot ($roots[0]);
307
308 # Create a working directory to store the downloaded registry files.
309 my $tmpdir = tempdir (CLEANUP => 1);
310
311 # Only used when merging to map downloaded hive names to hive handles.
312 my %hives;
313
314 if (!$merge) {                  # Export mode.
315     die __"expecting 1 or 2 more parameters, subkey path and optionally the value to export\n"
316         if @ARGV < 1 || @ARGV > 2;
317
318     my $path = shift @ARGV;
319     my $name = shift @ARGV; # or undef
320
321     # Map this to the hive name.  This function dies on failure.
322     my ($hivename, $prefix);
323     ($hivename, $path, $prefix) = map_path_to_hive ($path);
324
325     # Download the chosen hive.
326     download_hive ($hivename);
327
328     # Open it.
329     my $h = Win::Hivex->open ("$tmpdir/$hivename", debug => $debug);
330
331     unless ($name) {
332         # Export it.
333         warn "exporting $path from $hivename with prefix $prefix ..." if $debug;
334         reg_export ($h, $path, \*STDOUT, prefix => $prefix);
335     } else {
336         # Export a single key using hivexget.
337         my @args = ("hivexget", "$tmpdir/$hivename", $path, $name);
338         warn "running ", join (" ", @args), " ..." if $debug;
339         system (@args) == 0 or die "hivexget failed: $?"
340     }
341 }
342 else {                          # Import mode.
343     if (@ARGV == 0) {
344         reg_import (\*STDIN, \&import_mapper, encoding => $encoding);
345     } else {
346         foreach (@ARGV) {
347             open my $fh, $_ or die "open: $_: $!";
348             reg_import ($fh, \&import_mapper, encoding => $encoding);
349         }
350     }
351
352     # Now we've done importing, commit all the hive handles and
353     # close them all.
354     $_->commit (undef) foreach values %hives;
355     %hives = ();
356
357     # Look in the tmpdir for all the hive files which have been
358     # downloaded / modified by the import mapper, and upload
359     # each one.
360     opendir my $dh, $tmpdir or die "$tmpdir: $!";
361     foreach (readdir $dh) {
362         unless (/^\./) {
363             upload_hive ($_)
364         }
365     }
366
367     # Sync everything.
368     $g->umount_all ();
369     $g->sync ();
370 }
371
372 exit 0;
373
374 # map function passed to reg_import.
375 sub import_mapper
376 {
377     local $_ = shift;
378
379     my ($hivename, $path, $prefix) = map_path_to_hive ($_);
380
381     # Need to download this hive?
382     unless (-f "$tmpdir/$hivename") {
383         download_hive ($hivename);
384
385         my $h = Win::Hivex->open ("$tmpdir/$hivename",
386                                   write => 1, debug => $debug);
387         $hives{$hivename} = $h;
388     }
389
390     return ($hives{$hivename}, $path);
391 }
392
393 # Given a path, map that to the name of the hive and the true path
394 # within that hive.
395 sub map_path_to_hive
396 {
397     local $_ = shift;
398     my ($hivename, $prefix);
399
400     if (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SAM(\\.*)?$/i) {
401         $hivename = "sam";
402         $_ = defined $1 ? $1 : "\\";
403         $prefix = "HKEY_LOCAL_MACHINE\\SAM";
404     }
405     elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SECURITY(\\.*)?$/i) {
406         $hivename = "security";
407         $_ = defined $1 ? $1 : "\\";
408         $prefix = "HKEY_LOCAL_MACHINE\\SECURITY";
409     }
410     elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SOFTWARE(\\.*)?$/i) {
411         $hivename = "software";
412         $_ = defined $1 ? $1 : "\\";
413         $prefix = "HKEY_LOCAL_MACHINE\\SOFTWARE";
414     }
415     elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SYSTEM(\\.*)?$/i) {
416         $hivename = "system";
417         $_ = defined $1 ? $1 : "\\";
418         $prefix = "HKEY_LOCAL_MACHINE\\SYSTEM";
419     }
420     elsif (/^\\?(?:HKEY_USERS|HKU)\\.DEFAULT(\\.*)?$/i) {
421         $hivename = "default";
422         $_ = defined $1 ? $1 : "\\";
423         $prefix = "HKEY_LOCAL_MACHINE\\.DEFAULT";
424     }
425     else {
426         die __x("virt-win-reg: {p}: not a supported Windows Registry path\n",
427                 p => $_)
428     }
429
430     return ($hivename, $_, $prefix);
431 }
432
433 # Download a named hive file.  Die on failure.
434 sub download_hive
435 {
436     local $_;
437     my $hivename = shift;
438
439     my $winfile_before = "$systemroot/system32/config/$hivename";
440     my $winfile;
441     eval { $winfile = $g->case_sensitive_path ($winfile_before); };
442     if ($@) {
443         die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
444                 p => $winfile_before, err => $@);
445     }
446
447     warn "downloading $winfile ..." if $debug;
448     eval { $g->download ($winfile, "$tmpdir/$hivename"); };
449     if ($@) {
450         die __x("virt-win-reg: {p}: could not download registry file: {err}\n",
451                 p => $winfile, err => $@);
452     }
453 }
454
455 # Upload a named hive file.  Die on failure.
456 sub upload_hive
457 {
458     local $_;
459     my $hivename = shift;
460
461     my $winfile_before = "$systemroot/system32/config/$hivename";
462     my $winfile;
463     eval { $winfile = $g->case_sensitive_path ($winfile_before); };
464     if ($@) {
465         die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
466                 p => $winfile_before, err => $@);
467     }
468
469     warn "uploading $winfile ..." if $debug;
470     eval { $g->upload ("$tmpdir/$hivename", $winfile); };
471     if ($@) {
472         die __x("virt-win-reg: {p}: could not upload registry file: {err}\n",
473                 p => $winfile, err => $@);
474     }
475 }
476
477 =head1 SHELL QUOTING
478
479 Libvirt guest names can contain arbitrary characters, some of which
480 have meaning to the shell such as C<#> and space.  You may need to
481 quote or escape these characters on the command line.  See the shell
482 manual page L<sh(1)> for details.
483
484 =head1 SEE ALSO
485
486 L<hivex(3)>,
487 L<hivexsh(1)>,
488 L<hivexregedit(1)>,
489 L<guestfs(3)>,
490 L<guestfish(1)>,
491 L<virt-cat(1)>,
492 L<Sys::Guestfs(3)>,
493 L<Sys::Guestfs::Lib(3)>,
494 L<Win::Hivex(3)>,
495 L<Win::Hivex::Regedit(3)>,
496 L<Sys::Virt(3)>,
497 L<http://libguestfs.org/>.
498
499 =head1 BUGS
500
501 When reporting bugs, please enable debugging and capture the
502 I<complete> output:
503
504  export LIBGUESTFS_DEBUG=1
505  virt-win-reg --debug [... rest ...] > /tmp/virt-win-reg.log 2>&1
506
507 Attach /tmp/virt-win-reg.log to a new bug report at
508 L<https://bugzilla.redhat.com/>
509
510 =head1 AUTHOR
511
512 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
513
514 =head1 COPYRIGHT
515
516 Copyright (C) 2010 Red Hat Inc.
517
518 This program is free software; you can redistribute it and/or modify
519 it under the terms of the GNU General Public License as published by
520 the Free Software Foundation; either version 2 of the License, or
521 (at your option) any later version.
522
523 This program is distributed in the hope that it will be useful,
524 but WITHOUT ANY WARRANTY; without even the implied warranty of
525 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
526 GNU General Public License for more details.
527
528 You should have received a copy of the GNU General Public License
529 along with this program; if not, write to the Free Software
530 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.