X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=tools%2Fvirt-win-reg;h=ab5daa7174442069f923d227e5d138e1a1a677f1;hp=695e619beb547cdb8a030abe1faaffcc91f1b40b;hb=cd077b8229731e292798f34dd56892cbfa6f1e0e;hpb=c49fc3831d12788c27b90d12f06a1cd69a88e3be diff --git a/tools/virt-win-reg b/tools/virt-win-reg index 695e619..ab5daa7 100755 --- a/tools/virt-win-reg +++ b/tools/virt-win-reg @@ -14,7 +14,7 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. use warnings; use strict; @@ -125,7 +125,9 @@ Enable debugging messages. my $uri; -=item B<--connect URI> | B<-c URI> +=item B<-c URI> + +=item B<--connect URI> If using libvirt, connect to the given I. If omitted, then we connect to the default libvirt hypervisor. @@ -177,6 +179,47 @@ L. The default is to use UTF-16LE, which should work with recent versions of Windows. +=cut + +my $unsafe_printable_strings; + +=item B<--unsafe-printable-strings> + +When exporting (only), assume strings are UTF-16LE and print them as +strings instead of hex sequences. Remove the final zero codepoint +from strings if present. + +This is unsafe and does not preserve the fidelity of strings in the +original Registry for various reasons: + +=over 4 + +=item * + +Assumes the original encoding is UTF-16LE. ASCII strings and strings +in other encodings will be corrupted by this transformation. + +=item * + +Assumes that everything which has type 1 or 2 is really a string +and that everything else is not a string, but the type field in +real Registries is not reliable. + +=item * + +Loses information about whether a zero codepoint followed the string +in the Registry or not. + +=back + +This all happens because the Registry itself contains no information +about how strings are encoded (see +L). + +You should only use this option for quick hacking and debugging of the +Registry contents, and I use it if the output is going to be +passed into another program or stored in another Registry. + =back =cut @@ -188,6 +231,7 @@ GetOptions ("help|?" => \$help, "format=s" => \$format, "merge" => \$merge, "encoding=s" => \$encoding, + "unsafe-printable-strings" => \$unsafe_printable_strings, ) or pod2usage (2); pod2usage (1) if $help; if ($version) { @@ -233,7 +277,10 @@ 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. +# Used when merging (only) to map from the downloaded hiveshortname to +# various properties about the hive. The key is hiveshortname. The +# value is a hashref containing {h} (hive handle) and {hivefile} (full +# hive path on the Windows side). my %hives; if (!$merge) { # Export mode. @@ -244,22 +291,25 @@ if (!$merge) { # Export mode. 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); + my ($hiveshortname, $hivefile, $prefix); + ($hiveshortname, $hivefile, $path, $prefix) = map_path_to_hive ($path); # Download the chosen hive. - download_hive ($hivename); + download_hive ($hivefile, $hiveshortname); # Open it. - my $h = Win::Hivex->open ("$tmpdir/$hivename", debug => $debug); + my $h = Win::Hivex->open ("$tmpdir/$hiveshortname", debug => $debug); unless ($name) { # Export it. - warn "exporting $path from $hivename with prefix $prefix ..." if $debug; - reg_export ($h, $path, \*STDOUT, prefix => $prefix); + warn "exporting $path from $hiveshortname with prefix $prefix ..." + if $debug; + reg_export ($h, $path, \*STDOUT, + prefix => $prefix, + unsafe_printable_strings => $unsafe_printable_strings); } else { # Export a single key using hivexget. - my @args = ("hivexget", "$tmpdir/$hivename", $path, $name); + my @args = ("hivexget", "$tmpdir/$hiveshortname", $path, $name); warn "running ", join (" ", @args), " ..." if $debug; system (@args) == 0 or die "hivexget failed: $?" } @@ -276,17 +326,15 @@ else { # Import mode. # 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 ($_) - } + foreach (values %hives) { + my $h = $_->{h}; + delete $_->{h}; + $h->commit (undef); + } + + # Upload all the downloaded hives. + foreach my $hiveshortname (keys %hives) { + upload_hive ($hiveshortname, $hives{$hiveshortname}->{hivefile}) } # Sync everything. @@ -301,18 +349,19 @@ sub import_mapper { local $_ = shift; - my ($hivename, $path, $prefix) = map_path_to_hive ($_); + my ($hiveshortname, $hivefile, $path, $prefix) = map_path_to_hive ($_); # Need to download this hive? - unless (-f "$tmpdir/$hivename") { - download_hive ($hivename); + unless (-f "$tmpdir/$hiveshortname") { + download_hive ($hivefile, $hiveshortname); - my $h = Win::Hivex->open ("$tmpdir/$hivename", + my $h = Win::Hivex->open ("$tmpdir/$hiveshortname", write => 1, debug => $debug); - $hives{$hivename} = $h; + my %hash = ( h => $h, hivefile => $hivefile ); + $hives{$hiveshortname} = \%hash; } - return ($hives{$hivename}, $path); + return ($hives{$hiveshortname}->{h}, $path); } # Given a path, map that to the name of the hive and the true path @@ -320,57 +369,162 @@ sub import_mapper sub map_path_to_hive { local $_ = shift; - my ($hivename, $prefix); + my ($hiveshortname, $hivefile, $path, $prefix); if (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SAM(\\.*)?$/i) { - $hivename = "sam"; - $_ = defined $1 ? $1 : "\\"; + $hiveshortname = "sam"; + $hivefile = "$systemroot/system32/config/$hiveshortname"; + $path = defined $1 ? $1 : "\\"; $prefix = "HKEY_LOCAL_MACHINE\\SAM"; } elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SECURITY(\\.*)?$/i) { - $hivename = "security"; - $_ = defined $1 ? $1 : "\\"; + $hiveshortname = "security"; + $hivefile = "$systemroot/system32/config/$hiveshortname"; + $path = defined $1 ? $1 : "\\"; $prefix = "HKEY_LOCAL_MACHINE\\SECURITY"; } elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SOFTWARE(\\.*)?$/i) { - $hivename = "software"; - $_ = defined $1 ? $1 : "\\"; + $hiveshortname = "software"; + $hivefile = "$systemroot/system32/config/$hiveshortname"; + $path = defined $1 ? $1 : "\\"; $prefix = "HKEY_LOCAL_MACHINE\\SOFTWARE"; } elsif (/^\\?(?:HKEY_LOCAL_MACHINE|HKLM)\\SYSTEM(\\.*)?$/i) { - $hivename = "system"; - $_ = defined $1 ? $1 : "\\"; + $hiveshortname = "system"; + $hivefile = "$systemroot/system32/config/$hiveshortname"; + $path = defined $1 ? $1 : "\\"; $prefix = "HKEY_LOCAL_MACHINE\\SYSTEM"; } elsif (/^\\?(?:HKEY_USERS|HKU)\\.DEFAULT(\\.*)?$/i) { - $hivename = "default"; - $_ = defined $1 ? $1 : "\\"; + $hiveshortname = "default"; + $hivefile = "$systemroot/system32/config/$hiveshortname"; + $path = defined $1 ? $1 : "\\"; $prefix = "HKEY_LOCAL_MACHINE\\.DEFAULT"; } + elsif (/^\\?(?:HKEY_USERS|HKU)\\(S-1-5-[-\d]+)(\\.*)?$/i) { + my $sid = $1; + $hiveshortname = $sid; + $prefix = "HKEY_USERS\\$sid"; + $path = defined $2 ? $2 : "\\"; + # This requires a recursive call to download the SOFTWARE hive. + $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat"; + } + elsif (/^\\?(?:HKEY_USERS|HKU)\\LocalSystem(\\.*)?$/i) { + my $sid = "S-1-5-18"; + $hiveshortname = $sid; + $prefix = "HKEY_USERS\\$sid"; + $path = defined $1 ? $1 : "\\"; + # This requires a recursive call to download the SOFTWARE hive. + $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat"; + } + elsif (/^\\?(?:HKEY_USERS|HKU)\\LocalService(\\.*)?$/i) { + my $sid = "S-1-5-19"; + $hiveshortname = $sid; + $prefix = "HKEY_USERS\\$sid"; + $path = defined $1 ? $1 : "\\"; + # This requires a recursive call to download the SOFTWARE hive. + $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat"; + } + elsif (/^\\?(?:HKEY_USERS|HKU)\\NetworkService(\\.*)?$/i) { + my $sid = "S-1-5-20"; + $hiveshortname = $sid; + $prefix = "HKEY_USERS\\$sid"; + $path = defined $1 ? $1 : "\\"; + # This requires a recursive call to download the SOFTWARE hive. + $hivefile = lookup_pip_of_user_sid ($sid) . "/ntuser.dat"; + } + elsif (/^\\?(?:HKEY_USERS|HKU)\\(.*?)(\\.*)?$/i) { + $hiveshortname = "user_$1"; + $prefix = "HKEY_USERS\\$1"; + $path = defined $2 ? $2 : "\\"; + # XXX We should probably look this up properly. + if (is_dir_nocase ("/Users/$1")) { + $hivefile = "/Users/$1/ntuser.dat" + } elsif (is_dir_nocase ("/Documents and Settings/$1")) { + $hivefile = "/Documents and Settings/$1/ntuser.dat" + } else { + die __x("virt-win-reg: {p}: cannot find user directory\n", + p => $1) + } + } else { die __x("virt-win-reg: {p}: not a supported Windows Registry path\n", p => $_) } - return ($hivename, $_, $prefix); + return ($hiveshortname, $hivefile, $path, $prefix); +} + +# Given a User SID, consult +# HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid +# and return the ProfileImagePath value. +sub lookup_pip_of_user_sid +{ + local $_; + my $sid = shift; + + my $path = + "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\". + $sid; + + my ($hiveshortname, $hivefile, $prefix); + ($hiveshortname, $hivefile, $path, $prefix) = map_path_to_hive ($path); + + download_hive ($hivefile, $hiveshortname) + unless -f "$tmpdir/$hiveshortname"; + + my @args = ("$tmpdir/$hiveshortname", $path, "ProfileImagePath"); + warn "running hivexget ", join (" ", @args), " ..." if $debug; + + my $fh; + open $fh, "-|", "hivexget", @args + or die "hivexget: see earlier errors: $!"; + $_ = <$fh>; + close $fh or die "hivexget: see earlier errors: $!"; + + chomp; + + # The contents of the registry are a windows path, possibly + # containing %systemroot% and %systemdrive% (on Win XP). Expand + # it and remove some other windows-isms. The caller will do + # case_sensitive_path for us, so we don't need to do that. + s/%systemroot%/$systemroot/i; + s/%systemdrive%//i; + s/^c://i; + s,\\,/,g; + + $_; +} + +sub is_dir_nocase +{ + local $_; + my $dir = shift; + + my $windir; + eval { $windir = $g->case_sensitive_path ($dir); }; + if ($@) { + return 0; + } + return $g->is_dir ($windir); } # Download a named hive file. Die on failure. sub download_hive { local $_; - my $hivename = shift; + my $hivefile = shift; + my $hiveshortname = shift; - my $winfile_before = "$systemroot/system32/config/$hivename"; my $winfile; - eval { $winfile = $g->case_sensitive_path ($winfile_before); }; + eval { $winfile = $g->case_sensitive_path ($hivefile); }; if ($@) { die __x("virt-win-reg: {p}: file not found in guest: {err}\n", - p => $winfile_before, err => $@); + p => $hivefile, err => $@); } warn "downloading $winfile ..." if $debug; - eval { $g->download ($winfile, "$tmpdir/$hivename"); }; + eval { $g->download ($winfile, "$tmpdir/$hiveshortname"); }; if ($@) { die __x("virt-win-reg: {p}: could not download registry file: {err}\n", p => $winfile, err => $@); @@ -381,18 +535,18 @@ sub download_hive sub upload_hive { local $_; - my $hivename = shift; + my $hiveshortname = shift; + my $hivefile = shift; - my $winfile_before = "$systemroot/system32/config/$hivename"; my $winfile; - eval { $winfile = $g->case_sensitive_path ($winfile_before); }; + eval { $winfile = $g->case_sensitive_path ($hivefile); }; if ($@) { die __x("virt-win-reg: {p}: file not found in guest: {err}\n", - p => $winfile_before, err => $@); + p => $hivefile, err => $@); } warn "uploading $winfile ..." if $debug; - eval { $g->upload ("$tmpdir/$hivename", $winfile); }; + eval { $g->upload ("$tmpdir/$hiveshortname", $winfile); }; if ($@) { die __x("virt-win-reg: {p}: could not upload registry file: {err}\n", p => $winfile, err => $@); @@ -404,15 +558,35 @@ sub upload_hive The program currently supports Windows NT-derived guests starting with Windows XP through to at least Windows 7. -Registry support is done for C, -C, C, -C and C. +The following Registry keys are supported: + +=over 4 + +=item C + +=item C + +=item C + +=item C + +=item C + +=item C> + +where I is a Windows User SID (eg. C). + +=item C> + +where I is a local user name (this is a libguestfs extension). + +=back You can use C as a shorthand for C, and C for C. -C and C are B supported at -this time. +The literal keys C and C are not +supported (there is no "current user"). =head1 ENCODING @@ -466,6 +640,17 @@ to find out is to look at the C key: Similarly, other C keys in the path may need to be replaced. +=head1 DELETING REGISTRY KEYS AND VALUES + +To delete a whole registry key, use the syntax: + + [-HKEY_LOCAL_MACHINE\Foo] + +To delete a single value within a key, use the syntax: + + [HKEY_LOCAL_MACHINE\Foo] + "Value"=- + =head1 WINDOWS TIPS Note that some of these tips modify the guest disk image. The guest @@ -615,4 +800,4 @@ 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. +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.