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