Synology Cloud Station Client Can’t Handle Read-Only Files

Script Updated 2017-03-01

I picked up a Synology NAS (DS411+ii) years ago and it’s still alive and kicking today, mostly due to the rock-solid hardware and the amazing improvements in DSM.

I’ve been slowly working on weaning myself off services like DropBox, OneDrive, Google Drive to rely fully on the NAS but it certainly hasn’t been without it’s struggles.

I setup Cloud Station on the NAS and installed the Cloud Station Client (CSC) on my home machines to sync key directories on the machine to the NAS.  Initially things worked well, but as I added more data – like 300GB of data – I noticed things were not synchronizing correctly.  Upon further investigation, the Cloud Station Client (CSC) was having trouble processing read-only files.

So here’s my operation:

  1. I work on system A, generating files in a synchronized directory
  2. The CSC syncs the data up to the NAS and all is well.
  3. I jump to system B and setup CSC which syncs the data down from the NAS

Both systems now have identical sets of data.

  1. I modify a handful of read-only files on system B which gets synced up to the NAS
  2. I switch to system A and the read-only files are now updated with copies from the NAS.
  3. I make changes to the same read-only files on system A which get synched up to the NAS
  4. I switch back to system B and CSC is up in arms because “FILENAME cannot be synced due to access permission denied, or it is in use.”

In this state, a file sync queue builds because CSC is hung up on the handful of files that it cannot overwrite locally because they’re read-only.  I thought it was going to fix itself so I left it in this state for a while until I realized changes I made were not reaching the other system.  That’s when I noticed the file sync queue was something like 90k and CSC needed some help.

The other cloud sync services don’t fall prey to this issue, so it’s a little strange that the CSC isn’t able to handle it.  I took to the Synology forums and not only found I wasn’t the only one experiencing this problem, but that someone wrote a small script to address this issue.  Score!

I ran the script which mostly worked but I ran into some odd problems.  Since I had nothing better to do, I expanded on it, adding some checks & balances, visual feedback and I tried to add some error handling.

It may not be perfect, but it resolved the issues I was running into and provides meaningful output.


[CmdletBinding()]
Param
    (
        # How long we want to give the client to catch up
        # Note, this is a calculated timeout based on this + the number of files it found that needed fixing
        # In my testing, this worked better than a fixed number that was either
        #     insanely high for a low number of files
        #     too low for a high number of files
        [Parameter(Mandatory=$false)]
            [int]$CloudStationClientCatchUpTimeOut = 60,

        # How long we wait before restarting the loop
        [Parameter(Mandatory=$false)]
            [int]$RestartFixTimeOut = 30,

        # Creation of this file will allow you to:
        #    safely stop the script, and
        #    un-fix (read: re-apply the read-only attribute) on files it already processed
        [Parameter(Mandatory=$false)]
            [string]$StopMonitoringFlagFile = "$env:LOCALAPPDATA\CloudStation\log\StopMonitoring.txt",

        # This is the path to the CloudStation log file; you should't have to change this.
        [Parameter(Mandatory=$false)]
            [string]$CSDaemonLog = $env:LOCALAPPDATA + '\CloudStation\log\daemon.log',

        # Use this for debugging purposes only.
        [Parameter(Mandatory=$false)]
            [bool]$DebugEnabled = $false
    )

###############################################################
###          DON'T CHANGE ANYTHING BELOW THIS LINE          ###
###############################################################

##*=============================================
##* VARIABLE DECLARATION
##*=============================================
#region VariableDeclaration

# Master Counter - Number of times the script ran
[int]$Global:Loop = 0

# Counter for number of files fixed
[int]$TotalNumberOfFilesFixed = 0

# Global Variable for Debug Mode
[bool]$Global:DebugEnabled = $DebugEnabled
if($Global:DebugEnabled -eq $true) { write-host "$(Get-Date -Format s) - [$Global:Loop] :: DEBUG :: DEBUG MODE HAS BEEN ENABLED [$Global:DebugEnabled]" }

#endregion VariableDeclaration
##*=============================================
##* END VARIABLE DECLARATION
##*=============================================

##*=============================================
##* FUNCTION LISTINGS
##*=============================================
#region FunctionListings

