יבוא PST בעייתי לאאוטלוק

יבוא PST בעייתי לאאוטלוק

הפעם אני רוצה לחלוק איתכם את הדרך שעשיתי לפתרון בעיה של יבוא ארכיונים בעיתיים אל תוך תיבות דואר חיות.

ניתן גם לקרוא את מאמר ההמשך לניקוי כפילויות מתיבות דואר.

המאמר מיועד למי שנושאים טכניים מעניינים אותו, למי שרוצה להעשיר את הידע שלו בכתיבת סקריפטים ב-PowerShell, וגם למי שנתקל בבעיה דומה, וירצה להשתמש בסקריפט או בחלקים ממנו.

במאמר הבא אגע בנושאים הבאים:

  • Exchange
  • Enterprise Vault
  • Outlook
  • קבצי PST
  • PowerShell

התוכן:

1.     תיאור הבעיה בה נתקלתי, הדרישות מאת הלקוח ודרכי הפתרון – באופן כללי ועל פי פירוט דל יחסית.

2.     הסקריפט שכתבתי על מנת ליישם את הפתרון.

3.     הוראות וטיפים לשימוש בסקריפט.

4.     פירוט מדוקדק של פעולות הסקריפט והפונקציות הכלולות בו – נועד למי שרוצה להבין את הסקריפט הארוך הזה בצורה קלה יחסית.

הערות פתיחה:

1.     מאמר נוסף על מחיקת כפילויות בתיבות דואר, יבוא בהמשך.

2.     אני יוצא מנקודת הנחה שמי שקורא את המאמר מכיר את הנושאים. לפחות רמה כללית בתפעול וניהול של שרת דואר של מיקרוסופט, או טיפול באאוטלוק כולל פרופילי דואר, רמת מטמון וכו'. הסברים מפורטים מדי בנושאים אלה היו מאריכים ביותר את המאמר הזה, שאיננו קצר גם בלעדיהם.

רקע:

שרתי ושירותי דואר הם עולם בפני עצמו. חשיבותו של הדואר האלקטרוני עצומה בכל צורת עבודה שהיא, ועל כן הפתרונות והאפשרויות בנושא רבים ומגוונים.

אחד הממשקים הנפוצים והגדולים בתחום, זו תוכנת אאוטלוק שבאה עם חבילת אופיס של מיקרוסופט. למיקרוסופט יש גם את חבילת שרתי הדואר שלהם הידועה בשם Microsoft Exchange. יצרנים וספקי שירותים רבים מתאימים תוספים, חבילות ופתרונות בהתאמה לשיטת העבודה הכל כך נפוצה הזו.

אימייל. כולם משתמשים, כולם מכירים וכולם חייבים. מה זה אימייל? כמה מילים וכותרת. אולי גם חתימה, לפעמים קובץ מצורף.

רוב המיילים שוקלים כמה KB. כאלה המכילים קובץ, שוקלים יותר. אבל כמו שמשטח של דפי A4  שוקל המון, כך גם מיילים שמצטברים תופסים המון מקום אחסון. כל מי שמתמצא מעט בענף, יודע שככל שהארגון/משרד גדול יותר – ככה כמות המיילים שכל עובד מקבל ושולח, גדולה יותר.

כמות כזו של מיילים תופסת שטח אחסון ענק, וזה משמעותי מבחינת המשאבים שהמערכת צורכת.

ניהול ארכיון:

אחד הפתרונות הקיימים בשוק לעניין זה, נקרא Enterprise Vault. מדובר בסוג של כספת שאוספת מיילים מתיבת הדואר ומשאירה במקומם מעין קיצור דרך. מצד אחד, מערכות כמו Outlook ו-Exchange יכולות לעבוד יותר בקלות בלי שכל העומס הזה יושב עליהן. ומצד שני, המידע נגיש לאינדקס של חיפוש או כשהמשתמש עובר על הדואר שלו. וברגע שהוא רוצה, בלחיצה אחת המייל קופץ מן הכספת ונפתח לו באאוטלוק.

עד כאן הכל טוב ויפה, רק שבמקרה שלנו המערכת הפכה מיושנת עד שהופסקה התמיכה בה. עם הזמן תיבות דואר איבדו סנכרון עם הכספת, וחלק מהמיילים נותרו בכספת וחלק בתיבות הדואר.

במקום המיילים שבכספת, נותרו בתיבת הדואר הבלתי מסונכרנת כל קיצורי הדרך.

לכן הוחלט לסיים את חייה של המערכת. את תכולת הכספות צריך לייצא לקבצי PST, אותם ניתן יהיה להזין לשרתי הדואר. העניין הזה פשוט יחסית. ברמת הממשק של Enterprise Vault, מבצעים יצוא של תיבות הדואר לקבצים, ולאחר מכן מוצאים את הדרך לייבא את ה-PST.

…ואז החלו הבעיות.

יבוא PST:

הליכי יבוא PST לתוך תיבות דואר, נועדו למנוע כפילויות ולמזער טעויות. האפשרות הזו קיימת גם באאוטלוק לשולחן העבודה, וגם כפקודה בממשק הפקודה של שרת הדואר של מיקרוסופט – מה שנקרא Exchange Shell. אבל במקרים מסוימים זה פשוט לא עובד, והתהליך נכשל שוב ושוב ושוב. כל נסיון כושל יוצר עוד ועוד כפילויות בתיבות הדואר. התיבות מתנפחות מכמה אימיילים שעוברים את התהליך, אבל הכל נשבר באמצע והנתונים לא עוברים.

במקומות מסוימים, יש חשיבות גדולה לכל מייל ואסור לאבד נתונים. והנה אנחנו בבעיה.

