mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
494 lines
14 KiB
Perl
Executable File
494 lines
14 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
||
# virt-win-reg
|
||
# 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
|
||
# the Free Software Foundation; either version 2 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program; if not, write to the Free Software
|
||
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||
|
||
use warnings;
|
||
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 Win::Hivex;
|
||
use Win::Hivex::Regedit qw(reg_import reg_export);
|
||
|
||
use Pod::Usage;
|
||
use Getopt::Long;
|
||
use File::Temp qw/tempdir/;
|
||
use Locale::TextDomain 'libguestfs';
|
||
|
||
=encoding utf8
|
||
|
||
=head1 NAME
|
||
|
||
virt-win-reg - Export and merge Windows Registry entries from a Windows guest
|
||
|
||
=head1 SYNOPSIS
|
||
|
||
virt-win-reg domname 'HKLM\Path\To\Subkey'
|
||
|
||
virt-win-reg domname 'HKLM\Path\To\Subkey' name
|
||
|
||
virt-win-reg domname 'HKLM\Path\To\Subkey' @
|
||
|
||
virt-win-reg --merge domname [input.reg ...]
|
||
|
||
virt-win-reg [--options] disk.img ... # instead of domname
|
||
|
||
=head1 WARNING
|
||
|
||
You must I<not> use C<virt-win-reg> with the C<--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.
|
||
|
||
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 C<--merge>
|
||
option, make sure you have a reliable backup first.
|
||
|
||
=head1 DESCRIPTION
|
||
|
||
This program can export and merge Windows Registry entries from a
|
||
Windows guest.
|
||
|
||
The first parameter is the libvirt guest name or the raw disk image of
|
||
a Windows guest.
|
||
|
||
If C<--merge> is I<not> specified, then the chosen registry
|
||
key is displayed/exported (recursively). For example:
|
||
|
||
$ virt-win-reg Windows7 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft'
|
||
|
||
You can also display single values from within registry keys,
|
||
for example:
|
||
|
||
$ cvkey='HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
|
||
$ virt-win-reg Windows7 $cvkey ProductName
|
||
Windows 7 Enterprise
|
||
|
||
With C<--merge>, you can merge a textual regedit file into
|
||
the Windows Registry:
|
||
|
||
$ virt-win-reg --merge Windows7 changes.reg
|
||
|
||
=head2 SUPPORTED SYSTEMS
|
||
|
||
The program currently supports Windows NT-derived guests starting with
|
||
Windows XP through to at least Windows 7.
|
||
|
||
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>.
|
||
|
||
You can use C<HKLM> as a shorthand for C<HKEY_LOCAL_MACHINE>, and
|
||
C<HKU> for C<HKEY_USERS>.
|
||
|
||
C<HKEY_USERS\$SID> and C<HKEY_CURRENT_USER> are B<not> supported at
|
||
this time.
|
||
|
||
=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 L<libguestfs(3)>
|
||
or L<guestfish(1)> and access them locally, eg. using L<hivex(3)>,
|
||
L<hivexsh(1)> or L<hivexregedit(1)>.
|
||
|
||
=head2 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 C<--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 C<--merge> it.
|
||
|
||
=head2 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.
|
||
|
||
=head2 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. Replace this with C<ControlSet001>, and
|
||
similarly for other C<Current...> keys.
|
||
|
||
=head1 OPTIONS
|
||
|
||
=over 4
|
||
|
||
=cut
|
||
|
||
my $help;
|
||
|
||
=item B<--help>
|
||
|
||
Display brief help.
|
||
|
||
=cut
|
||
|
||
my $version;
|
||
|
||
=item B<--version>
|
||
|
||
Display version number and exit.
|
||
|
||
=cut
|
||
|
||
my $debug;
|
||
|
||
=item B<--debug>
|
||
|
||
Enable debugging messages.
|
||
|
||
=cut
|
||
|
||
my $uri;
|
||
|
||
=item B<--connect URI> | B<-c 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 $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 C<--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.
|
||
|
||
=back
|
||
|
||
=cut
|
||
|
||
GetOptions ("help|?" => \$help,
|
||
"version" => \$version,
|
||
"connect|c=s" => \$uri,
|
||
"debug|d" => \$debug,
|
||
"merge" => \$merge,
|
||
"encoding=s" => \$encoding,
|
||
) or pod2usage (2);
|
||
pod2usage (1) if $help;
|
||
if ($version) {
|
||
my $g = Sys::Guestfs->new ();
|
||
my %h = $g->version ();
|
||
print "$h{major}.$h{minor}.$h{release}$h{extra}\n";
|
||
exit
|
||
}
|
||
|
||
# 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;
|
||
|
||
warn "launching libguestfs ..." if $debug;
|
||
|
||
my @lib_args = ([$domname_or_image]);
|
||
push @lib_args, address => $uri if $uri;
|
||
push @lib_args, rw => 1 if $merge;
|
||
my $g = open_guest (@lib_args);
|
||
$g->launch ();
|
||
|
||
warn "inspecting guest ..." if $debug;
|
||
|
||
# List of possible filesystems.
|
||
my @partitions = get_partitions ($g);
|
||
|
||
# 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);
|
||
|
||
my $oses = inspect_operating_systems ($g, \%fses);
|
||
|
||
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];
|
||
|
||
my $os = $oses->{$root_dev};
|
||
my $ro = $merge ? 0 : 1;
|
||
mount_operating_system ($g, $os, $ro);
|
||
|
||
# Create a working directory to store the downloaded registry files.
|
||
my $tmpdir = tempdir (CLEANUP => 1);
|
||
|
||
# Only used when merging to map downloaded hive names to hive handles.
|
||
my %hives;
|
||
|
||
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 ($hivename, $prefix);
|
||
($hivename, $path, $prefix) = map_path_to_hive ($path);
|
||
|
||
# Download the chosen hive.
|
||
download_hive ($hivename);
|
||
|
||
# Open it.
|
||
my $h = Win::Hivex->open ("$tmpdir/$hivename", debug => $debug);
|
||
|
||
unless ($name) {
|
||
# Export it.
|
||
warn "exporting $path from $hivename with prefix $prefix ..." if $debug;
|
||
reg_export ($h, $path, \*STDOUT, prefix => $prefix);
|
||
} else {
|
||
# Export a single key using hivexget.
|
||
my @args = ("hivexget", "$tmpdir/$hivename", $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);
|
||
}
|
||
}
|
||
|
||
# Now we've done importing, commit all the hive handles and
|
||
# close them all.
|
||
$_->commit (undef) foreach values %hives;
|
||
%hives = ();
|
||
|
||
# Look in the tmpdir for all the hive files which have been
|
||
# downloaded / modified by the import mapper, and upload
|
||
# each one.
|
||
opendir my $dh, $tmpdir or die "$tmpdir: $!";
|
||
foreach (readdir $dh) {
|
||
unless (/^\./) {
|
||
upload_hive ($_)
|
||
}
|
||
}
|
||
|
||
# Sync everything.
|
||
$g->umount_all ();
|
||
$g->sync ();
|
||
}
|
||
|
||
exit 0;
|
||
|
||
# map function passed to reg_import.
|
||
sub import_mapper
|
||
{
|
||
local $_ = shift;
|
||
|
||
my ($hivename, $path, $prefix) = map_path_to_hive ($_);
|
||
|
||
# Need to download this hive?
|
||
unless (-f "$tmpdir/$hivename") {
|
||
download_hive ($hivename);
|
||
|
||
my $h = Win::Hivex->open ("$tmpdir/$hivename",
|
||
write => 1, debug => $debug);
|
||
$hives{$hivename} = $h;
|
||
}
|
||
|
||
return ($hives{$hivename}, $path);
|
||
}
|
||
|
||
# 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 ($hivename, $prefix);
|
||
|
||
if (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SAM(\\.*)?$/i) {
|
||
$hivename = "sam";
|
||
$_ = defined $1 ? $1 : "\\";
|
||
$prefix = "HKEY_LOCAL_MACHINE\\SAM";
|
||
}
|
||
elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SECURITY(\\.*)?$/i) {
|
||
$hivename = "security";
|
||
$_ = defined $1 ? $1 : "\\";
|
||
$prefix = "HKEY_LOCAL_MACHINE\\SECURITY";
|
||
}
|
||
elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SOFTWARE(\\.*)?$/i) {
|
||
$hivename = "software";
|
||
$_ = defined $1 ? $1 : "\\";
|
||
$prefix = "HKEY_LOCAL_MACHINE\\SOFTWARE";
|
||
}
|
||
elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SYSTEM(\\.*)?$/i) {
|
||
$hivename = "system";
|
||
$_ = defined $1 ? $1 : "\\";
|
||
$prefix = "HKEY_LOCAL_MACHINE\\SYSTEM";
|
||
}
|
||
elsif (/^\\?(?:HKEY_USERS|HKU)\\.DEFAULT(\\.*)?$/i) {
|
||
$hivename = "default";
|
||
$_ = defined $1 ? $1 : "\\";
|
||
$prefix = "HKEY_LOCAL_MACHINE\\.DEFAULT";
|
||
}
|
||
else {
|
||
die __x("virt-win-reg: {p}: not a supported Windows Registry path\n",
|
||
p => $_)
|
||
}
|
||
|
||
return ($hivename, $_, $prefix);
|
||
}
|
||
|
||
# Download a named hive file. Die on failure.
|
||
sub download_hive
|
||
{
|
||
local $_;
|
||
my $hivename = shift;
|
||
|
||
my $systemroot = $os->{root}->{systemroot} || "/windows";
|
||
my $winfile_before = "$systemroot/system32/config/$hivename";
|
||
my $winfile;
|
||
eval { $winfile = $g->case_sensitive_path ($winfile_before); };
|
||
if ($@) {
|
||
die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
|
||
p => $winfile_before, err => $@);
|
||
}
|
||
|
||
warn "downloading $winfile ..." if $debug;
|
||
eval { $g->download ($winfile, "$tmpdir/$hivename"); };
|
||
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 $hivename = shift;
|
||
|
||
my $systemroot = $os->{root}->{systemroot} || "/windows";
|
||
my $winfile_before = "$systemroot/system32/config/$hivename";
|
||
my $winfile;
|
||
eval { $winfile = $g->case_sensitive_path ($winfile_before); };
|
||
if ($@) {
|
||
die __x("virt-win-reg: {p}: file not found in guest: {err}\n",
|
||
p => $winfile_before, err => $@);
|
||
}
|
||
|
||
warn "uploading $winfile ..." if $debug;
|
||
eval { $g->upload ("$tmpdir/$hivename", $winfile); };
|
||
if ($@) {
|
||
die __x("virt-win-reg: {p}: could not upload registry file: {err}\n",
|
||
p => $winfile, err => $@);
|
||
}
|
||
}
|
||
|
||
=head1 SEE ALSO
|
||
|
||
L<hivex(3)>,
|
||
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/>
|
||
|
||
=head1 COPYRIGHT
|
||
|
||
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
|
||
the Free Software Foundation; either version 2 of the License, or
|
||
(at your option) any later version.
|
||
|
||
This program is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with this program; if not, write to the Free Software
|
||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|