mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
619 lines
18 KiB
Perl
Executable File
619 lines
18 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);
|
|
use Win::Hivex;
|
|
use Win::Hivex::Regedit qw(reg_import reg_export);
|
|
|
|
use Pod::Usage;
|
|
use Getopt::Long;
|
|
use File::Temp qw/tempdir/;
|
|
use File::Basename;
|
|
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 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)>.
|
|
|
|
=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 $format;
|
|
|
|
=item B<--format> raw
|
|
|
|
Specify the format of disk images given on the command line. If this
|
|
is omitted then the format is autodetected from the content of the
|
|
disk image.
|
|
|
|
If disk images are requested from libvirt, then this program asks
|
|
libvirt for this information. In this case, the value of the format
|
|
parameter is ignored.
|
|
|
|
If working with untrusted raw-format guest disk images, you should
|
|
ensure the format is always specified.
|
|
|
|
=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,
|
|
"format=s" => \$format,
|
|
"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;
|
|
push @lib_args, format => $format if defined $format;
|
|
my $g = open_guest (@lib_args);
|
|
$g->launch ();
|
|
|
|
warn "inspecting guest ..." if $debug;
|
|
|
|
my @roots = $g->inspect_os ();
|
|
if (@roots == 0) {
|
|
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",
|
|
prog => basename ($0));
|
|
}
|
|
if (@roots > 1) {
|
|
die __x("{prog}: multiboot operating systems are not supported.\n",
|
|
prog => basename ($0))
|
|
}
|
|
my %fses = $g->inspect_get_mountpoints ($roots[0]);
|
|
my @fses = sort { length $a <=> length $b } keys %fses;
|
|
my $mountopts = $merge ? "" : "ro";
|
|
foreach (@fses) {
|
|
$g->mount_options ($mountopts, $fses{$_}, $_);
|
|
}
|
|
|
|
my $systemroot = $g->inspect_get_windows_systemroot ($roots[0]);
|
|
|
|
# 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 $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 $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 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.
|
|
|
|
=head1 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.
|
|
|
|
=head1 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.
|
|
|
|
C<CurrentControlSet> is usually an alias for C<ControlSet001>. In
|
|
some circumstances it might refer to another control set. The way
|
|
to find out is to look at the C<HKLM\SYSTEM\Select> key:
|
|
|
|
# virt-win-reg WindowsGuest 'HKLM\SYSTEM\Select'
|
|
[HKEY_LOCAL_MACHINE\SYSTEM\Select]
|
|
"Current"=dword:00000001
|
|
"Default"=dword:00000001
|
|
"Failed"=dword:00000000
|
|
"LastKnownGood"=dword:00000002
|
|
|
|
"Current" is the one which Windows will choose when it boots.
|
|
|
|
Similarly, other C<Current...> keys in the path may need to
|
|
be replaced.
|
|
|
|
=head1 WINDOWS TIPS
|
|
|
|
Note that some of these tips modify the guest disk image. The guest
|
|
I<must> be shut off, else you will get disk corruption.
|
|
|
|
=head2 RUNNING A BATCH SCRIPT WHEN A USER LOGS IN
|
|
|
|
Prepare a DOS batch script, VBScript or executable. Upload this using
|
|
L<guestfish(1)>. For this example the script is called C<test.bat>
|
|
and it is uploaded into C<C:\>:
|
|
|
|
guestfish -i -d WindowsGuest upload test.bat /test.bat
|
|
|
|
Prepare a regedit file containing the registry change:
|
|
|
|
cat > test.reg <<'EOF'
|
|
[HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce]
|
|
"Test"="c:\\test.bat"
|
|
EOF
|
|
|
|
In this example we use the key C<RunOnce> which means that the script
|
|
will run precisely once when the first user logs in. If you want it
|
|
to run every time a user logs in, replace C<RunOnce> with C<Run>.
|
|
|
|
Now update the registry:
|
|
|
|
virt-win-reg --merge WindowsGuest test.reg
|
|
|
|
=head2 INSTALLING A SERVICE
|
|
|
|
This section assumes you are familiar with Windows services, and you
|
|
either have a program which handles the Windows Service Control
|
|
Protocol directly or you want to run any program using a service
|
|
wrapper like SrvAny or the free RHSrvAny.
|
|
|
|
First upload the program and optionally the service wrapper. In this
|
|
case the test program is called C<test.exe> and we are using the
|
|
RHSrvAny wrapper:
|
|
|
|
guestfish -i -d WindowsGuest <<EOF
|
|
upload rhsrvany.exe /rhsrvany.exe
|
|
upload test.exe /test.exe
|
|
EOF
|
|
|
|
Prepare a regedit file containing the registry changes. In this
|
|
example, the first registry change is needed for the service itself or
|
|
the service wrapper (if used). The second registry change is only
|
|
needed because I am using the RHSrvAny service wrapper.
|
|
|
|
cat > service.reg <<'EOF'
|
|
[HKLM\SYSTEM\ControlSet001\services\RHSrvAny]
|
|
"Type"=dword:00000010
|
|
"Start"=dword:00000002
|
|
"ErrorControl"=dword:00000001
|
|
"ImagePath"="c:\\rhsrvany.exe"
|
|
"DisplayName"="RHSrvAny"
|
|
"ObjectName"="NetworkService"
|
|
|
|
[HKLM\SYSTEM\ControlSet001\services\RHSrvAny\Parameters]
|
|
"CommandLine"="c:\\test.exe"
|
|
"PWD"="c:\\Temp"
|
|
EOF
|
|
|
|
Notes:
|
|
|
|
=over 4
|
|
|
|
=item *
|
|
|
|
For use of C<ControlSet001> see the section above in this manual page.
|
|
You may need to adjust this according to the control set that is in
|
|
use by the guest.
|
|
|
|
=item *
|
|
|
|
C<"ObjectName"> controls the privileges that the service will have.
|
|
An alternative is C<"ObjectName"="LocalSystem"> which would be the
|
|
most privileged account.
|
|
|
|
=item *
|
|
|
|
For the meaning of the magic numbers, see this Microsoft KB article:
|
|
L<http://support.microsoft.com/kb/103000>.
|
|
|
|
=back
|
|
|
|
Update the registry:
|
|
|
|
virt-win-reg --merge WindowsGuest service.reg
|
|
|
|
=head1 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.
|
|
|
|
Libvirt guest names can contain arbitrary characters, some of which
|
|
have meaning to the shell such as C<#> and space. You may need to
|
|
quote or escape these characters on the command line. See the shell
|
|
manual page L<sh(1)> for details.
|
|
|
|
=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://people.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.
|