את שרת ה-Enterprise Vault צריך להרוג כי הוא כבר לא שימושי ולא נתמך, וגם צורך המון משאבים. הנתונים חייבים לחזור לתיבות הדואר ולהיות זמינים למשתמש.

המשימה: למצוא דרך לייבא את הנתונים לתיבות הדואר. היבוא חייב לשמור על הסדר הקיים, מבלי לאבד מידע.

בעיות להתמודד איתן:

מיילים שבזמן הסנכרון לכספת ישבו בתיקיה מסוימת באאוטלוק, כעת יושבות בנתיב אחר לגמרי. אם מדובר בקיצור הדרך של אאוטלוק, צריך גם לוודא שהליך היבוא לא יזהה את קיצור-הדרך-לכספת כמייל אמיתי. זיהוי שגוי ימנע את היבוא מחשש לכפילות. וגם צריך שהיבוא יאחזר את המיילים לאותו מקום בדיוק בו יושב קיצור הדרך שלהם. זאת כדי לשמור על הסדר הנוכחי אותו יצר המשתמש.

בכל שלב – אסור אסור אסור לאבד מידע!

המשימה:

וכך קרא לי ראש הצוות שלי וביקש ממני לראות אם אני יכול לכתוב סקריפט שיטפל בזה.

מתחילה ניסיתי לגשת לעניין דרך שורת הפקודה של שרת הדואר. אבל ראיתי שמיקרוסופט חיסרו רבות בעניין וזה לא עובד. כך חיפושים בתיבת דואר דרך Exchange Shell לא מאפשרים לאסוף את תוצאות החיפוש אל תוך משתנה. אם כן, אי אפשר לחטט באובייקטים כמו שצריך, אלא מחייבים הפניה של התוצאות לתיבה אחרת. גם קריאת תוכן של PST דרך Exchange Sell איננה אפשרית. אז לא ממש מצאתי דרך מיטבית לאסוף פרטים ומאפיינים מתוכנו של קובץ PST. איסוף פרטים זה חיוני על מנת שאוכל לבצע את ההשוואות הדרושות בין מיילים בתיבה החיה, לבין ארכיון PST.

לבסוף גיליתי את האפשרות של MAPI, שבעצם טוענת דרך PowerShell תהליך של Outlook. דרך התהליך הזה, ניתן לעבוד מתוך PowerShell על כל פריט, תיקיה ואובייקט מתיבת הדואר, ולעבד את הנתונים שלו.

כך לאחר עבודה מאומצת הצלחתי ליצור תהליך שעובד ועומד בכל הדרישות.

שלבי התהליך:

הכנה:

1.     יצירת משתמש בדיקות/יעודי לפעולה זו.

2.     יצירת שרת ווינדוס בעל כמות נאה של משאבים – 8 ליבות מעבד, 32GB זכרון ושטח דיסק של עד 2 TB.

3.     התקנת אופיס על השרת.

4.     בממשק הניהול של שרת הדואר, מתן הרשאות מלאות למשתמש היעודי על תיבת דואר שאליה רוצים לייבא ארכיון PST.

5.     בשרת החדש יוצרים פרופיל דואר מתוך לוח הבקרה הישן (כדי שלא ינסה לפתוח אאוטלוק עם הדואר של המשתמש היעודי). לפרופיל מצמידים את תיבת הדואר שאליה מייבאים.

6.     הגדרת הפרופיל על מצב מטמון מלא – כל תיבת הדואר תטען לתוך השרת באמצעות אאוטלוק. לא שנה אחורה, לא שלושה חודשים – כל התיבה.

7.     הפעלת אאוטלוק עד לטעינה מלאה של כל תיבת הדואר אל תוך המחשב. זה יכול לקחת כמה שעות, עד שרואים בשורת הנתונים למטה באאוטלוק את ההודעה שכל התיקיות מעודכנות.

8.     בהגדרות אאוטלוק, להכנס למרכז האמון (Trust Center). שם להגדיר שלעולם לא יקפיץ אזהרה על ישומים זרים המנסים לגשת לנתונים של אאוטלוק.

9.     להכנס להגדרות יומן ולהגדרות מתקדמות, ולבטל את כל ההתרעות שקופצות מהיומן. בלי התרעות יומן בכלל.

10. העתקת אותו ארכיון PST שרוצים לייבא לתיבה. לשים בתיקיית המסמכים של המשתמש שבאמצעותו פועלים (המשתמש היעוד שהוקם לטובת התהליך). צריך לשנות את שם הקובץ לשם המשתמש או לשם נוח אחר.

הפעלת הסקריפט:

11. פתיחה של PowerShell ISE, לפתוח באמצעותו את הסקריפט שיבוא בהמשך.

12. לוודא שהסקריפט לוקח אינדקס של כל תיבת הדואר ושל כל הארכיון ומשווה ביניהם.

13. לאחר ההשוואה, מתחילים בהעברה של כל הפריטים בארכיון שנמצאה להם הקבלה בתיבת הדואר בדמות קיצור דרך. כל פריט עובר לצדו של קיצור הדרך שלו. כל פריט עובר ולא מועתק, כך שלאט לאט הארכיון מצטמצם ומתרוקן.

14. לאחר מכן מתבצעת העברה של כל הפריטים בארכיון שאין להם הקבלה בתיבת הדואר. אלה עוברים למיקום מקביל בתיבת הדואר. מקביל למיקום שהיה להם בארכיון, באותו מיקום בדיוק על עץ התיקיות. אם אין נתיב כזה, התהליך יצור אותו עד שהמייל ישב באותו נתיב בדיוק שישב בו בארכיון – למעט העובדה שכעת הוא יושב בתיבה ולא בארכיון.

