| Welcome to the Zimbra - Forums! | |
Welcome, if you would like to post a comment please register.
We also encourage you to explore all things Zimbra with our team and members of the community.
|  | 
10-23-2006, 08:47 AM
| | Partner (VAR/HSP) | |
Posts: 71
| | Restore mail to another user's mailbox I have a situation where a user account left the company, but management wants that user's email to be placed in a subfolder of another mailbox. For example, We want the contents of jsmith's Zimbra mailbox to be placed into a folder named jsmith in jdoe's account.
Is there a good way of going about this?
I thought restoring from Backups into jdoe's folder would work, but apparently you can only restore into either the original mailbox or into a new one. The only route I can think of is to log into the old user's mailbox with the Exchange connector (or IMAP client or some such), do a full export, then log into the destination user's mailbox and restore into a folder. It just seems like there should be a quicker way of doing it | 
10-23-2006, 09:42 AM
| | Zimbra Employee | |
Posts: 1,434
| | restore & imapsync Restore into a new mailbox, then use imapsync to copy the restored messages to the existing user's subdirectory? I believe that imapsync supports a target folder... | 
10-23-2006, 09:44 AM
| | Elite Member & Volunteer | |
Posts: 255
| | I'm not sure of the backup/restore option, but IMAP sync may be a good way to go. | 
01-16-2008, 03:35 PM
| | | I ran into a situation where a service recipient had deleted their Zimbra sent folder. Zimbra NE allows me to restore the user’s account to another account (restored_username) just fine but then what do I do to get the mail folder over to the other (production) account?
As suggested, imapsync is one option. It won't do tags though.
To fix my problem, I wrote something in perl to copy entire folders to another account. It's limited by a zmmailbox limitation of 1000 messages but you can use the move option and run it more than once. It's also a little slow so I implemented forking to do some of the grunt work in parallel.
For what it's worth, here is how I am copying folders to other mailboxes: Code: #!/usr/bin/perl
# Name: zimbracopyfolder
# Purpose: The original intent was to help users out when they cry
# "Help! I deleted all of my Sent items!" and the like.
# Version: 0.88 (alpha)
# Author: Troy Adams (troy { at } troyadams { dot } ca)
# Date: Fri Dec 21 12:50:33 MST 2007
# License: GPL-3
# Usage: zimbracopyfolder --help
# Resources: http://www.zimbra.com/forums/administrators/10393-solved-how-use-zmmailbox-show-original.html
# http://www.zimbra.com/forums/administrators/5065-restore-mail-another-users-mailbox.html
# http://blog.troyadams.ca/?p=3
# Warning: No warranty! This is program is for academic analysis only.
# Read the code yourself!
#
# Example:
# Step 1) restore from zimbra backup
# # zmrestore --createAccount --prefix restored_ \
# --restoreFullBackupOnly --account test@mydomain.ca
# Step 2) copy the required folder from the restored_ account to the production account:
# # zimbracopyfolder --source restored_test@mydomain.ca:/Sent \
# --destination test@mydomain.ca:/restore_Sent \
# --noverify --verbose --forks 16
# Limitations:
# It seems that, due to a limitation of zmmailbox, this program can
# only handle folders with a maximum of 1000 messages.
#
# Log: zimbracopyfolder,v
#
# Revision 1.1 2007/10/29 15:39:21 troy
# Initial revision
#
# benchmarks (tested in the Athabasca University data center):
# (1) 19 minutes for 129 messages with NUM_FORKS=8; 6.78 msgs/minute
# (2) 43 minutes for 185 messages with NUM_FORKS=16; 4.30 msgs/minute
# (3) 6 minutes for 36 messages with NUM_FORKS=1; 6 msgs/minute
# bypass extra shell call to $zmMAILBOX by calling java direct
# (4) 5m08s for 29 messages with NUM_FORKS=1; 5.64 msgs/minute
# (5) 1m59s for 16 messages with NUM_FORKS=1; 8.07 msgs/minute
# (6) 1m50s for 16 messages with NUM_FORKS=4; 8.73 msgs/minute
# (7) 1m45s for 16 messages with NUM_FORKS=8; 9.14 msgs/minute
# (8) 1m20s for 16 messages with NUM_FORKS=16; 12.0 msgs/minute
# (9) 3m08s for 32 messages with NUM_FORKS=16; 10.2 msgs/minute
# back to calling /opt/zimbra/bin/zmmailbox (no more bypass)
# (10) 3m08s for 32 messages with NUM_FORKS=16; 10.2 msgs/minute
# best we have so far is 12.0 msgs/minute; that isn't too good!
# I modified the thread queue handler to shift off the top instead of
# pop off the bottom - silly me. Now we get:
# (11) 3m36s for 49 messages with NUM_FORKS=16; 13.6 msgs/minute
#
# Plan:
# (1) aquire and verify cmd-line args such as source and destination
# (2) split out and verify src/dst components
# (3) enumerate source target contents
# (4) construct destination target
# (5) test destination target for content
# (6) export->import full messages
# (7) test destination target again for content
# Errors/features that we don't yet code for:
# (1) ERROR: zclient.CLIENT_ERROR (unknown tag: 71)
# We need to build support for on-the-fly tag creation?
# (2) we can only do 1000 msgs at a time; to do a folder with
# more than 1000 msgs then just use the --move option
# and keep executing until all the msgs are moved.
# (3) the hostname portion of the source and destination are
# for future implementation of cross-host copying
#
use Env;
our $id=0; # don't think we need this globally
my $NUM_FORKS=1;
my $preservetags=0;
my $i = 0;
my $TEMP_FILE = "/tmp/zimbracopyfoldertmp.$$";
my $TEMP_FILE2 = "/tmp/zimbracopyfoldertmp-metadata.$$";
my $EXEC_FILE = "/tmp/zimbracopyfolder.execute.$$";
my $MAX_SEARCH = 1000; # seems like $zmMAILBOX will only return 1000 hits at most
#my $zmMAILBOX="/opt/zimbra/bin/zmjava com.zimbra.cs.zclient.ZMailboxUtil";
my $zmMAILBOX="/opt/zimbra/bin/zmmailbox";
sub quit
{
if (-f $TEMP_FILE) { unlink $TEMP_FILE or die "ERROR: cannot remove '$TEMP_FILE'.\n" }
if (-f $EXEC_FILE) { unlink $EXEC_FILE or die "ERROR: cannot remove '$EXEC_FILE'.\n" }
exit shift;
}
sub giveup
{
print STDERR "@_\n";
quit 1
}
sub execute
{
my @exec_cmd = @_;
push @exec_cmd, ">$EXEC_FILE";
my $x = "@exec_cmd";
my @system = ($x);
my @output = ();
print "executing '$x'\n" unless $DEBUG<2;
if ($commandlog)
{
open CMDLOG, ">>$commandlog";
print CMDLOG "$x\n";
close CMDLOG;
}
system($x) == 0 or return 1;
open EXEC_TMP, "$EXEC_FILE" or giveup "ERROR: failed to retrieve execution output\n";
while (<EXEC_TMP>)
{
push @output, $_;
}
close EXEC_TMP;
unlink "$EXEC_FILE" or giveup "ERROR: failed to cleanup execution output\n";
return (0, @output);
}
sub usage
{
print "$0 --source username\@host:folderpath --destination username\@host:folderpath\n";
print " --move Move the data after the copy (optional).\n";
print " --noverify Shut off some verification (optional).\n";
print " --forks num The number of parallel copy processes.\n";
print " --preservetags Preserve the message tags.\n";
print " --verbose Be more noisy.\n";
print "\n";
print "$0 does not currently support copying across hosts but that is fine.\n";
print "\n";
print "example usage:\n";
print " $0 --source restored_username\@mydomain.com:/Sent --destination username\@mydomain.com:/Sent --noverify --verbose --forks 16\n";
print "\n";
print "Warning: No warranty! Read the code yourself!\n";
print "\n";
print "\n";
}
# (1) aquire and verify command-line options
use Getopt::Long;
GetOptions("verbose!"=>\$verbose, # optional flag verbose
"source=s"=>\$source, # source is mandatory
"destination=s"=>\$destination, # destination is mandatory
"commandlog:s"=>\$commandlog, # optional command log file
"help!"=>\$help, # option help
"forks:i"=>\$forks, # optional number of forks
"preservetags!"=>\$preservetags, # optional flag preservetags
"noverify!"=>\$noverify, # optional flag noverify
"move!"=>\$move); # optional flag move
if ($move) { print "WARNING: move enabled! source contents will be deleted upon successful copy!\n"; sleep 3 }
if ($ARGV[0])
{
print "I didn't understand the following command-line options:\n";
foreach (@ARGV)
{
print " $_\n";
}
}
if ($forks > 1) { $NUM_FORKS = $forks }
if ($help) { usage ; quit 0 }
print "source: $source\n" unless !$verbose;
print "destination: $destination\n" unless !$verbose;
# (2) split out and verify src/dst components
(my $source_user, my $source_host, my $source_path) = split /[\@\:]/, $source;
(my $destination_user, my $destination_host, my $destination_path) = split /[\@\:]/, $destination;
if ($source_user eq $destination_user) { $preservetags=1; }
print STDERR "DEBUG: $source_user, $source_host, $source_path\n" unless !$DEBUG;
print STDERR "DEBUG: $destination_user, $destination_host, $destination_path\n" unless !$DEBUG;
# (3) enumerate source target contents
($code, @output) = execute "$zmMAILBOX -z -m $source_user search -l $MAX_SEARCH -t message 'in:\"$source_path\"' 2>&1";
$code == 0 or giveup "ERROR: enumerate source target contents failure";
$i = 0; # source target message counter
foreach (@output)
{
next unless (/ mess /);
$i++;
#//s/^.*\. //;
s/^.{1,4}\.\s+([\d ]{1,6}) /$1 /;
push @source_id_list, (split / +/)[0];
}
print "zimbracopyfolder: source target contains $i ";
print "or more " unless $i <1000;
print "messages\n";
if ($i == $MAX_SEARCH)
{
print "WARNING: It seems that, due to a limitation of zmmailbox, this program can\n";
print "WARNING: only handle folders with a maximum of 1000 messages.\n"
}
# (4) construct destination target
($code, @output) = execute "$zmMAILBOX -z -m $destination_user getFolder $destination_path >/dev/null 2>&1";
if ($code != 0)
{
($code, @output) = execute "$zmMAILBOX -z -m $destination_user createFolder $destination_path";
}
$code == 0 or giveup "ERROR: construct destination target failure";
# (5) test destination target for content
($code, @output) = execute "$zmMAILBOX -z -m $destination_user search -l 10 -t message 'in:\"$destination_path\"' 2>&1";
$code == 0 or giveup "ERROR: test destination target for content failure";
$i = 0; # destination target message counter
foreach (@output)
{
next unless / mess /; #/
$i++;
push @destination_list, $_;
}
if ($i>0)
{
print STDERR "ERROR: destination target contains messages!\n";
foreach (@destination_list)
{
print STDERR "$_" unless $DEBUG <2;
}
quit 1 unless $noverify;
}
# (6) export->import full messages
print "zimbracopyfolder: copying...\n";
my $num_queued=0;
my $num_dequeued=0;
foreach $id (@source_id_list)
{
print " FORK QUEUE: \n@children\n" unless !$DEBUG;
# (6.0) fork off up to $NUM_FORKS forks
if ($#children+1 >= $NUM_FORKS)
{
# hold up, we got too many forks!
$child = shift @children;
{
print " BLOCKING -- $pid\n" unless !$DEBUG;
waitpid($child, 0); # Hold off on further execution until this child exits
$num_dequeued++;
print " DEQUEUED <- $pid\n" unless !$DEBUG;
print " DEQUEUED $num_dequeued of ",$#source_id_list+1,"\n" unless !$verbose;
print " FORK QUEUE: \n@children\n" unless !$DEBUG;
}
#print "PUSH -> $pid\n";
}
print " FORKED: $pid\n" unless !$DEBUG;
my $pid = fork();
if ($pid)
{
# begin parent code
# just make record and keep on moving
push(@children, $pid);
$num_queued++;
print " QUEUED -> $pid to handle ($id)\n" unless !$DEBUG;
print " QUEUED $num_queued of ",$#source_id_list+1,"\n" unless !$verbose;
# end is parent code
}
else
{
# begin child code (the forked proc)
$EXEC_FILE .= ".$$";
print " copy message ID='$id'; exec file = '$EXEC_FILE'\n" unless !$DEBUG;
# (6.1) export content
($code, @output) = execute "$zmMAILBOX -z -m $source_user getMessage $id";
if ($code != 0)
{
print STDERR "ERROR: export message failure:\n";
foreach (@output) { print; }
quit 1;
}
open TMPMSG, ">$TEMP_FILE.$$";
foreach (@output) { print TMPMSG }
close TMPMSG;
# (6.2) export metadata
($code, @output) = execute "$zmMAILBOX -z -m $source_user getMessage --verbose $id";
if ($code != 0)
{
print STDERR "ERROR: export metadata failure:\n";
foreach (@output) { print; }
quit 1;
}
my $metadata_date="";
my $metadata_tags="";
foreach (@output)
{
next unless /tags\":|receivedDate:/;
chomp; # trailing newline
s/^\s+//; # leading whitespace
s/,$//; # trailing comma
if (/"tags":/) { ($nothing,$metadata_tags) = split / / }
if (/receivedDate:/) { ($nothing,$metadata_date) = split / / }
#print
}
if ($metadata_tags =~ /null/ || !$preservetags ) { $metadata_tags="" }
# (6.3) import content and metadata
if ($metadata_tags && $metadata_date)
{
($code, @output) = execute "$zmMAILBOX -z -m $destination_user addMessage --date $metadata_date --tags $metadata_tags $destination_path $TEMP_FILE.$$";
}
else
{
($code, @output) = execute "$zmMAILBOX -z -m $destination_user addMessage --date $metadata_date $destination_path $TEMP_FILE.$$";
}
unlink "$TEMP_FILE.$$" or print "ERROR: failed to cleanup temporary message file\n";
if ($code != 0)
{
print STDERR "ERROR: import messages failure:\n";
foreach (@output) { print; }
quit 1;
}
if ($move)
{
($code, @output) = execute "$zmMAILBOX -z -m $source_user deleteMessage $id";
if ($code != 0)
{
print STDERR "ERROR: delete messages failure:\n";
foreach (@output) { print; }
quit 1;
}
}
exit 0
}
}
foreach $child (@children)
{
print " BLOCKING -- $pid\n" unless !$DEBUG;
waitpid($child, 0); # Hold off on further execution until this child exits
print " DEQUEUED <- $pid\n" unless !$DEBUG;
print " FORK QUEUE: \n@children\n" unless !$DEBUG;
}
print "zimbracopyfolder: done copying.\n";
# (7) test destination target again for content
($code, @output) = execute "$zmMAILBOX -z -m $destination_user search -l $MAX_SEARCH -t message 'in:\"$destination_path\"' 2>&1";
$code == 0 or giveup "ERROR: test destination target again for content failure";
$i = 0; # destination target message counter
foreach (@output)
{
next unless / mess /; #/
$i++;
push @destination_list, $_;
}
if ($i>0)
{
print "zimbracopyfolder: destination target contains $i messages:\n";
foreach (@destination_list) { print unless $DEBUG <2 }
}
else
{
print STDERR "ERROR: destination target contains $i messages!\n";
quit 1;
}
quit 0;
Last edited by troy : 01-16-2008 at 04:07 PM.
| 
02-08-2008, 08:26 AM
| | Special Member | |
Posts: 105
| | Nice... will definetly give it a shot...
Now can someone make an admin zimlet for it ? | | Thread Tools | | | | Display Modes | Linear Mode | | Why Join? Registering let's you ask questions, makes it easier to search, displays any files attached to posts, and notifies you about replies.  |