#region Function Pause-Script
Function Pause-Script
    {
        Param
            (
                [Parameter(Mandatory=$false)]
                    [string]$MSG,

                [Parameter(Mandatory=$false)]
                    [string]$Title
            )

        if([string]::IsNullOrEmpty($MSG)) { $MSG = 'Script Paused.  Press any key to continue.' }
        if([string]::IsNullOrEmpty($Title)) { $Title = 'Script Paused.  Press any key to continue.' }

        if($host.Name -notmatch 'ISE')
            {
                write-host `r`n`r`n$MSG
                $HOST.UI.RawUI.ReadKey(“NoEcho,IncludeKeyDown”) | Out-Null
                $HOST.UI.RawUI.Flushinputbuffer()
            }
        Else
            {
                [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
                [System.Windows.Forms.MessageBox]::Show($MSG,$Title,[System.Windows.Forms.MessageBoxButtons]::OK) | Out-Null
            }
    }
#endregion #region Function Pause-Script

#region Function Toggle-ReadOnly
Function Toggle-ReadOnly
    {
        [CmdletBinding()]
        Param
            (
	            [Parameter(Mandatory=$true)]
                [ValidateScript({If(Test-Path $_){$true}else{Throw "Invalid path given: [$_]"}})]
                    [string[]]$File,

                [Parameter(Mandatory=$false)]
                    [switch]$MakeReadOnly
            )

        Begin {  }

        Process
            {
                Foreach($Item in $File)
                    {
                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Item [$Item]" }
                        [string]$ItemFixed = $Item.ToString().Replace('"','')
                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: ItemFixed [$ItemFixed]" }

                        If(Test-Path -Path $ItemFixed)
                            {
                                Try { (Get-ChildItem -Path "$ItemFixed" -Force -ErrorAction Stop).IsReadOnly = $MakeReadOnly; $Return = $true } Catch { write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR TOGGLING READONLY ATTRIBUTE ON [$ItemFixed]: [$_]"; $Return = $_ }
                            }
                        Else { write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR DID NOT FIND [$ItemFixed]" }
                    }
            }

        End { return $Return }
    }
#endregion Function Toggle-ReadOnly

#endregion FunctionListings
##*=============================================
##* END FUNCTION LISTINGS
##*=============================================

##*=============================================
##* SCRIPT BODY
##*=============================================
#region ScriptBody

Do
    {
        # Increment Counter
        [int]$Global:Loop++ | Out-Null

        # Reset these at the start of each loop
        # This is the abort flag
        [bool]$Abort = $false

        # This is the error flag
        [bool]$CriticalErrorEncountered = $false

        # This is the number of files we need to fix this time raound
        [int]$CountOfFilesToFix = 0

        # This is the array that holds the files we've successfully processed
        [string]$arrProcessed = $null;[System.Collections.ArrayList]$arrProcessed = @()

        if(Test-Path -Path $StopMonitoringFlagFile -PathType Leaf) { [bool]$Abort = $true }

        if(!(Test-Path $CSDaemonLog -PathType Leaf)) { write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR MISSING CLOUD STATION DAEMON LOG [$CSDaemonLog]"; $CriticalErrorEncountered = $true; continue }

        Try
            {
                write-host "$(get-date -Format s) - [$Global:Loop] :: Ingesting log [$CSDaemonLog]"
                $CSDaemonLogFullContent = $null;$CSDaemonLogFullContent = Get-Content $CSDaemonLog -ErrorAction Stop

                ##############################################################################
                #                             SUPER DEBUG                                    #
                # Only enable this if you're seeing //really// strange or unexpected results #
                <#                 if($Global:DebugEnabled -eq $true)                     {                         write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: CSDaemonLogFullContent BEGIN>>>>"
                        write-host $CSDaemonLogFullContent
                        write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: CSDaemonLogFullContent END<<<<"                     }                 #>
                ##############################################################################

                Try
                    {
                        write-host "$(get-date -Format s) - [$Global:Loop] :: Checking ingested log content for errors"
                        # Only grab lines that contain '[Error]' since those are the lines we want to focus on.
                        $CSDaemonLogContent = $null; [System.Collections.ArrayList]$CSDaemonLogContent = {$CSDaemonLogFullContent | ? { $_ -match [regex]::Escape('[Error]') }}.Invoke()

                        ##############################################################################
                        #                             SUPER DEBUG                                    #
                        # Only enable this if you're seeing //really// strange or unexpected results #
                        <#                         if($Global:DebugEnabled -eq $true)                             {                                 write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Number of items in `$CSDaemonLogContent BEFORE refinement [$($CSDaemonLogContent.Count)]"                                 write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: CSDaemonLogContent BEGIN>>>>"
                                for($i=0; $i -lt $CSDaemonLogContent.Count;$i++) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: CSDaemonLogContent[$i] $($CSDaemonLogContent[$i])" }
                                write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: CSDaemonLogContent END<<<<"                             }                         #>
                        ##############################################################################

                        # However, there are certain types of errors we can likely ignore since they don't specifically speak to the read-only issue this script aims to resolves.
                        $arrErrorsToIgnore = @('*channel error while connecting to server*',
                                               '*Failed to remove local signature*',
                                               '*Failed to write magic*',
                                               '*Failed to send protocol header.*',
                                               '*Failed to send protocol.*'
                                               )
                                               <#                                                ,                                                'Failed to prepare file block for',                                                'Failed to read file for'                                                #>

                        # Loop through the errors to ignore and remove them from the array if they match
                        # At the same time we'll eliminate any lines that don't look like they contain a real drive letter (*:\*)
                        foreach($Line in @($CSDaemonLogContent))
                            {
                                [bool]$IgnoreLine = $false
                                foreach($ErrorToIgnore in $arrErrorsToIgnore)
                                    {
                                        ##############################################################################
                                        #                             SUPER DEBUG                                    #
                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: [$i][$ErrorToIgnore][$Line]" }
                                        ##############################################################################

                                        if(($Line -like $ErrorToIgnore) -eq $true)
                                            {
                                                $IgnoreLine = $true
                                                ##############################################################################
                                                #                             SUPER DEBUG                                    #
                                                # Only enable this if you're seeing //really// strange or unexpected results #
                                                #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Ignoring [$IgnoreLine] because [$ErrorToIgnore] was found in: [$Line] " }
                                                ##############################################################################
                                            }
                                        else
                                            {
                                                ##############################################################################
                                                #                             SUPER DEBUG                                    #
                                                # Only enable this if you're seeing //really// strange or unexpected results #
                                                #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Proceeding normally because [$ErrorToIgnore] was not found in: [$Line]" }
                                                ##############################################################################
                                            }
                                    }

                                # No need to check if we're already ignoring the line
                                if($IgnoreLine -ne $true)
                                    {
                                        ##############################################################################
                                        #                             SUPER DEBUG                                    #
                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Performing drive check (*:\*) because IgnoreLine is not true [$IgnoreLine]" }
                                        ##############################################################################

                                        # Check to ensure the line contains :\ which is more than likely a real path, otherwise we can skip it
                                        if($Line -notlike '*:\*')
                                            {
                                                $IgnoreLine = $true
                                                ##############################################################################
                                                #                             SUPER DEBUG                                    #
                                                # Only enable this if you're seeing //really// strange or unexpected results #
                                                #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Ignoring [$IgnoreLine] because it doesn't appear to correspond to a local path: [$Line] " }
                                                ##############################################################################
                                            }
                                        else
                                            {
                                                ##############################################################################
                                                #                             SUPER DEBUG                                    #
                                                # Only enable this if you're seeing //really// strange or unexpected results #
                                                #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Proceeding normally because it appears to correspond to a local path: [$Line]" }
                                                ##############################################################################

                                                # If it does not have slashes, it's not a file path so we can ignore it
                                                if(-not $Matches[1].Contains('/'))
                                                    {
                                                        $IgnoreLine = $true
                                                        ##############################################################################
                                                        #                             SUPER DEBUG                                    #
                                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Ignoring [$IgnoreLine] because it doesn't contain a '/': [$Line]" }
                                                        ##############################################################################
                                                    }
                                                else
                                                    {
                                                        ##############################################################################
                                                        #                             SUPER DEBUG                                    #
                                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Proceeding normally because it does contain a '/': [$Line]" }
                                                        ##############################################################################
                                                    }
                                            }
                                    }
                                else
                                    {
                                        ##############################################################################
                                        #                             SUPER DEBUG                                    #
                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Skipping Drive (*:\*) check because IgnoreLine is already true [$IgnoreLine]" }
                                        ##############################################################################
                                    }

                                if($IgnoreLine -eq $true)
                                    {
                                        ##############################################################################
                                        #                             SUPER DEBUG                                    #
                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: IgnoreLine is true [$IgnoreLine]: Removing from `$CSDaemonLogContent [$Line]" }
                                        ##############################################################################

                                        # Remove that line/log entry from the array since we deemed it wasn't valid.
                                        $CSDaemonLogContent.Remove($Line) | Out-Null
                                    }
                                else
                                    {
                                        ##############################################################################
                                        #                             SUPER DEBUG                                    #
                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: IgnoreLine is false [$IgnoreLine]: Leaving `$CSDaemonLogContent [$Line]" }
                                        ##############################################################################
                                    }
                            }

                        ##############################################################################
                        #                             SUPER DEBUG                                    #
                        # Only enable this if you're seeing //really// strange or unexpected results #
                        <#                         if($Global:DebugEnabled -eq $true)                             {                                 write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Number of items in `$CSDaemonLogContent AFTER refinement [$($CSDaemonLogContent.Count)]"                                 write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: CSDaemonLogContent BEGIN>>>>"
                                for($i=0; $i -lt $CSDaemonLogContent.Count;$i++) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: CSDaemonLogContent[$i] $($CSDaemonLogContent[$i])" }
                                write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: CSDaemonLogContent END<<<<"                             }                         #>
                        ##############################################################################

                        # Here we check if the array contains content (read: errors) and if not, we pause then start the loop again.
                        if(([string]::Isnullorempty($CSDaemonLogContent)) -or ($CSDaemonLogContent.Count -eq 0))
                            {
                                write-host "$(get-date -Format s) - [$Global:Loop] :: Congratulations - No file errors found!"
                                if(($CriticalErrorEncountered -ne $true) -and ($Abort -ne $true) -and  (!(Test-Path -Path $StopMonitoringFlagFile -PathType Leaf)))
                                    {
                                        write-host "$(get-date -Format s) - [$Global:Loop] :: Restarting in [$RestartFixTimeOut] seconds.`r`n"
                                        Start-Sleep -Seconds $RestartFixTimeOut
                                    }
                                continue
                            }

                        # Otherwise there //are// errors so we need to dive into that further.
                        write-host "$(get-date -Format s) - [$Global:Loop] :: `tWARNING: Found [$($CSDaemonLogContent.Count)] error(s) in the log [$CSDaemonLog]`r`n"

                        ##############################################################################
                        #                             SUPER DEBUG                                    #
                        # Only enable this if you're seeing //really// strange or unexpected results #
                        #if($Global:DebugEnabled -eq $true) { for($i=0; $i -ne $CSDaemonLogContent.Count;$i++) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Error $($i+1) of $($CSDaemonLogContent.Count): $($CSDaemonLogContent[$i])" } }
                        ##############################################################################

                        for($i=0; $i -lt $CSDaemonLogContent.Count;$i++)
                            {
                                [bool]$IgnoreError = $false

                                ##############################################################################
                                #                             SUPER DEBUG                                    #
                                # Only enable this if you're seeing //really// strange or unexpected results #
                                #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: `$CSDaemonLogContent[$i] [$($CSDaemonLogContent[$i])]" }
                                ##############################################################################

                                # This is a regex that aims to extract just the file name bits from the 'error' line entry.
                                # The regex looks for what ever is in between the single quotes '' and extracts just that
                                # Example
                                #     Mar 01 11:15:48 [ERROR] upload-local-handler.cpp(624): Failed to read file for 'path:\some/nested/directory/fi.le', try it again 0 潎攠牲牯
                                #     Mar 01 11:15:48 [ERROR] upload-local-handler.cpp(435): Failed to prepare file block for 'path:\some/nested/directory/fi.le'. System error.
                                # Leaving us with just: 'path:\some/nested/directory/fi.le'
                                $CSDaemonLogContent[$i] -match ".+'(.*?.+?)'" | Out-Null

                                # Matches[0] is the entire line
                                # Matches[1] is regex result

                                # Checks to see if $Matches[1] is null or empty; hopefully shouldn't happen at this point
                                if([string]::IsNullOrEmpty($Matches[1]) -eq $true)
                                    {
                                        ##############################################################################
                                        #                             SUPER DEBUG                                    #
                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: WARNING Matches[1] is null or empty [$($Matches[1])]" }
                                        ##############################################################################
                                        continue
                                    }
                                else
                                    {
                                        ##############################################################################
                                        #                             SUPER DEBUG                                    #
                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Matches[1] is not null or empty [$($Matches[1])]" }
                                        ##############################################################################
                                    }

                                # If it does not have slashes, it's not a file path so we can ignore it
                                if(-not $Matches[1].Contains('/'))
                                    {
                                        ##############################################################################
                                        #                             SUPER DEBUG                                    #
                                        # Only enable this if you're seeing //really// strange or unexpected results #
                                        #if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Matches[1] [$($Matches[1])] DOES NOT contain a '/' [$($Matches[1].Contains('/'))]" }
                                        ##############################################################################
                                        continue
                                    }

                                # Unix to Windows path fix
                                $FilePathFixed = $null; [string]$FilePathFixed = $Matches[1].Replace('/','\')
                                if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: FilePathFixed: [$FilePathFixed]" }

                                # If $Matches[1] contains something and it's not already in the array $arrProcessed, we need to check it out
                                if(([string]::IsNullOrEmpty($Matches[1]) -eq $false) -and ($arrProcessed.Contains($FilePathFixed) -eq $false))
                                    {
                                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Matches[1] is not null or empty [$($Matches[1])] AND `$arrProcessed does not contain [$FilePathFixed] [$($arrProcessed.Contains($FilePathFixed))]" }

                                        write-host "$(get-date -Format s) - [$Global:Loop] :: The Synology Cloud Station Client had trouble processing file [$FilePathFixed]"

                                        if(Test-Path -Path "$FilePathFixed")
                                            {
                                                if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Found FilePathFixed [$FilePathFixed]" }

                                                Try
                                                    {
                                                        # Determine if the file is read-only or not
                                                        [bool]$IsReadOnly = (Get-ChildItem -Path "$FilePathFixed" -Force -ErrorAction Stop ).IsReadOnly
                                                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: IsReadOnly is [$IsReadOnly]" }

                                                        If ($IsReadOnly -eq $true)
                                                            {
                                                                write-host "$(get-date -Format s) - [$Global:Loop] :: REASON: Possibly because the file is read-only; Removing read-only attribute..."
                                                                $DisableReadOnly = Toggle-ReadOnly -File "$FilePathFixed"
                                                                if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: DisableReadOnly is [$DisableReadOnly]" }

                                                                if($DisableReadOnly -eq $true)
                                                                    {
                                                                        write-host "$(get-date -Format s) - [$Global:Loop] :: Successfully Toggled Read-Only Attribute on [$FilePathFixed]"
                                                                        Try { $arrProcessed.Add($FilePathFixed) | Out-Null; [int]$CountOfFilesToFix++ | Out-Null; [int]$TotalNumberOfFilesFixed++ | Out-Null } Catch { write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR ADDING TO COLLECTION [$FilePathFixed]: $_" }
                                                                    }
                                                                else { write-host "$(get-date -Format s) - [$Global:Loop] :: `tERROR ENABLING READ-ONLY ATTRIBUTE ON FILE [$FilePathFixed]: [$DisableReadOnly]" }
                                                            }
                                                        else { write-host "$(get-date -Format s) - [$Global:Loop] :: `tWARNING: Unsure why the Synology Cloud Station Client had trouble with file [$FilePathFixed] since it's not read-only [$IsReadOnly]." }
                                                    }
                                                Catch { write-host "$(get-date -Format s) - [$Global:Loop] :: `tERROR CHECKING READ-ONLY ATTRIBUTES ON FILE [$FilePathFixed]: $_" }
                                            }
                                        else { write-host "$(get-date -Format s) - [$Global:Loop] :: REASON: Possibly because the file is missing [$FilePathFixed]" }
                                    }
                                Elseif(([string]::IsNullOrEmpty($Matches[1]) -eq $false) -and ($arrProcessed.Contains($FilePathFixed) -eq $true)) { write-host "$(get-date -Format s) - [$Global:Loop] :: Skipping Processed File [$FilePathFixed]"  }
                                else
                                    {
                                        write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR: SOMETHING UNEXPECTED HAPPENED:"
                                        write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR: `$Matches[1] [$($Matches[1])]"
                                        write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR: `$arrProcessed.Contains($($Matches[1])) [$($arrProcessed.Contains($Matches[1]))]"
                                        write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR: `$FilePathFixed [$FilePathFixed]"
                                        write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR: `$arrProcessed.Contains($FilePathFixed) [$($arrProcessed.Contains($FilePathFixed))]"
                                        write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR: DUE TO THE UNEXPECTED NATURE OF THE ERROR ABOVE WE ARE BREAKING OUT OF THE FIXING LOOP"
                                        $CriticalErrorEncountered = $true
                                    }

                                # Check for the flag file that will stop the script
                                if(Test-Path -Path $StopMonitoringFlagFile -PathType Leaf)
                                    {
                                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: WARNING Stop monitoring flag file found [$StopMonitoringFlagFile]; BREAKING OUT OF THE FIXING LOOP" }
                                        [bool]$Abort = $true
                                    }
                                else { if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Stop monitoring flag file not found [$StopMonitoringFlagFile]; Continuing execution" } }

                                if(($CriticalErrorEncountered -eq $true) -or ($Abort -eq $true))
                                    {
                                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: ERROR EITHER CRITICALERRORENCOUNTERED IS TRUE [$CriticalErrorEncountered] OR ABORT IS TRUE [$Abort]; BREAKING OUT OF THE FIXING LOOP" }
                                        break
                                    }
                                else
                                    {
                                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Both CriticalErrorEncountered [$CriticalErrorEncountered] and Abort [$Abort] are false; Continuing execution" }
                                        Write-Host
                                    }
                            }

                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: CountOfFilesToFix is [$CountOfFilesToFix]" }

                        if ($CountOfFilesToFix -gt 0)
                            {
                                write-host "$(get-date -Format s) - [$Global:Loop] :: Finished processing [$CountOfFilesToFix] file(s)."
                                if($DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: `$CountOfFilesToFix.ToString().Length is [$([string]$CountOfFilesToFix.ToString().Length)]" }

                                # I find that the more files we fix, the longer we have to wait for the Cloud Station Client to catch up.
                                # So this is some simple logic based on my [limited] testing with a little over 125000 files to figure out how long we should wait.
                                switch([string]$CountOfFilesToFix.ToString().Length)
                                    {
                                        { ($_ -eq 1) } { $CalculatedTimeOut = $CloudStationClientCatchUpTimeOut }
                                        { ($_ -eq 2) } { $CalculatedTimeOut = $CloudStationClientCatchUpTimeOut + ([math]::Round($CountOfFilesToFix/10)*10) }
                                        { ($_ -eq 3) } { $CalculatedTimeOut = $CloudStationClientCatchUpTimeOut + ([math]::Round($CountOfFilesToFix/100)*10) }
                                        { ($_ -ge 4) } { $CalculatedTimeOut = $CloudStationClientCatchUpTimeOut + ([math]::Round($CountOfFilesToFix/1000)*10) }
                                    }
                                write-host "$(get-date -Format s) - [$Global:Loop] :: Waiting [$CalculatedTimeOut] seconds for the Cloud Station Client to resync the [$CountOfFilesToFix] file(s) we fixed."
                                Start-Sleep -Seconds $CalculatedTimeOut

                                write-host "$(get-date -Format s) - [$Global:Loop] :: Unfixing read-only attributes on [$CountOfFilesToFix] changed file(s)."
                                foreach($ProcessedFile in @($arrProcessed))
                                    {
                                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: ProcessedFile is [$ProcessedFile]" }
                                        if(Test-Path -Path "$ProcessedFile")
                                            {
                                                Try
                                                    {
                                                        [bool]$IsReadOnly = (Get-ChildItem -Path "$ProcessedFile" -Force -ErrorAction Stop ).isreadonly
                                                        if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: IsReadOnly is [$IsReadOnly]" }

                                                        If ($IsReadOnly -eq $false)
                                                            {
                                                                write-host "$(get-date -Format s) - [$Global:Loop] :: Re-adding read-only attribute on file [$ProcessedFile]..."
                                                                $EnableReadOnly = Toggle-ReadOnly -File $ProcessedFile -MakeReadOnly
                                                                if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: EnableReadOnly is [$EnableReadOnly]" }

                                                                if($EnableReadOnly -eq $true)
                                                                    {
                                                                        write-host "$(get-date -Format s) - [$Global:Loop] :: Successfully re-set read-only attribute on file [$ProcessedFile]"
                                                                        Try { $arrProcessed.Remove($ProcessedFile) | Out-Null } Catch { write-host "$(get-date -Format s) - [$Global:Loop] :: ERROR REMOVING FILE [$ProcessedFile] FROM COLLECTION: $_" }
                                                                    }
                                                                else { write-host "$(get-date -Format s) - [$Global:Loop] :: `tERROR TOGGLING READ-ONLY ATTRIBUTE ON FILE [$ProcessedFile]: [$EnableReadOnly]" }
                                                            }
                                                        else { write-host "$(get-date -Format s) - [$Global:Loop] :: `tWARNING File [$ProcessedFile] is already set to read-only [$IsReadOnly]; Skipping..." }
                                                    }
                                                Catch { write-host "$(get-date -Format s) - [$Global:Loop] :: `tERROR CHECKING READ-ONLY ATTRIBUTES ON FILE [$ProcessedFile]: $_" }
                                            }
                                        else { write-host "$(get-date -Format s) - [$Global:Loop] :: `tERROR FILE MISSING: [$ProcessedFile]" }

                                        write-host
                                    }
                            }
                        else { write-host "$(get-date -Format s) - [$Global:Loop] :: Congratulations - No [$CountOfFilesToFix] files needed processing!"}
                    }
                Catch { write-host "$(get-date -Format s) - [$Global:Loop] :: `tERROR AN UNKNOWN INNER ERROR OCCURRED: $_"; $CriticalErrorEncountered = $true }
            }
        Catch { write-host "$(get-date -Format s) - [$Global:Loop] :: `tERROR GETTING CONTENT OF [$CSDaemonLog]: $_"; $CriticalErrorEncountered = $true }

        if(Test-Path -Path $StopMonitoringFlagFile -PathType Leaf)
            {
                if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: WARNING Stop monitoring flag file found [$StopMonitoringFlagFile]" }
                [bool]$Abort = $true
            }
        Else { if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: Stop monitoring flag file not found [$StopMonitoringFlagFile]; Continuing execution" } }

        if(($CriticalErrorEncountered -ne $true) -and ($Abort -ne $true))
            {
                write-host "$(get-date -Format s) - [$Global:Loop] :: Loop Completed - waiting [$RestartFixTimeOut] seconds before restarting.`r`n"
                Start-Sleep -Seconds $RestartFixTimeOut
            }
        else
            {
                if($Global:DebugEnabled -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: DEBUG :: ERROR EITHER CRITICALERRORENCOUNTERED IS TRUE [$CriticalErrorEncountered] OR ABORT IS TRUE [$Abort]; BREAKING OUT OF THE MAIN LOOP" }
                if($Abort -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: WARNING Process aborted due to presence of flag file [$StopMonitoringFlagFile]" }
                if($CriticalErrorEncountered -eq $true) { write-host "$(get-date -Format s) - [$Global:Loop] :: CRITICAL ERROR ENCOUNTERED; SCRIPT QUITTING!" }
            }
    }
Until(($Abort -eq $true ) -or ($CriticalErrorEncountered -eq $true))

write-host "$(get-date -Format s) - [$Global:Loop] :: Total number of files fixed this session: [$TotalNumberOfFilesFixed]`r`n"

Pause-Script

#endregion ScriptBody
##*=============================================
##* END SCRIPT BODY
##*=============================================

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s