Bugfixes for virt-resize.
[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.  Replace this with C<ControlSet001>, and
155 similarly for other C<Current...> keys.
156
157 =head1 OPTIONS
158
159 =over 4
160
161 =cut
162
163 my $help;
164
165 =item B<--help>
166
167 Display brief help.
168
169 =cut
170
171 my $version;
172
173 =item B<--version>
174
175 Display version number and exit.
176
177 =cut
178
179 my $debug;
180
181 =item B<--debug>
182
183 Enable debugging messages.
184
185 =cut
186
187 my $uri;
188
189 =item B<--connect URI> | B<-c URI>
190
191 If using libvirt, connect to the given I<URI>.  If omitted, then we
192 connect to the default libvirt hypervisor.
193
194 If you specify guest block devices directly, then libvirt is not used
195 at all.
196
197 =cut
198
199 my $merge;
200
201 =item B<--merge>
202
203 In merge mode, this merges a textual regedit file into the Windows
204 Registry of the virtual machine.  If this flag is I<not> given then
205 virt-win-reg displays or exports Registry entries instead.
206
207 Note that C<--merge> is I<unsafe> to use on live virtual machines, and
208 will result in disk corruption.  However exporting (without this flag)
209 is always safe.
210
211 =cut
212
213 my $encoding;
214
215 =item B<--encoding> UTF-16LE|ASCII
216
217 When merging (only), you may need to specify the encoding for strings
218 to be used in the hive file.  This is explained in detail in
219 L<Win::Hivex::Regedit(3)/ENCODING STRINGS>.
220
221 The default is to use UTF-16LE, which should work with recent versions
222 of Windows.
223
224 =back
225
226 =cut
227
228 GetOptions ("help|?" => \$help,
229             "version" => \$version,
230             "connect|c=s" => \$uri,
231             "debug|d" => \$debug,
232             "merge" => \$merge,
233             "encoding=s" => \$encoding,
234     ) or pod2usage (2);
235 pod2usage (1) if $help;
236 if ($version) {
237     my $g = Sys::Guestfs->new ();
238     my %h = $g->version ();
239     print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
240     exit
241 }
242
243 # virt-win-reg only takes a single disk image ...
244 die __"no libvirt domain name or disk image given\n" if @ARGV == 0;
245 my $domname_or_image = shift @ARGV;
246
247 warn "launching libguestfs ..." if $debug;
248
249 my @lib_args = ([$domname_or_image]);
250 push @lib_args, address => $uri if $uri;
251 push @lib_args, rw => 1 if $merge;
252 my $g = open_guest (@lib_args);
253 $g->launch ();
254
255 warn "inspecting guest ..." if $debug;
256
257 # List of possible filesystems.
258 my @partitions = get_partitions ($g);
259
260 # Now query each one to build up a picture of what's in it.
261 my %fses =
262     inspect_all_partitions ($g, \@partitions,
263       use_windows_registry => 0);
264
265 my $oses = inspect_operating_systems ($g, \%fses);
266
267 my @roots = keys %$oses;
268 die __"no root device found in this operating system image" if @roots == 0;
269 die __"multiboot operating systems are not supported by virt-win-reg" if @roots > 1;
270 my $root_dev = $roots[0];
271
272 my $os = $oses->{$root_dev};
273 my $ro = $merge ? 0 : 1;
274 mount_operating_system ($g, $os, $ro);
275
276 # Create a working directory to store the downloaded registry files.
277 my $tmpdir = tempdir (CLEANUP => 1);
278
279 # Only used when merging to map downloaded hive names to hive handles.
280 my %hives;
281
282 if (!$merge) {                  # Export mode.
283     die __"expecting 1 or 2 more parameters, subkey path and optionally the value to export\n"
284         if @ARGV < 1 || @ARGV > 2;
285
286     my $path = shift @ARGV;
287     my $name = shift @ARGV; # or undef
288
289     # Map this to the hive name.  This function dies on failure.
290     my ($hivename, $prefix);
291     ($hivename, $path, $prefix) = map_path_to_hive ($path);
292
293     # Download the chosen hive.
294     download_hive ($hivename);
295
296     # Open it.
297     my $h = Win::Hivex->open ("$tmpdir/$hivename", debug => $debug);
298
299     unless ($name) {
300         # Export it.
301         warn "exporting $path from $hivename with prefix $prefix ..." if $debug;
302         reg_export ($h, $path, \*STDOUT, prefix => $prefix);
303     } else {
304         # Export a single key using hivexget.
305         my @args = ("hivexget", "$tmpdir/$hivename", $path, $name);
306         warn "running ", join (" ", @args), " ..." if $debug;
307         system (@args) == 0 or die "hivexget failed: $?"
308     }
309 }
310 else {                          # Import mode.
311     if (@ARGV == 0) {
312         reg_import (\*STDIN, \&import_mapper, encoding => $encoding);
313     } else {
314         foreach (@ARGV) {
315             open my $fh, $_ or die "open: $_: $!";
316             reg_import ($fh, \&import_mapper, encoding => $encoding);
317         }
318     }
319
320     # Now we've done importing, commit all the hive handles and
321     # close them all.
322     $_->commit (undef) foreach values %hives;
323     %hives = ();
324
325     # Look in the tmpdir for all the hive files which have been
326     # downloaded / modified by the import mapper, and upload
327     # each one.
328     opendir my $dh, $tmpdir or die "$tmpdir: $!";
329     foreach (readdir $dh) {
330         unless (/^\./) {
331             upload_hive ($_)
332         }
333     }
334
335     # Sync everything.
336     $g->umount_all ();
337     $g->sync ();
338 }
339
340 exit 0;
341
342 # map function passed to reg_import.
343 sub import_mapper
344 {
345     local $_ = shift;
346
347     my ($hivename, $path, $prefix) = map_path_to_hive ($_);
348
349     # Need to download this hive?
350     unless (-f "$tmpdir/$hivename") {
351         download_hive ($hivename);
352
353         my $h = Win::Hivex->open ("$tmpdir/$hivename",
354                                   write => 1, debug => $debug);
355         $hives{$hivename} = $h;
356     }
357
358     return ($hives{$hivename}, $path);
359 }
360
361 # Given a path, map that to the name of the hive and the true path
362 # within that hive.
363 sub map_path_to_hive
364 {
365     local $_ = shift;
366     my ($hivename, $prefix);
367
368     if (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SAM(\\.*)?$/i) {
369         $hivename = "sam";
370         $_ = defined $1 ? $1 : "\\";
371         $prefix = "HKEY_LOCAL_MACHINE\\SAM";
372     }
373     elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SECURITY(\\.*)?$/i) {
374         $hivename = "security";
375         $_ = defined $1 ? $1 : "\\";
376         $prefix = "HKEY_LOCAL_MACHINE\\SECURITY";
377     }
378     elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SOFTWARE(\\.*)?$/i) {
379         $hivename = "software";
380         $_ = defined $1 ? $1 : "\\";
381         $prefix = "HKEY_LOCAL_MACHINE\\SOFTWARE";
382     }
383     elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SYSTEM(\\.*)?$/i) {
384         $hivename = "system";
385         $_ = defined $1 ? $1 : "\\";
386         $prefix = "HKEY_LOCAL_MACHINE\\SYSTEM";
387     }
388     elsif (/^\\?(?:HKEY_USERS|HKU)\\.DEFAULT(\\.*)?$/i) {
389         $hivename = "default";
390         $_ = defined $1 ? $1 : "\\";
391         $prefix = "HKEY_LOCAL_MACHINE\\.DEFAULT";
392     }
393     else {
394         die __x("virt-win-reg: {p}: not a supported Windows Registry path\n",
395                 p => $_)
396     }
397
398     return ($hivename, $_, $prefix);
399 }
400
401 # Download a named hive file.  Die on failure.
402 sub download_hive
403 {
404     local $_;
405     my $hivename = shift;
406
407     my $systemroot = $os->{root}->{systemroot} || "/windows";
408     my $winfile_before = "$systemroot/system32/config/$hivename";
409     my $winfile;
410     eval { $winfile = $g->case_sensitive_path ($winfile_before); };
411     if ($@) {
412         die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
413                 p => $winfile_before, err => $@);
414     }
415
416     warn "downloading $winfile ..." if $debug;
417     eval { $g->download ($winfile, "$tmpdir/$hivename"); };
418     if ($@) {
419         die __x("virt-win-reg: {p}: could not download registry file: {err}\n",
420                 p => $winfile, err => $@);
421     }
422 }
423
424 # Upload a named hive file.  Die on failure.
425 sub upload_hive
426 {
427     local $_;
428     my $hivename = shift;
429
430     my $systemroot = $os->{root}->{systemroot} || "/windows";
431     my $winfile_before = "$systemroot/system32/config/$hivename";
432     my $winfile;
433     eval { $winfile = $g->case_sensitive_path ($winfile_before); };
434     if ($@) {
435         die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
436                 p => $winfile_before, err => $@);
437     }
438
439     warn "uploading $winfile ..." if $debug;
440     eval { $g->upload ("$tmpdir/$hivename", $winfile); };
441     if ($@) {
442         die __x("virt-win-reg: {p}: could not upload registry file: {err}\n",
443                 p => $winfile, err => $@);
444     }
445 }
446
447 =head1 SEE ALSO
448
449 L<hivex(3)>,
450 L<hivexsh(1)>,
451 L<hivexregedit(1)>,
452 L<guestfs(3)>,
453 L<guestfish(1)>,
454 L<virt-cat(1)>,
455 L<Sys::Guestfs(3)>,
456 L<Sys::Guestfs::Lib(3)>,
457 L<Win::Hivex(3)>,
458 L<Win::Hivex::Regedit(3)>,
459 L<Sys::Virt(3)>,
460 L<http://libguestfs.org/>.
461
462 =head1 BUGS
463
464 When reporting bugs, please enable debugging and capture the
465 I<complete> output:
466
467  export LIBGUESTFS_DEBUG=1
468  virt-win-reg --debug [... rest ...] > /tmp/virt-win-reg.log 2>&1
469
470 Attach /tmp/virt-win-reg.log to a new bug report at
471 L<https://bugzilla.redhat.com/>
472
473 =head1 AUTHOR
474
475 Richard W.M. Jones L<http://et.redhat.com/~rjones/>
476
477 =head1 COPYRIGHT
478
479 Copyright (C) 2010 Red Hat Inc.
480
481 This program is free software; you can redistribute it and/or modify
482 it under the terms of the GNU General Public License as published by
483 the Free Software Foundation; either version 2 of the License, or
484 (at your option) any later version.
485
486 This program is distributed in the hope that it will be useful,
487 but WITHOUT ANY WARRANTY; without even the implied warranty of
488 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
489 GNU General Public License for more details.
490
491 You should have received a copy of the GNU General Public License
492 along with this program; if not, write to the Free Software
493 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.