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<URI>. If omitted, then we
connect to the default libvirt hypervisor.
# 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.
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;
+ 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: $?"
}
# 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.
{
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
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 => $@);
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 => $@);
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>.
+The following Registry keys are supported:
+
+=over 4
+
+=item C<HKEY_LOCAL_MACHINE\SAM>
+
+=item C<HKEY_LOCAL_MACHINE\SECURITY>
+
+=item C<HKEY_LOCAL_MACHINE\SOFTWARE>
+
+=item C<HKEY_LOCAL_MACHINE\SYSTEM>
+
+=item C<HKEY_USERS\.DEFAULT>
+
+=item C<HKEY_USERS\I<SID>>
+
+where I<SID> is a Windows User SID (eg. C<S-1-5-18>).
+
+=item C<HKEY_USERS\I<username>>
+
+where I<username> is a local user name (this is a libguestfs extension).
+
+=back
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.
+The literal keys C<HKEY_USERS\$SID> and C<HKEY_CURRENT_USER> are not
+supported (there is no "current user").
=head1 ENCODING