15. מריצים שוב את הליך האינדקס כדי לוודא שלא נשאר כלום. אם נשאר, להריץ שוב את מה שצריך עד שהכל עובר לתיבה.

16. מחיקה של פרופיל הדואר, של קובץ הארכיון המרוקן ושל הקבצים שהתהליך יצר על שולחן העבודה. אז חוזרים על התהליך בשביל משתמש נוסף.

17. הליך ניכוי כפילויות יתבצע בנפרד.

אני עבדתי ככה עם שלושה שרתים במקביל ושלושה משתמשים יעודיים לתהליך. הספקתי ככה יבוא של 26 ארכיונים בגודל ממוצע של 15GB, בתוך כשבוע וחצי. רוב התיבות היו תיבות גדולות, שמכילות מאות אלפי פריטים.

סיכום:

יכול להיות שהתהליך חסר. ודאי שמתכנתים ואנשי סקריפטים מנוסים יוכלו לתת הצעות שיפור ויעול לטירון שכמוני, ואני אשמח לקבל ביקורת בונה.

וכעת אסביר את פעולת הסקריפט, ואת המבנה שלו, ולמה כל חלק נבנה כפי שנבנה. בנוסף, בחלק של הקוד שכולל כמעט 300 שורות של קוד + תיעוד והסברים, יש גם הסבר מינימלי על הפעולה.

כעת, יבוא הסקריפט. לאחריו הוראות למי שרוצה להעזר בו ליבוא קבצי PST בעייתיים.

לבסוף אתן פירוט מדוקדק שמסביר את פעולות הסקריפט ואת הפונקציות שלו. מומלץ למי שרוצה להעשיר את הידע שלו בקוד של PowerShell, או במי שרוצה להפעיל את הסקריפט בצורה טובה.

הסקריפט:

<# This script intended to add an archive like PST file, into a Mailbox
In manner to avoid duplicates, the script should add into the Mailbox only items which is not there.
The script is working via outlook, add the PST and compare it with the MailBox.
The script is going through the folder tree of the PST file.
In each folder, going through all items.
For each item, the script checking if it’s existing in the parallel folder in the Mailbox. 
If it’s not there, check performed to see if it’s in deleted items or junk emails. 
If the item isn’t it thus 3 locations, it will be copied to the parallel folder in the Mailbox. #>


# Here you put the MailBox of the user, and the path for the PST which compared and loaded into the MailBox.
# =========================================================
$user = 'user@domain.suffix'
$PSTPath = "C:\Users\$env:USERNAME\Documents\username.pst"


<# This function does logging of the procces. printing datails and timestamp on screen and into log file.
Parameters: 
$a, $b: index of the string to print, inside the messages array.
$res: the value described by the messege printed above. #>
function Print-Output {
    param ($a, $b,[Parameter(Mandatory = $false)]$res = $null)
    
    $global:output[$a,$b,$a]
    $res
    Get-Date
    
    $global:output[$a,$b,$a] | Add-Content -Path C:\Users\$env:USERNAME\Desktop\log.txt -Encoding UTF8
    $res| Add-Content -Path C:\Users\$env:USERNAME\Desktop\log.txt -Encoding UTF8
    Get-Date | Add-Content -Path C:\Users\$env:USERNAME\Desktop\log.txt -Encoding UTF8
}


<# This function going through all folders in the folder tree, and for each folder check if there ar subfolders.
If there are subfolders, the function call recursive version of itselfe to run on each subfolder.
If there are no subfolders or the subfolders had been scanned already, than function list all items in the folder.
Lists of folder items is added to global index, according to the root store of the folder.
Parameters:
$currentfolder - represent the current folder which the action conducted whithin, to run on that object.
$rootstore - if the proccess conducted in the PST or in the MailBox. #>
function Index-Folder {
    param ($currentFolder, $rootstore)
    
    if ($currentFolder.name -in $global:exlude) {
        break 
    }
     
    if ($currentFolder.Folders.Count -gt 0) {        
        foreach ($x in $currentFolder.Folders) {
            Index-Folder -currentFolder $x -rootstore $rootstore
        }
    }


    if ($rootstore -eq 'mail') {
        $list = $currentFolder.items | ?{$_.MessageClass -EQ "IPM.Note.EnterpriseVault.Shortcut"} | select senton, subject, MessageClass
        $list | Add-Member -MemberType NoteProperty -Name 'Path' -Value $currentFolder.folderpath -Force
        $global:indexmail +=  $list
        
    } else {
        $list = $currentFolder.items | select senton, subject, MessageClass
        $list | Add-Member -MemberType NoteProperty -Name 'Path' -Value $currentFolder.folderpath -Force
        $global:indexpst +=  $list
    }    
}


<# This function finds the object of specific folder, according to the folder path.
Parameters:
$folder: represent the root of the folder required, wethere is the PST or Mailbox.
$path: hold the entire path of the required folder.
The function return the actual required folder as an object. #>
function Find-Folder {
    param ($folder, $path)


    if ($Folder.Folders.Count -gt 0) {        
        foreach ($x in $Folder.Folders) {
            if ($x.folderpath -eq $path) {
                return $x
            }            
            Find-Folder -folder $x -path $path
        }
    }
}


