iOS Devices with ActiveSync stop syncing shared... | MDaemon Technologies Community Forum

iOS Devices with ActiveSync stop syncing shared Calendar


  • We have a number of iOS devices with MDaemon accounts set up using ActiveSync and they all connect to a Shared calendar.  After some time the shared calendar stops syncing on these devices.  The time is random on each device (could be fine for a few days or a few weeks) but eventually the calendar no longer syncs new data. The only fix is to delete the acount on that device and add it again.  The calendar will then sync up with the new data and the whole process starts over.

    Is anyone having this same issue?  Are there any suggestions to permanently fix this issue?

    On a side note, we also have a number of Android devices set up this same way and none of them have experienced this issue we are having on the iOS devices.



  • What version of MDaemon are you using?

    What version of iOS is being used?

    What do the activesync logs show is happening when the devices stop synching?

    What version of the ActiveSync protocol are the iPhones using? 


  • We are running the latest version of MDaemon 26.0.2
    The iOS version on the iPhones is 26.4.2 on iPhone 17 Pro & Pro Max

    What exact log file should I be looking in for ActiveSync log info?
    How do I find ActiveSync version the iPhones are using?

    Thanks.


  • What exact log file should I be looking in for ActiveSync log info?

    ActiveSync log information is saved in the AirSync-$DATE$.log file in the MDaemon\Logs directory.  Make sure you have enabled debug logging for ActiveSync.  (ActiveSync | Diagnostics, set the log level to debug)

    It may also be helpful to log all XML requests and responses for accounts that are having the issues.  This will log all data that is sent between the device and the server, including email content, etc.  

    To enable it for an account go to Main | Account Manager | Select the Account | Edit | ActiveSync Client Settings, set Archive Transactions as XML to Yes.  We reccomend turning this off after you are done debugging.  

    How do I find ActiveSync version the iPhones are using?

    You can go to Main | Account Manager | Select the Account | Edit | ActiveSync Clients | Double Click the correct Client, on the dialog that opens looks for Protocol Version.  

    You can also find it in the AirSync log, it will look something like this:

    260521 100204274 I [000444BB] 0x41310044 Connection: Client Info: iPhone (2AAAAA1N9T6ZZ99BV50BBBBB98)[16.1]  PK:0 MP:No CMD:18

    The [16.1] indicates protocol version 16.1 is in use.  All of the iphone clients "should" be using 16.1.

    If you'd like help analyzing the AirSync log please upload the log for a day when a client stopped synching a shared calendar.  We'll need to know which client stopped.  Also we will want debug and xml logging enabled when the client stops synching the data.  This will give us the best chance to determine why the data is no longer being sync'd.

    You can upload the data to https://mdaemon.sharefile.com/r-rc3922c1eed334d4dbf5e34f0bd04ccd6


  • All iPhones are using the latest protocol.
    I have made the changes to the logs and when it happened again today, I have uploaded the log to you.

    Thanks.


  • Is it possible to reduce the number of items in the shared calendar?  And configure the iPhones to only sync 1 month of data?  

    There are currently about 11,000 items in the shared calendar.

    The server responds to clients with a retry command after it tries a number of times to obtain write access to the calendar.

    The logs show the iPhones are not losing the data, they are intentionally deleting it.  When the server responds with a retry command the iPhones are doing a full resync instead of just trying the last sync again.  So the phone deletes everything in the calendar locally and does a full re-sync from the server. 

    While the resync is occurring the calendar may appear empty on the iphone.  Only items that have been re-sync'd to the device will appear in the calenar. If the phone is set to sync all data, that is 11,000 items that need to be sent back to the device.  

    Reducing the number of events in the calendar would reduce the frequency of the retry commands being sent back to clients, and in the event an iPhone decided to do a full resync it would reduce the amount of time the full sync took. It also reduces the amount of time server processing takes as there are fewer items for the server to deal with, which reduces the contention on the calendar and the frequency the server responds with a retry command.

    Having the phone only sync 1 month of data further reduces the number of items that need to be sync'd, which also helps with both of these items.

    If reducing the events in the calendar is an option, I have a powershell script I can share that will help to remove old items.

    I'll also ask our development team to investigate to see if there are any server side improvements we can make.


  • Hi,

    I am currently seeing similar issues but only with certain users with the loading of calendars but on Android devices.

    What do I look for in the Airsync log to see if the phone is deleting calendar items? What controls how far back the calendar will sync? I only see a setting for how far back to sync emails unless the calendar shares this value? 


  • @Jerry 

    The original issue posted in this thread is specific to IOS devices. To help me keep things straight, please start a new thread with the following:

    What version of MDaemon are you using? 

    What is the brand and model of the Android devices?

    What do I look for in the Airsync log to see if the phone is deleting calendar items?

    You'd need to look for a device that is getting an error and then resynching entire content.  Assuming you've left the window size set at 25 and the calendar has a lot of items, you'll notice a device that is adding 25 items at a time, until it sync's however many items are in the calendar.

    It will look something like this:

    260601 085636023 I [CE6A3D78] 0x4131160C CID:CI-S-H61F9MDJ0DHQW77364CK22VK227 Server Items to Sync:25

    But with lots of other data in the log it may not be completely obvious.

    What controls how far back the calendar will sync? I only see a setting for how far back to sync emails unless the calendar shares this value? 

    It is controlled by the device.  If the androids you are using don't offer the ability to limit the amount of data that is sync'd, then it will sync the entire calendar.  The search I did indicates it is only possible on some Android devices.

    If you can enable debug logging for ActiveSync (ActiveSync | Diagnostics, set the log level to debug) and upload an AirSync log to us for an entire day when the issue occurred, we'll be happy to take a look.  Just let us know the email address of the user having the issue along with the calendar they are trying to sync.  You can upload the log to https://mdaemon.sharefile.com/r-rc3922c1eed334d4dbf5e34f0bd04ccd6.

     


  • @Paul 

    Would we be able to get a copy of the calendar folder so we can do some testing with it?

    If you are able to share it, please upload it to us.  https://mdaemon.sharefile.com/r-rc3922c1eed334d4dbf5e34f0bd04ccd6


  • @Arron 

    Thanks for the reply, I'll take a look through the airsync logs and see what I can find.

    Calendar problems are with Samsung Galaxy S21's


  • @Arron 
    I had everyone change their iPhone settings to sync their calendars to 6 months.
    The good news is that since making the change, no one has had issues with the calendar erasing itself and the sync failing.
    Unfortunately due to the nature of their job, everyone needs access to the last year of calendar data and the longest available setting is 6 months.  After that it's "Ever", which we now know is to much and fails.

    You mentioned having a script that will purge old items? Maybe that would work if everything older than 1 year can be purged?

    Also I am unable to provide a copy of a calendar folder as they contain confidential company data.


  • This script prompts you for the path to the users folder.  It will recursively prune every calendar.mrk file it finds in the directory provided and in any child folders.  If you only want to prune the calendars in the c:\mdaemon\users\domain.com\user1\calendar.IMAP folder, provide that full path.  The script does make a backup of every MRK file before it starts pruning.  On line 227 of the script, you set how many years to keep.  For example if you enter -2, then any event that is older than 2 years from today will be removed.  Recurring events are only removed if the last occurence of the event is more than X years ago, where X is the value provided.

    The script will also remove ICS, TNEF, and attachments associated with an event that is being pruned.

    If quotas are enabled, the script will also force MDaemon to recalculate the quotas for any account that has the calendar pruned.  

    I would highly reccomend that you test this script out in a test environment to be sure you it is going to do what you want and provided the desired outcome.

    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.  

    #########################################################################################################################
    #########################################################################################################################
    ## Prune Calendar Events  V1.4                                                                                                                                                                                                              ##
    ## March 5, 2024    						    						                                                      ##
    ## Copyright MDaemon Technologies	2024											                                          ##
    ## 	                                                                                                                          ##
    ## 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, attachments, and 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($File, $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($File, $Event){
        if($Event.PersistentData.DAVDataFile)
        {
            $ICSFileDir = Join-Path $File.DirectoryName "_DAV"
    
            if($Event.PersistentData.DAVDataFile.'#cdata-section')
            {
                #Write-Host "Found CDATA section setting the EndDateTime variable to the CDATA value."
                $ICSFile = Join-Path $ICSFileDir $Event.PersistentData.DAVDataFile.'#cdata-section'
            }
            else
            {
                #Write-Host "If there is no CDATA section then just get the value."
                $ICSFile = Join-Path $ICSFileDir $Event.PersistentData.DAVDataFile
            }
    
            if(Test-Path $ICSFile)
            {
                Write-Host "Removing $ICSFile"
                Remove-Item $ICSFile -force -ErrorAction SilentlyContinue
            }
        }
    }
    
    function RemoveAttachmentFile($File, $Event){
        if($Event.Attachment)
        {
            $AttachmentDirectory = Join-Path $File.DirectoryName "_attachments"
            ForEach($Attachment in $Event.Attachment){
               
                #Write-Host ""
                $AttachmentFile = Join-Path $AttachmentDirectory $Attachment.LocalFile
     
                if(Test-Path $AttachmentFile)
                {
                    Write-Host "Removing $AttachmentFile"
                    Remove-Item $AttachmentFile -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
    
    $PathtoUserFolders = "c:\bad"
    
    While(!(Test-Path $PathtoUserFolders))
    {
        Write-Host "The path to the user folders does not exist."
        
        $PathtoUserFolders = Read-Host "Please enter the path to the user folders, for example, C:\MDaemon\Users. Any Calendar.MRK files found in the path entered will be processsed and content could be removed. If you need to exit enter Exit"
        
        if($PathtoUserFolders -like "Exit"){
            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 -File)
    {
        $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"
        $null = 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)
        {
            #$Event.ID
            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 $File $Event
                        }
                        
                        if($Event.PersistentData.DAVDataFile)
                        {
                            RemoveICSFile $File $Event
                        }
    
                        if($Event.Attachment){
                            RemoveAttachmentFile $File $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 $File $Event
                    }
                    if($Event.PersistentData.DAVDataFile)
                    {
                        RemoveICSFile $File $Event
                    }
                    if($Event.Attachment){
                        RemoveAttachmentFile $File $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."
    }
    
    
    

    Let us know if you have any questions or if there is anything we can do to improve the script.


Please login to reply to this topic!