Calender Settings / MDaemon Connector / Outlook | MDaemon Technologies, Ltd.

Calender Settings / MDaemon Connector / Outlook


  • Hello,

    It is possible to automatically delete old calendar entries via MDaemon Connector.
    I tried auto-archiving to 24 months in the calendar properties in Outlook Set the option to “permanently
    delete old items”. Unfortunately this doesn't work.

    What I notice when setting up user accounts is that it always shows all calendar entries loaded, with us
    depending on the calendar entries between 1000-70000.

    Here I would like to have a calendar setting in the MDaemon connector where you can set up settings
    for the existing calendar or public calendar.

    - 2 weeks
    - last month
    - 3 months
    - 6 months
    - All

    Best regards, Martin



  • There are no settings in MDaemon Connector to prune old calendar entries, but we do have a PoweShell script you run on the server to prune them.  This script will automatically try to find the mailbox directories by looking at the default path for the New Account template, if that is not the correct location, you'll have to alter the script by setting $PathtoUserFolders to the correct path on line 243.  243 is current a blank line, setting the $PathtoUserFolders at this location will overwrite the value obtained from the AccountTemplates.dat file.  If you set the value to d:\mdaemon\users\, the script will recursively find all calendars and prune them.  If you want to run it on a single account or single calendar you should point it directly to the account or calendar.  For example, c:\mdaemon\users\company.test\arron\calendar.imap.

    By default the script will remove any calendar entries that are older than 3 years.  You can adjust this by changing the $Years variable on line 254.  The value is in years and must be a whole integer. If you set the value to 0 it will delete all entries. 

    As alwasy, I highly reccomend that you thoroughly test this script and make backups before running it on your live mailboxes.

    Let us know if you have any questions.

    ########################################################################################################################
    ########################################################################################################################
    ## Prune Calendar Events  V1.1                                                                                                ##
    ## September 19, 2022   												                                                      ##
    ## Copyright MDaemon Technologies	2022											                                          ##
    ## 	                                                                                                                          ##
    ## This script is designed prune events from the Calendar.mrk file that are older than X years. It searches the default       ##
    ## mail path configured for New Account templates and checks each Calendar.mrk file for old events. It checks for recurrence  ##
    ## removes the associated .MSG files, and removes the associated .ICS files.  A backup is made of the Calendar.mrk file       ##
    ## before any changes are made to it. It is reccomended that you backup your entire user folder structure before running      ##
    ## this script.                                                                                                               ##
    ##                                                                                                                            ##
    ## MDaemon Technologies offers no warranty, provides no support, and is not responsible for any damages that may arise from   ##
    ## the use of this script. Use at your own risk.                                                                              ##
    ##                                                                                                                            ##
    ########################################################################################################################
    ########################################################################################################################
    
    Function Get-IniContent {
        <#
        .Synopsis
            Gets the content of an INI file
    
        .Description
            Gets the content of an INI file and returns it as a hashtable
    
        .Notes
            Author		: Oliver Lipkau <oliver@lipkau.net>
    		Source		: https://github.com/lipkau/PsIni
                          http://gallery.technet.microsoft.com/scriptcenter/ea40c1ef-c856-434b-b8fb-ebd7a76e8d91
            Version		: 1.0.0 - 2010/03/12 - OL - Initial release
                          1.0.1 - 2014/12/11 - OL - Typo (Thx SLDR)
                                                  Typo (Thx Dave Stiff)
                          1.0.2 - 2015/06/06 - OL - Improvment to switch (Thx Tallandtree)
                          1.0.3 - 2015/06/18 - OL - Migrate to semantic versioning (GitHub issue#4)
                          1.0.4 - 2015/06/18 - OL - Remove check for .ini extension (GitHub Issue#6)
                          1.1.0 - 2015/07/14 - CB - Improve round-tripping and be a bit more liberal (GitHub Pull #7)
                                               OL - Small Improvments and cleanup
                          1.1.1 - 2015/07/14 - CB - changed .outputs section to be OrderedDictionary
    
            #Requires -Version 2.0
    
        .Inputs
            System.String
    
        .Outputs
            System.Collections.Specialized.OrderedDictionary
    
        .Parameter FilePath
            Specifies the path to the input file.
    
        .Parameter CommentChar
            Specify what characters should be describe a comment.
            Lines starting with the characters provided will be rendered as comments.
            Default: ";"
    
        .Parameter IgnoreComments
            Remove lines determined to be comments from the resulting dictionary.
    
        .Example
            $FileContent = Get-IniContent "C:\myinifile.ini"
            -----------
            Description
            Saves the content of the c:\myinifile.ini in a hashtable called $FileContent
    
        .Example
            $inifilepath | $FileContent = Get-IniContent
            -----------
            Description
            Gets the content of the ini file passed through the pipe into a hashtable called $FileContent
    
        .Example
            C:\PS>$FileContent = Get-IniContent "c:\settings.ini"
            C:\PS>$FileContent["Section"]["Key"]
            -----------
            Description
            Returns the key "Key" of the section "Section" from the C:\settings.ini file
    
        .Link
            Out-IniFile
        #>
    
        [CmdletBinding()]
        Param(
            [ValidateNotNullOrEmpty()]
            [ValidateScript({(Test-Path $_)})]
            [Parameter(ValueFromPipeline=$True,Mandatory=$True)]
            [string]$FilePath,
            [char[]]$CommentChar = @(";"),
            [switch]$IgnoreComments
        )
    
        Begin
        {
            Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"
            $commentRegex = "^([$($CommentChar -join '')].*)$"
        }
    
        Process
        {
            Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath"
    
            $ini = New-Object System.Collections.Specialized.OrderedDictionary([System.StringComparer]::OrdinalIgnoreCase)
            $commentCount = 0
            switch -regex -file $FilePath
            {
                "^\s*\[(.+)\]\s*$" # Section
                {
                    $section = $matches[1]
                    $ini[$section] = New-Object System.Collections.Specialized.OrderedDictionary([System.StringComparer]::OrdinalIgnoreCase)
                    $CommentCount = 0
                    continue
                }
                $commentRegex # Comment
                {
                    if (!$IgnoreComments)
                    {
                        if (!(test-path "variable:section"))
                        {
                            $section = "_"
                            $ini[$section] = New-Object System.Collections.Specialized.OrderedDictionary([System.StringComparer]::OrdinalIgnoreCase)
                        }
                        $value = $matches[1]
                        $CommentCount++
                        $name = "Comment" + $CommentCount
                        $ini[$section][$name] = $value
                    }
                    continue
                }
                "(.+?)\s*=\s*(.*)" # Key
                {
                    if (!(test-path "variable:section"))
                    {
                        $section = "_"
                        $ini[$section] = New-Object System.Collections.Specialized.OrderedDictionary([System.StringComparer]::OrdinalIgnoreCase)
                    }
                    $name,$value = $matches[1..2]
                    $ini[$section][$name] = $value
                    continue
                }
            }
            Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing file: $FilePath"
            Return $ini
        }
    
        End
            {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"}
    }
    
    function ConverttoSystemDateTime($DTString){
    
        #Split the EndDateTime variable to a Date and a Time, the Time is Trash here.    
        #$EndDate,$Trash = Split-String -Input $DTString -Separator " "
        
        $EndDate,$Trash = $DTString -split " "
        
        #Split the Date into year, month, and day values.
        #$EndYear,$EndMonth,$EndDay = Split-String -Input $EndDate -Separator "-"
    
        $EndYear,$EndMonth,$EndDay = $EndDate -split "-"
    
        #Using the year, month, and day values create a new EndDate object of type System.DateTime
        $EndDate = New-Object System.DateTime $EndYear,$EndMonth,$EndDay 
    
        return $EndDate
    }
    
    function RemoveTNEFFile($Event){
    
        if($Event.TNEF)
        {
            if($Event.TNEF.'#cdata-section')
            {
                #Write-Host "Found CDATA section setting the EndDateTime variable to the CDATA value."
                $TNEFFile = Join-Path $File.DirectoryName $Event.TNEF.'#cdata-section'
            }
            else
            {
                #Write-Host "If there is no CDATA section then just get the value."
                $TNEFFile = Join-Path $File.DirectoryName $Event.TNEF
            }
    
            if(Test-Path $TNEFFile)
            {
                Write-Host "Removing $TNEFFile"
                Remove-Item $TNEFFile -force -ErrorAction SilentlyContinue
            }
        }
    }
    
    function RemoveICSFile($Event){
        if($Event.DAVDataFile)
        {
            $ICSFile = Join-Path $File.DirectoryName "_DAV"
    
            if($Event.DAVDataFile.'#cdata-section')
            {
                #Write-Host "Found CDATA section setting the EndDateTime variable to the CDATA value."
                $ICSFile = Join-Path $ICSFile $Event.DAVDataFile.'#cdata-section'
            }
            else
            {
                #Write-Host "If there is no CDATA section then just get the value."
                $ICSFile = Join-Path $ICSFile $Event.DAVDataFile
            }
    
            if(Test-Path $ICSFFile)
            {
                Write-Host "Removing $ICSFFile"
                Remove-Item $ICSFFile -force -ErrorAction SilentlyContinue
            }
        }
    }
    
    #Get the path to the MDaemon\app folder.
    if(Test-Path "HKLM:\SOFTWARE\Alt-N Technologies\MDaemon")
    {
        $MDAPPPath = (Get-ItemProperty "HKLM:\SOFTWARE\Alt-N Technologies\MDaemon" -Name AppPath).AppPath
        $MDINIPath = (Get-ItemProperty "HKLM:\SOFTWARE\Alt-N Technologies\MDaemon" -Name IniPath).IniPath
    }
    elseif(Test-Path "HKLM:\SOFTWARE\Wow6432Node\Alt-N Technologies\MDaemon")
    {
        $MDAPPPath = (Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Alt-N Technologies\MDaemon" -Name AppPath).AppPath
        $MDINIPath = (Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Alt-N Technologies\MDaemon" -Name IniPath).IniPath
    }
    else
    {
        Write-Host "Oh No, I couldn't find the registry keys for MDaemon!"
        Write-Host "The script will now exit."
        Exit
    }
    
    $AccountTemplatePath = Join-Path $MDAPPPath "AccountTemplates.dat"
    
    $MDINIContent = Get-IniContent $MDINIPath
    $AccounteTemplatesContent = Get-IniContent $AccountTemplatePath
    
    #Path to the MDaemon\Users folder.
    $PathtoUserFolders = $AccounteTemplatesContent["New Accounts"]["RootMailPath"]
    $PathtoUserFolders = Split-Path $PathtoUserFolders
    $PathtoUserFolders = Split-Path $PathtoUserFolders
    
    write-Host "The path to the users folders is set to $PathtoUserFolders"
    
    if(!(Test-Path $PathtoUserFolders))
    {
        Write-Host "The path to the user folders does not exist.  Exiting."
        Exit
    }
    
    #Number of years to keep.  This should be a negative value.  If you wnat to remove events that ended more than 2 years
    #ago set the value to -2."
    $Years = -3
    
    if($Years -ge 0)
    {
        Write-Host "The Years variable needs to be set to a negative value. $Years"
        Exit
    }
    
    $ClearQuotaCounts = ""
    
    #Searches the directory structure for Calendar.mrk files.
    ForEach ($File in Get-ChildItem -Path $PathtoUserFolders -Include Calendar.mrk -Recurse)
    {
        $AddUsertoClearQuotaCountsSEM = "No"
    
        #Locking the MRK file so it can't be changed by anything else."
        $LockMRKFile = Join-Path $File.DirectoryName "Calendar.lck"
        Write-Host "Creating lock file for Calendar.mrk"
        New-Item $LockMRKFile -type File -Force
    
        #Making a backup copy of the MRK file, just in case."
        $BackupMRK = Join-Path $File.DirectoryName "Calendar.mrk.bck"
        Write-Host "Making a backup of the Calendar.mrk file." 
        Copy-Item $File $BackupMRK -Force
            
        Write-Host "Loading $File"
        $Data = [Xml] (Get-Content $File)
        $MyDate = Get-Date
    
        $EndDateTime = $null
        $EndDate = $null
    
        #Looks at each event in the calendar to determine if it should be removed.
        foreach($Event in $Data.Calendar.event)
        {
    
            if($Event.End.'#cdata-section')
            {
                #Write-Host "Found CDATA section setting the EndDateTime variable to the CDATA value."
                $EndDateTime = $Event.End.'#cdata-section'
            }
            else
            {
                #Write-Host "If there is no CDATA section then just get the value."
                $EndDateTime = $Event.End
            }
        
            $EndDate = ConverttoSystemDateTime $EndDateTime
           
            #if the end date on the event is more than X years ago.
            if($EndDate -lt ($MyDate.AddYears($Years)))
            {
                #If the event is recurring
                if($Event.Recurrence)
                {
    
                    $RecurrEndDate = ConverttoSystemDateTime $Event.Recurrence.End
         
                    #If the recurrence ends more than X years ago
                    if($RecurrEndDate -lt ($MyDate.AddYears($Years)))
                    {
                        $AddUsertoClearQuotaCountsSEM = "Yes"
    
                        #Removing event and associated files because the last ocurrence of the event was more than 3 years ago.
                        Write-Host "Removing recurring event" $Event.ID
                        $Event.ParentNode.RemoveChild($Event)
                        
                        if($Event.TNEF)
                        {
                            RemoveTNEFFile($Event)
                        }
                        
                        if($Event.DAVDataFile)
                        {
                            RemoveICSFile($Event)
                        }
                    }
                    #If the recurrence do nothing. 
                    else
                    {
                        #Not doing anything here because the recurring event is not older than the date set.
                    }
                
                }
                else
                {
                    $AddUsertoClearQuotaCountsSEM = "Yes"
                    
                    #Removing Event and associated files because the event was more than 3 years ago.
                    Write-Host "Removing event" $Event.ID
                    $Event.ParentNode.RemoveChild($Event)
    
                    if($Event.TNEF)
                    {
                        RemoveTNEFFile($Event)
                    }
                    if($Event.DAVDataFile)
                    {
                        RemoveICSFile($Event)
                    }
                }
                
            }    
        }
    
        $Output = Join-Path $File.DirectoryName "MRKOutput.mrk"
    
        Write-Host "Writing out new file at $File"
        $Data.save($File)
    
        Remove-Item $LockMRKFile -Force
    
        if($AddUsertoClearQuotaCountsSEM -eq "Yes")
        {
            #Building the email address of the user.
            $Mailbox = Split-Path -Path (Split-Path -Path $File.Directory) -Leaf
            $Domain = Split-Path -Path (Split-Path -Path (Split-Path -Path $File.Directory)) -Leaf
    
            $Email = $Mailbox + "@" + $Domain
    
            Write-Host "$Email will be added to the reloadquotacounts.sem file."
            $ClearQuotaCounts = $ClearQuotaCounts + $Email + "`r`n"
        }
    }
    
    $QuataCountsSub = $MDINIContent["Special"]["QuotaCountsSubFolders"]
    $CountGroupwareFolders = $MDINIContent["Special"]["CountGroupwareFolders"]
    
    if($QuataCountsSub -eq "Yes" -and $CountGroupwareFolders -eq "Yes")
    {
        $SemPath = Join-Path $MDAPPPath "ClearQuotaCounts.sem"
    
        Write-Host "Creating SEM files to update Quota Caches."
        New-Item -Path $SemPath -type File -Value $ClearQuotaCounts -Force
    }
    else
    {
        Write-Host "Quotas are not configured to count Groupware folder or sub folders so the cache will not be updated."
    }
    
    
    

     


  • Hello Arron,

    thank you very much for the PS script.

    I changed line 243:
    write-Host "C:\MDaemon\Users\company.test\test\Kalender.IMAP"

    The following error message then appears, but after that it continues.

     

    C:\MDaemon\Users\company.test\test\Kalender.IMAP
    Test-Path : The argument cannot be bound to the "Path" parameter because it is NULL.
    In C:\MDaemon-Kalender-CleanUP.ps1:245 Zeichen:16
    + if(!(Test-Path $PathtoUserFolders))
    +                ~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidData: (:) [Test-Path], ParameterBindingValidationException
        + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.TestPathCommand
     
    Creating lock file for Calendar.mrk
    
    
        Verzeichnis: C:\mdaemon\Public Folders\company.test.IMAP\Kalender.IMAP
    
    
    Mode                LastWriteTime         Length Name                                                                                                                                
    ----                -------------         ------ ----                                                                                                                                
    -a----       14.10.2023     13:08              0 Calendar.lck                                                                                                                        
    Making a backup of the Calendar.mrk file.
    Loading C:\mdaemon\Public Folders\company.test.IMAP\Kalender.IMAP\Calendar.mrk
    Writing out new file at C:\mdaemon\Public Folders\company.test.IMAP\Kalender.IMAP\Calendar.mrk
    Creating lock file for Calendar.mrk
    

    Why is he going to User02 here?

    
        Verzeichnis: C:\mdaemon\Users\company.test\user02\Kalender.IMAP
    
    
    Mode                LastWriteTime         Length Name                                                                                                                                
    ----                -------------         ------ ----                                                                                                                                
    -a----       14.10.2023     13:08              0 Calendar.lck                                                                                                                        
    Making a backup of the Calendar.mrk file.
    Loading C:\mdaemon\Users\company.test\user02\Kalender.IMAP\Calendar.mrk
    Writing out new file at C:\mdaemon\Users\company.test\user02\Kalender.IMAP\Calendar.mrk
    Creating lock file for Calendar.mrk
    
    
        Verzeichnis: C:\mdaemon\Users\company.test\test\Kalender.IMAP
    
    
    Mode                LastWriteTime         Length Name                                                                                                                                
    ----                -------------         ------ ----                                                                                                                                
    -a----       14.10.2023     13:08              0 Calendar.lck                                                                                                                        
    Making a backup of the Calendar.mrk file.
    Loading C:\mdaemon\Users\company.test\test\Kalender.IMAP\Calendar.mrk
    Writing out new file at C:\mdaemon\Users\company.test\test\Kalender.IMAP\Calendar.mrk
    Quotas are not configured to count Groupware folder or sub folders so the cache will not be updated.
    
    

    Best regards, Martin


  • Write-Host only writes the value in quotes to the screen.  

    Instead of using this on line 243:

    write-Host "C:\MDaemon\Users\company.test\test\Kalender.IMAP"

    You need to use this:

    $PathtoUserFolders = "C:\MDaemon\Users\company.test\test\Kalender.IMAP"

    This should be added just after the second occurrence of this line:

    $PathtoUserFolders = Split-Path $PathtoUserFolders

    And in case you are wondering why that line is there twice, its because its splitting the last folder off the path and there are two folders that needed to be removed.

     

     


  • Hello Arron,

    I tested it, it works.
    My series elements are retained, that's good.

    Is this feature planned for a future version?

    Martin


  • At this time we do not have plans to expand the functionality.


Please login to reply this topic!