<# This function verifying the path of an item to transfer. Sometimes an item exists in PST only,
and therefore, doesn't exist the parallel path to transfer the item from it's location in PST to indentical location in the MailBox.
The function going through all folders in the path hyrarchy of the target. if it's not exist, the function will create it.
Parameters:
$folder: the root MailBox folder as an object to explore.
$path: the full path of the target to transfer.
The function return an object of the actual folder, which it's path is the terget path required. #>
function Verify-Path {
    param ($folder, $path)


    $path_arr = $path.split("\")
    $path_arr = $path_arr[3..($path_arr.count)]


    foreach ($x in $path_arr) {
        $check = $folder.Folders | ?{$_.name -eq $x}
        if ($check  -ne $null) {
            $folder = $check
        } else {
            $folder.folders.add($x)
            $folder = $folder.Folders | ?{$_.name -eq $x}
        }
    }
    return $folder
}


<# This function loops over all items remained in PST, when the copy of items parallel with shortcuts in the mailbox is done.
The function running over all folders, copy all items to the parallel path in the MailBox.
Parameters:
$folder: the root MailBox folder as an object to explore. #>
function Copy-Remaining {
    param ($folder)
     
    if ($Folder.Folders.Count -gt 0) {        
        foreach ($x in $Folder.Folders) {
            Copy-Remaining -folder $x
        }
    }


    Print-Output -a 0 -b 11 -res  $folder.folderpath
    $target = Verify-Path -folder $global:mailboxRoot -path $folder.folderpath
    foreach ($x in $folder.items){
        try {
            $x.Move($target)
            $global:pstmoved +=1
        } catch {
            $_.tostring() | Add-Content C:\Users\$env:USERNAME\Desktop\log.txt
            "Can't move item" | Add-Content C:\Users\$env:USERNAME\Desktop\log.txt 
            $log = $x | select senton, subject
            $log | Add-Member -MemberType NoteProperty -Name 'Path' -Value $target.folderpath
            $log | Add-Member -MemberType NoteProperty -Name 'Error' -Value $_.tostring()
            Get-Date | Add-Content C:\Users\$env:USERNAME\Desktop\log.txt
            "========================" | Add-Content C:\Users\$env:USERNAME\Desktop\log.txt 
            $global:logs += $log
        }
    }
}


<# This function copy items from PST, to the location of their shortcut in the MailBox.
Sometimes the general proccess to copy EV items, missing few object and can't find them. 
This function closing the gap.
Parameters:
$ev: generaly represents the ev index, which contains the PST items which had shortcuts in the MailBox.
But in case of errors, you can use $logs veriable to reproccess the copy of all items that failed to copy. #> 
function Copy-RemainingEV {
    param ($ev)


    $shared = @()
    foreach ($x in $ev) {
        $y = $global:indexpst | ?{$_.senton -in $x.senton} | ?{$_.subject -in $x.subject}
        $y | Add-Member -MemberType NoteProperty -Name 'Target' -Value $x.path -force
        $shared += $y
    }


    $count = 0
    foreach ($x in $shared) {
        $folder = Find-Folder -folder $global:pstRoot -path $x.path
        $target = Find-Folder -folder $global:mailboxRoot -path $x.target
        $message = $folder.Items | ?{($_.senton.GetDateTimeFormats())[71] -in $x.senton} | ?{$_.subject -in $x.subject} 
        $message.Move($target)
        $count ++
    }
    "Moved: "
    $count
}


<# This function copy the last few items from PST into the Mailbox, those which the general proccess bypassed.
It's for the remaining items - those which are not in the Mailbox. #>
function Copy-FewPST {
    $count = 0
    foreach ($x in $global:indexpst) {
        $folder = Find-Folder -folder $global:pstRoot -path $x.path
        $target = Verify-Path -folder $Global:mailboxRoot -path $x.Path
        $message = $folder.Items | ?{($_.senton.GetDateTimeFormats())[71] -in $x.senton} | ?{$_.subject -in $x.subject} 
        $message.Move($target)
        $count ++ 
    }
    "Moved: "
    $count
}


# Configuring Outlook MailBox and PST as an available object to work with.
$outlook = New-Object -com Outlook.Application
$namespace = $outlook.GetNamespace("MAPI")
$namespace.AddStore($PSTPath)
$mailbox = $namespace.Stores | ? {$_.displayname -like $user}
$pst = $namespace.Stores | ? {$_.FilePath -like $PSTPath}
# Veriable points to the root folder of the MailBox.
$global:mailboxRoot = $mailbox.GetRootFolder()
# Veriable points to the root folder of the PST Archive.
$global:pstRoot = $pst.GetRootFolder()


#################################### For testing, you can refer the process to a single subfolder


#$global:pstspec = $Global:pstRoot.Folders[6].Folders[1]
#$global:mailspec = $Global:mailboxRoot.Folders[2].Folders[1]


##########################################


$global:exlude = 'RSS Subscriptions', 'Quick Step Settings', 'Sync Issues', 'Conversation Action Settings', 'Yammer Root', 'Recipient Cache'
$Global:count = 0
$evmoved = 0
$global:pstmoved = 0
$global:indexmail = @()
$global:indexpst = @()
$global:logs = @()
$global:final = $null
$failed = @()
$notinpst = @()


# Contains output messages To print on screen and log in file, for monitoring purpes.
$global:output = "===================================", "         Indexing Mailbox", "     Mailbox Indexing Completed", "           Indexing PST", `
"       PST Indexing Completed", "           Index result", "             Mail Items", "             PST Items", "     Archive Sortcuts Number", `
"    Begining Copy EV Proccess", "Target: ", "Copy from: ", "    EV Items Copied Into MailBox", "Begining Copy Remain-In-PST Proccess", `
"    Remained Items Copied Into MailBox", "           Total Copied" 


# =======================================================
#                    The mean process
# =======================================================


# Index the Mailbox, getting the list of all items, and all shortcuts in the Mailbox.
# If the process failed in the middle for some reason, can save the time of indexing and import this data from files.
# In this case, the indexing lines shuld be marked.
Print-Output -a 0 -b 1
#Index-Folder -currentFolder $global:mailboxRoot -rootstore 'mail'
#$global:indexmail | Export-Csv -NoTypeInformation -Encoding UTF8 -Path C:\Users\$env:USERNAME\Desktop\mail.index.csv
$global:indexmail = $null
$global:indexmail = Import-Csv -Path C:\Users\$env:username\Desktop\mail.index.csv
Print-Output -a 0 -b 2
Print-Output -a 0 -b 3


#Index-Folder -currentFolder $global:pstRoot -rootstore 'pst'
#$global:indexpst | Export-Csv -NoTypeInformation -Encoding UTF8 -Path C:\Users\$env:USERNAME\Desktop\pst.index.csv
$global:indexpst = $null
$global:indexpst = Import-Csv -Path C:\Users\$env:username\Desktop\pst.index.csv
#$ev = $Global:indexmail | ?{$_.senton -in $Global:indexpst.senton} | ?{$_.subject -in $Global:indexpst.subject}
#$ev | Export-Csv -NoTypeInformation -Encoding UTF8 -Path C:\Users\$env:USERNAME\Desktop\ev.index.csv
$ev = Import-Csv -Path C:\Users\$env:username\Desktop\ev.index.csv


Print-Output -a 0 -b 4
Print-Output -a 0 -b 5
Print-Output -a 0 -b 6 -res $global:indexmail.Count
Print-Output -a 0 -b 7 -res $global:indexpst.Count
Print-Output -a 0 -b 8 -res $ev.Count
Print-Output -a 0 -b 9  
#<#
# Loop over all EV shortcuts in the mailbox, find the full parallel mail in the PST and move it into the mailbox.
$pathgroup = $ev | Group-Object -Property path
foreach ($group in $pathgroup) {
    
    Print-Output -a 0 -b 10 -res  $group.Name
    $target = Find-Folder -path $group.name -folder $global:mailboxRoot
    $shared = $global:indexpst | ?{$_.senton -in $group.Group.senton} | ?{$_.subject -in $group.Group.subject}
    $pstgroup = $shared | Group-Object -Property path


    foreach ($location in $pstgroup){
        Print-Output -a 0 -b 11 -res  $location.Name
        $folder = Find-Folder -folder $global:pstRoot -path $location.Name
        $count = 0
        While ($count -lt ($location.Count -1)) {
            $portion = $location.Group[$count..($count + 4999)]
            $messages = $folder.Items | ?{($_.senton.GetDateTimeFormats())[71] -in $portion.senton} | ?{$_.subject -in $portion.subject}       
            foreach ($x in $messages){
                try {
                    $x.Move($target)
                    $evmoved += 1
                } catch {
                    $_.tostring() | Add-Content C:\Users\$env:USERNAME\Desktop\log.txt
                    "Can't move item" | Add-Content C:\Users\$env:USERNAME\Desktop\log.txt 
                    $log = $x | select senton, subject
                    $log | Add-Member -MemberType NoteProperty -Name 'Path' -Value $target.folderpath
                    $log | Add-Member -MemberType NoteProperty -Name 'Error' -Value $_.tostring()
                    Get-Date | Add-Content C:\Users\$env:USERNAME\Desktop\log.txt
                    "========================" | Add-Content C:\Users\$env:USERNAME\Desktop\log.txt 
                    $global:logs += $log
                }
                $Count ++ 
            }   
        } 
    }
}   


# Get the Remaining Items in PST which arn't in the mailbox, and move in into the mailbox.
Print-Output -a 0 -b 13
<#
while ($global:pstmoved -lt ($global:indexpst.Count - $ev.Count)){
    Copy-Remaining -folder $global:pstRoot
}#>
Print-Output -a 0 -b 12 -res $evmoved
Print-Output -a 0 -b 14 -res $global:pstmoved
Print-Output -a 0 -b 15 -res ($global:pstmoved + $evmoved)
#>
#Copy-RemainingEV -ev $ev
#Copy-FewPST.

הוראות וטיפים:

הגדרה נכונה ותהליך עבודה:

1.     בהתחלה תמיד לזכור להזין את כתובת הדואר של התיבה שעליה עובדים, ואת הנתיב לקובץ הארכיון שאותו מייבאים.

2.     כפי שניתן לראות בקוד, מעבר לשורות של התיעוד ישנן שורות קוד של ממש המסומנות בסולמית או בסימון מקטע (<# #>) שמורה למחשב לא להתייחס אליהן כקוד. הכל נובע מכך שהתהליך ארוך ומורכב מכדי להריץ אותו במכה אחת. ברובם המוחלט של המקרים זה לא יעבוד. התהליך יתקע באמצע ולא ימשיך.

3.     התהליך מהווה בעצם קומבינה שרוכבת על אאוטלוק דרך פאוור של. שתי התוכנות לא נועדו לעבד כמויות כאלה של נתונים במכה. לכן צריך להריץ את הקוד במקטעים, ולפעמים לתת הרצות חוזרות.

4.     בהרצה הראשונה מומלץ רק לאסוף אינדקסים. אוספים אינדקס של תיבת הדואר, של הארכיון והעיבוד שמייצר אינדקס של קיצורי דרך מתיבת הדואר שקיים להם מימוש בארכיון. כל אלה יוצאים לקבצים שיושבים על שולחן העבודה. לבטל את הסולמית הראשוני אחרי הקריאה לפונקציית הפלט עם הקריאה להדפסת מחרוזת מס' 9. זה יסמן את כל ההליך הראשי כשורות שצריך להתעלם מהן, והתהליך יפסיק לרוץ לאחר איסוף האינדקסים ויצוא שלהם לקבצים.

תקיעות בתהליך:

5.     אחרי כל מחזור של הרצה מומלץ לאתחל את השרת לפני הרצה נוספת. זה מאפס ומנקה את הזכרון. והזכרון הוא המקור הראשון לתקיעות בתהליך.

6.     אם כן, לאחר איסוף אינדקס ויצוא, איתחול. לאחר האיתחול, לוודא שהשורות המייצרות את האינדקס מסומנות בסולמית. במקומן צריך רק לייבא מהקבצים שכבר יושבים על שולחן העבודה.

7.     אם התהליך נתקע באמצע, לבדוק אם הדיסק לא התמלא. אני הגדלתי את השרתים שאיתם עבדתי עד ל-2TB גודל דיסק. התהליך פורש את האובייקטים שהוא עובר עליהם, מתוך הארכיון בדרך לתיבת דואר. אלה נשמרים עליהם מנופחים מאוד – באופן זמני, בקובץ המטמון של אאוטלוק, בתוך תיקיית Appdata. אם רואים שנגמר המקום, הגדלת שטח הדיסק תאפשר לו להמשיך לרוץ.

8.     אם התהליך נתקע מסיבה של זכרון נדיף או משהו אחר שלא רואים, לאתחל את השרת. לאחר מכן, לפני הרצה נוספת כדאי למחוק את קבצי האינדקס של הארכיון ו-EV. ביצירה מחדש שלהם נראה שהפעם המספרים קטנים יותר, כי מופחת מהמניין כל הקבצים שהספיקו לעבור.

חסכון בזמן הרצה:

9.     כמעט אף פעם אין סיבה לקחת מחדש אינדקס של תיבת הדואר. פעם אחת מספיקה ודי, חבל על הזמן.

10. בכל פעם שמריצים את התהליך, לשים לב איזה אינדקסים מייבאים מן המוכן ואיזה מייצרים מחדש. הסימון על השורות האלה חשוב, וצריך להתמצא ולדעת מה עושים.

11. לא לחינם מסומן החלק שקורא לפונקציה שמעבירה את הפריטים שאין להם קיצורי דרך בתיבה. גם אם ההליך סיים לרוץ בלי להתקע, יכול להיות עדיין שלא העתיק הכל. אז צריך הרצה נוספת. להעתיק פריטים שיש להם קיצור דרך בהעתקה הכללית, לא בהכרח יביא אותם לתיקיה בה נמצא קיצור הדרך שלהם.

12. בסיומו של תהליךקובץ הארכיון אמור להיות ריק. (שלפעמים נדרשות כמה הרצות ושינויי סימונים עד שמגיעים לשם). כשמריצים את התהליך פעם נוספת, האינדקס של הארכיון אמור להעלות את המספר 0 – כי לא הצליח למצוא שום פריט להכניס לאינדקס.

ניתוח הקוד:

פונקציה ראשונה – פלט ובקרה:

כל הליך קצת יותר ארוך מסקריפט של 50 שורות, צריך רמה מסוימת של בקרה. זאת לוודא שהכל פועל כראוי. אם התהליך ארוך ולוקח זמן, כדאי מדי פעם לקבל חיווי באיזה שלב ההליך נמצא. בנוסף, כאשר יש שגיאות, צריך לדעת מה הן ואיפה הן נתקעו.

לכן הפונקציה הראשונה היא פונקציה שהתהליך קורא לה בשלבים שונים של התהליך. בכל שלב כזה היא זורקת פלט מסוים – גם למסך וגם לתוך קובץ לוג. הפלט נותן נקודת זמן ופרטים על מה שקורה.

פונקציה שניה – אינדקס:

לצורך כל השוואה ובדיקה בין תיבת הדואר לארכיון, צריך לדעת מה יש בתיבה ומה יש בארכיון. לכן השלב הראשון הוא ליצור אינדקס קליל של תוכן התיבה והארכיון. זאת כדי שנוכל לאתר מיילים באמצעות מאפיינים מסוימים.

התהליך מתבצע באמצעות פונקציה רקורסיבית שעוברת על כל התיקיות בתיבת הדואר (או בארכיון). רקורסיבית, כי צריך לבצע בדיקה בכל תיקיה שאין תתי תיקיות. ואם יש תת-תיקיה, לוודא שגם בה אין תת תיקיה. אחרי שבוצעה הבדיקה אם יש או אין תת תיקיה, מתבצע מעבר על כל הפריטים בתיקיה ואיסוף של מספר מאפיינים שלהם. המאפיינים לאיסוף:

  • תאריך שליחה – מדויק לרמת מאית השניה, ויכול לשמש מזהה יחודי לכל פריט.
  • כותרת/נושא ההודעה/פריט – מוסיף עוד מאפיין לבדיקת היחודיות והזיהוי של הפריט.
  • סוג הפריט – להבדיל בין פריט ממשי לבין קיצור דרך לכספת.
  • לכל פריט נוסף מאפיין – הנתיב המלא של התיקיה שבה יושב הפריט.

מתחילה הפונקציה הרקורסיבית היתה קוראת בכל תיקיה לפונקציה נוספת שתבצע את הליך האינדקס עצמו. כחלק מיעול התהליך וקיצור זמן הריצה, הכנסתי פשוט את הליך האינדקס לסוף של הפונקציה הרקורסיבית. פשוט לאחר החלק שבו הוא בודק אם יש תתי תיקיות.

בנוסף, החלטתי שמיותר לאסוף מידע על כל הפריטים בתיבת הדואר. מספיק לי לאסוף רק אינדקס של קיצורי-דרך-לארכיון שיש לי בתיבת הדואר. הרי המידע על תיבת הדואר משמש אותי רק כדי לדעת לאן להעביר את הפריטים שיש להם קיצור דרך.

הפונקציה הרקורסיבית מקבלת שני פרמטרים, ויכולה להנפיק (לא להחזיר) משתנה גלובלי אחד מתוך שנים. הפרמטרים הם:

  • התיקיה שבה מתבצע הליך האינדקס
  • המקור הראשי שבו מתבצע ההליך – אם זה התיבה או הארכיון.
    בהתאם למקור, הפונקציה מזינה את נתוני האינדקס שנאספו. אלה נכנסים לאחד משני משתני אינדקס גלובלי: אינדקס ארכיון, או אינדקס תיבה.

פונקציה שלישית – מציאת תיקיה:

ההליך פועל באמצעות אינדקס. אחרת אורך התהליך היה אינסופי. זאת במידה והיה מצליח לעבוד, בלי לקרוס כשטוענים את כל האובייקטים לזכרון.

לכן במקרים רבים התהליך יודע איפה פריט מסוים יושב – לפי האינדקס. אז צריך לטפל בפריט עצמו, ועל כן צריך למצוא את האובייקט הממשי של התיקיה בה יושב הפריט.

הפונקציה השלישית מקבלת שני פרמטרים:

  • מקור ראשי לחפש בו – תיבה או ארכיון
  • נתיב שלאורכו מתבצע החיפוש עד שנמצאת התיקיה עצמה.

הפונקציה מחזירה את התיקיה המבוקשת כאובייקט.

פונקציה רביעית – אימות נתיב:

כאמור לעיל, הארכיון מכיל גם רכיבים שאינם קיימים כקיצור דרך בתיבת הדואר. או כי נמחקו בעבר או מסיבות אחרות. היות ואין קיצור דרך בתיבת הדואר, הפריטים האלה עוברים לתיבה לאותו מקום בו ישבו בארכיון. ואם המיקום הזה בנתיב לא קיים, הפונקציה יוצרת את הנתיב עד לתיקיה האחרונה שבו. לאותה תיקיה אמורים להעביר את הפריט.

הפונקציה מקבלת כפרמטרים:

  • תיקיה מסוימת להתחיל לחפש בתוכה
  • נתיב שצריך למצוא את התיקיה שבסופו – או ליצור את הנתיב עד שנגיע לתיקיה שבסופו.

התיקיה מחזירה כאובייקט את התיקיה שבסוף הנתיב – מתוך תיבת הדואר.

פונקציה חמישית – העתקת הנותרים:

עוד פונקציה רקורסיבית שמטרתה לעבור על עץ תיקיות. בדרך כלל זה יקרה בארכיון. הפעולה בודקת כל תיקיה בעץ ולבסוף מבצעת פעולה באותה תיקיה.

הפונקציה עוברת על עץ תיקיות ובכל אחת מן התיקיות בודקת האם ישנן תת תיקיות.   אם יש, על כל תת-תיקיה מופעלת הפונקציה רקורסיבית. לאחר הבדיקה, מתבצעת קריאה  לפונקציה הרביעית – וידוא נתיב. התיקיה החוזרת מן הפונקציה, מסומנת כיעד להעברת הפריטים שבתיקיה הנוכחית. התיקיה החוזרת מן הפונקציה הרביעית, אמורה להיות המקבילה שבתיבת הדואר, לנוכחית שמן הארכיון.

פונקציה שישית – הנותרים מקיצורי הדרך:

הליך העברת הפריטים הכללי עובד בקבוצות ובקנה מידה גדול. מסיבות שונות הוא מפספס פריטים בודדים פה ושם. לבסוף נותרים כמה פריטים (זה יכול להיות גם 100, אבל לא משהו שיעבור בהליך הרגיל.) והפונקציה הזו דואגת להעביר גם אותם.

הפונקציה מקבלת כפרמטר אינדקס כלשהו – בדרך כלל אינדקס קיצורי הדרך שבתיבת הדואר. אז עוברת על כל פריט ופריט מבין הנותרים באינדקס קיצורי הדרך. הפונקציה מוצאת את היעד שלו בארכיון, ומעתיקה למיקום של קיצור הדרך שבתיבת הדואר.

אזהרה!

אין לנסות להעתיק את כל הארכיון דרך הפונקציה הזו, זה יקח יותר מדי זמן. ההליך הכללי נועד לעשות את זה בצורה חסכונית.

פונקציה שביעית – המעטים שנותרו בארכיון:

גם הליך העברת הפריטים הרגילים מהארכיון לתיבת הדואר, אמור לעבור על כל פריט בכל תיקיה. בפועל הוא מפספס לפעמים כמה בודדים. עבורם נועדה הפונקציה השביעית.

השיטה שלה שונה מההליך הכללי. במקום לעבור על כל תיקיה ולרוקן אותה מפריטים, הפונקציה עוברת על האינדקס של הארכיון. על כל פריט באינדקס היא מוצאת את האובייקט עצמו בארכיון, ומעבירה לתיבה.

אזהרה!

אין לנסות להעתיק את כל הארכיון דרך הפונקציה הזו, זה יקח יותר מדי זמן. ההליך הכללי נועד לעשות את זה בצורה חסכונית.

ההליך הכללי:

הכנה:

  • מתחילה מוגדרים שני משתנים – המשתמש שלו שייכת תיבת הדואר המבוקשת, והנתיב לקובץ הארכיון.
  • ההליך הכללי יוצר כמשתנה תהליך ממוזער של אאוטלוק. לתהליך זה טוענים את תיבת הדואר של המשתמש ואת קובץ הארכיון. את אלה שהוגדרו בשלב הקודם.
  • ההליך קובע בשני משתנים את תיקיות המקור  – זו של התיבה וזו של הארכיון. התיקיות האלה תשמשנה מקור לגישה לעץ התיקיות ולאובייקטים.
  • הגדרת משתנים נוספים הנצרכים להליך. כמו החרגת תיקיות כלשהן מהליך העבודה, מונים כלשהם, משתנים גלובליים עבור האינדקס, עבור איסוף שגיאות.
  • הגדרת מערכת מחרוזות עבור הודעות פלט. אלה עולות על המסך ועוברות ללוגים, לציין את שלביו השונים של ההליך.
  • קריאה לפונקציה השניה – יצירת אינדקס של כל הפריטים בתיבת הדואר מסוג קיצור דרך.
  • קריאה לפונקציה השניה – יצירת אינדקס של כל הפריטים בארכיון.
  • יצירת משתנה המכיל אינדקס של כל קיצורי הדרך שיש להם הקבלה ומימוש בארכיון. מכאן ואילך האינדקס הזה מהווה מדד לתוכן תיבת הדואר, השאר לא מעניין.
  • כל אינדקס יוצא לקובץ CSV ואז מיובא חזרה אל תוך ההליך. הסיבה – פורמט התאריך משתנה כשהוא יוצא לקובץ. אין סיבה לחזור על הליך האינדקס של תיבת הדואר יותר מפעם אחת. ואם ההליך נתקע, עדיין יש את האינדקס. בהפעלה חוזרת מייבאים את האינדקס מהקובץ וחוסכים את הזמן שלוקח ליצור אותו. אך פורמט התאריך של אינדקס שחזר מקובץ איננו תואם לפורמט באינדקס חי שהרגע נאסף. הניסיון להשוות ביניהם יוביל לתוצאה שלילית בכל פעם, למרות שמדובר באותה נקודת זמן. לכן כל האינדקסים עוברים לפורמט של יבוא מקובץ, על מנת ליצור תבנית אחידה. הפער בין התבנית של האינדקסים לתבנית של הפריטים החיים בתיקיות, נלקח בחשבון כשבאים למצוא את הפריטים בתיקיות.

מיון וטיפול:

  • מיון לקבוצות של כל קיצורי הדרך מתיבת הדואר. המיון מתבצע על פי נתיב.
  • מעבר על כל אחת מהקבוצות שנוצרו. כל קבוצה בעצם יוצרת יעד אחד להעתקה. אל יעד זה צריך להעתיק את כל הפריטים שקיצורי הדרך שלהם יושבים באותה התיקיה. חוסך לחפש את תיקיית היעד עבור כל פריט מחדש. קוראים פעם אחת וזהו לפונקציה השלישית ומקבלים את התיקיה.
  • בכל קבוצה כזו של קיצורי דרך, משווים מול אינדקס הארכיון. שם מוצאים את כל הפריטים המייצגים את המימוש של קיצורי הדרך.
  • את כל הפריטים המתאימים מחלקים לקבוצות, בהתאם לנתיב שבו יושב המימוש בארכיון. כך שכל קבוצה מכילה תיקית מוצא אחת.
  • על כל קבוצה כזו, קוראים לפונקציה השלישית. שם קובעים תיקית מוצא שממנה מעתיקים את כל הפריטים המבוקשים.
  • אוספים את כל הפריטים בתיקיה המיוצגים בקבוצה המדוברת, ומעבירים אותם אחד אחד לתיקית היעד.

חלוקת קבוצות גדולות:

  • העניין הוא שבתיקיות גדולות לפעמים עלולים להיות עשרות אלפי פריטים. לדוגמה – תיקיות כמו דואר נכנס, דואר יוצא או פריטים שנמחקו. זה מעיק על הזכרון ותוקע את התהליך עד שהוא נפסק באמצע ללא הודעה. לכן נוצר מנגנון שמחלק את הפריטים בקבוצה למנות קצובות – אפשר 5,000 ואפשר גם אלף.
  • מתבצע איסוף של הפריטים הממשיים המקבילים לפריטי האינדקס שבאותה מנה קצובה, ואותם מעבירים.

ניהול שגיאות:

  • ההליך מתבצע בצורה של ניסוי-כיסוי. אם הניסוי מצליח, מה טוב. אם לא, הכיסוי מגדיר פעולה למקרה של שגיאה.
  • הכיסוי שומר את האינדקס של הפריט שנכשל בהעברה. אז מוסיפים למאפיינים שלו ולקובץ התיעוד של הפעולה, את השגיאה שמנעה את ההעברה. בנוסף יש חותמת זמן.
  • אם ההעברה הצליחה, מונה ההעברות מסמן שעוד העברת קיצור דרך עברה בהצלחה.

החלק האחרון של הקוד:

  • הלולאה עוברת בזאת על כל קבוצות היעד, ובכל קבוצת יעד, על כל קבוצות המוצא. בכל קבוצת מוצא גדולה, יש לולאה שעוברת על כל המנות המחלקות את הקבוצה – עד לסיום התהליך.
  • לאחר שהסתיים הטיפול בקיצורי הדרך, מתבצעת קריאה לפונקציה שמעבירה את הפריטים שאינם מממשים קיצורי דרך.
  • מתן פלט למסך ולתיעוד על הפריטים שהועתקו.
  • אפשרות קריאה לפונקציות שמעבירות את השאריות.

כתיבת תגובה