ניקוי כפילויות מתוך תיבות דואר

ניקוי כפילויות מתוך תיבות דואר

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

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

בעיית כפילויות:

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

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

פתרון:

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

את הסקריפט יש להריץ באמצעות PowerShell במחשב שבו מותקן אאוטלוק שבו טעונה במלואה תיבת הדואר שאותה רוצים לנקות. תיבה טעונה במלואה  (Full Cache Mode) נדרשת על מנת לנקות את כל הכפילויות בתיבה.

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

כעת אציג את הסקריפט, ולאחריו אסביר את פעולתו.

הסקריפט:

$user = 'user@domain.suffix

<# This function does logging if 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, cleaning duplicated items in the folder.
At first it's making index of duplicates, than call another function for the actual cleaning.
Parameters:
$folder: represent the folder to work inside. #>
function Clean-Folder {
    param ($Folder)


    if ($Folder.name -in $global:exlude) {
        break 
    } elseif ($Folder.name -eq 'Deleted Items') {
        $global:final = $Folder
    }


    if ($Folder.Folders.Count -gt 0) {        
        foreach ($x in $Folder.Folders) {
            Clean-Folder -Folder $x
        }
    }
    
    Print-Output -a 0 -b 1 -res $Folder.folderpath
    $list = $Folder.items | select senton, subject
    $duplicates = $list | Group-Object -Property senton, subject | ?{$_.count -gt 1}
    $allduplicates = @() 
    $tempgroup = @()
    $tmpcount = 0
    $counter = 0
    foreach ($group in $duplicates) {
        if (($counter -gt 1000) -or ($group -eq $duplicates[-1])) {
            $tempgroup += $group.Group
            $tempgroup | Add-Member -MemberType NoteProperty -Name 'count' -Value ($tmpcount += 1) -Force
            $allduplicates += $tempgroup
            $tempgroup = @()
            $counter = 0
        }
        $tempgroup += $group.Group
        $counter += $group.count 
    }
    
    $allduplicates = $allduplicates | Group-Object -Property count
    foreach ($dup in $allduplicates) {
        $messages = $folder.Items | ?{$_.senton -in $dup.group.senton} | ?{$_.subject -in $dup.group.subject}
        $duplicates = $messages | Group-Object -Property senton, subject


        foreach ($group in $duplicates) {
        $count = 0
        While ($count -lt ($group.count -1)) {
          
            if (($group.Group | ?{$_.MessageClass -EQ "IPM.Note.EnterpriseVault.Shortcut"}) -ne $null) {
                if (($group.Group | ?{$_.MessageClass -EQ "IPM.Note.EnterpriseVault.Shortcut"}).count -gt 1) {
                    ($group.Group | ?{$_.MessageClass -EQ "IPM.Note.EnterpriseVault.Shortcut"})[$count].delete()
                } else {
                    ($group.Group | ?{$_.MessageClass -EQ "IPM.Note.EnterpriseVault.Shortcut"}).delete()
                }    
            } else {
                $group.Group[$count].delete()
            }
            $count += 1            
        }
        }    
    }   
}

# Configuring Outlook MailBox and PST as an available object to work with.
$outlook = New-Object -com Outlook.Application
$namespace = $outlook.GetNamespace("MAPI")
$mailbox = $namespace.Stores | ? {$_.displayname -like $user}
# Veriable points to the root folder of the MailBox.
$global:mailboxRoot = $mailbox.GetRootFolder()
# Folders that irrelevant.
$global:exlude = 'RSS Subscriptions', 'Quick Step Settings', 'Sync Issues', 'Conversation Action Settings', 'Yammer Root', 'Recipient Cache'


# Contains output messages To print on screen and log in file, for monitoring purpes.
$global:output = "===================================", "         Cleaning folder"


Clean-Folder -Folder $global:mailboxRoot
Clean-Folder -Folder $global:final'

פעולת הסקריפט על פי שלבים:

הגדרה ופונקציות:

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

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

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

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

מיון וחלוקת מקטעים:

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

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

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

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

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

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

עבודה במקטעים:

9.     התהליך עובר על כל קבוצת-אלף שכזו.

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

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

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

הליך הניקוי:

13. איפוס מונה.

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

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

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

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

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

18. אם יש רק קיצור דרך אחד, פשוט מוחקים אותו.

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

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

טיפול סופי:

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

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

כתיבת תגובה