Controlling iCloud/OneDrive file sync status in CMD

12 minute read

Published:

If you ever find yourself needing to control the sync status of a OneDrive or iCloud for Windows file from a batch file or CMD, it is fortunately quite straightforward with ATTRIB.exe built into Windows (comprehensive documentation here), which is used to view and change file attributes. Here, we are only concerned with three particular attributes (with their attrib flags in parenthesis): Offline (O), Pinned (P), and Unpinned (U).

In CMD, using the attrib command with just a filename (e.g., attrib filename.txt) without flags prints the file’s current attributes and path. By adding flags with a plus/minus (+/-) between attrib and the filename, the file’s individual attributes are changed and toggled on/off (True/False). Putting a plus before the flag tells it to set that attribute to True (e.g., +P) while a minus sign sets it to false (e.g., -P). Each of the various icons indicating sync status of a file corresponds to some combination of these flags being enabled or disabled:

IconAttributesSet withExplanation
O U / O +U“Only available online” files, in the cloud only and not locally available
P+P“Always keep on this device” files, locally available and will be kept downloaded
` `-P / +P then -P“Available on this device” files, locally available (will be made online-only if space is needed)
O P / O n/aSync in progress (status when a file is downloading or queued to be downloaded)

Note that the Offline (O) flag is something that just updates automatically and is not something we set. Setting U or P to True automatically sets the other to False, resulting in Unpinned (U) files being online only and Pinned (P) files being always locally available. Both U and P can be set to False simultaneously, resulting in the file being locally available but not necessarily always available.

In the picture below is an example with a file called IMG_6736.MOV in my iCloud Photos library. The file goes through the following steps:

  1. It begins as online only (O)
  2. It’s then “pinned” (+P) to be always kept on this device
  3. We check to see the attributes while queued/downloading (O P)
  4. We check to see the attributes after the download finishes (P)
  5. Its “pinned” status is revoked (-P) to remain available but not necessarily always downloaded
  6. We check its attributes and see both U and P are False
  7. It’s finally “unpinned” (+U) to remove the download and make it online only again
  8. And we check its attributes to verify its online only status (O U)

The iCloud Photos use case

My motivation for looking into all of this in the first place revolved around iCloud Photos and its abhorrently terrible performance when trying to download many files at once. This seems like a normal use case, no? I have just downloaded iCloud for Windows, and I want local copies of all of my iPhone’s photos that are synced to iCloud. I have nearly 28,000 photos, so I right click the “Photos” folder and select “Always keep on this device” then go to bed. I wake up to see in the window from the iCould taskbar tray icon that there was essentially no progress in eight hours. After some troubleshooting, I eventually right click the “Photos” folder and select “Free up space” to stop this stalled process.

If iCloud can’t handle trying to download 28,000 files, how many can it? After some amount of testing, I discover 50 is about the sweet spot. If I select 50 files and set them to “Always keep on this device”, it takes on the order of five minutes (to a bit less) before they are all downloaded. If I do the same but with a selection of 75 files, this time increass to a bit over 10 minutes. With 200 files, it varied but was on the order of an hour. When I had 500 selected, it took well over six hours. It seems iCloud gets increasingly bogged down the longer its queue for files to be downloaded is.

So, what am I to do? Am I really going to babysit my iCloud Photos File Explorer window and spoonfeed it 50 files at a time to download every five minutes or so… for over 550 cycles?!? Assuming perfect efficiency, that’s 45 hours of tending to it. Arguably better (but perhaps wrose even), I could feed it ~500 files to download overnight and do this nightly for nearly two months… These are obviously not sensible solutions.

What I needed was a way to automate this. A scripted solution. Thus, the batch script at the bottom of this article was created. The script scans through all files in the “Photos” directory and sets attrib +P to any files it sees are currently set as Offline (O). Once it has “pinned” 50 files, a sleep/timeout timer of 300 seconds is activated to give iCloud time to download the files. The counter is then reset, and the scan continues.

One small hiccup I did encounter was that the substantially larger (and sporadically located) .MOV video files were taking longer to download and would sometimes cause a backup where the 5 minute timer was insufficient to download the 50 flagged files if they contained too many/large .MOV files. This then spiralled the queue out of control as iCloud choked on the increasing number of files being fed to it, slowing down even more the further behind it got. To circumvent this, I would suggest running the script first with the directory scan only looking for .MOV files (for %%f in (Photos\*.MOV)) with a lower counter value before the sleep timer is activated, such as if !count!==10, and perhaps with a longer sleep timer too depending on your video file sizes and network speed. After all the larger video files are downlaoded, just rerun the script but searching for all file extensions and allowing the count to get up to 50. The counter limit and sleep timer duration are values you can play around with and optimize for what prevents backlogs in your own environment.

As a warning, it may be tempting to reduce the sleep timer as much as possible to maximize efficiency, the percentage of time spent actually downloading files rather than sleeping. However, especially if you would plan to leave the script running unattended for hours, I would caution against getting too close to “maximum” efficiency. Remember, when iCloud gets overburdened with too many files queued, it slows down, meaning if you keep feeding it more files to download at a constant rate, it will just continuously slow down more and more and fail catastrophically, getting hung up and (nearly) ceasing all downloads. Thus, I think it is best to give it ample time to download the “slowest” batch of files fed to it at a time.

The below Windows batch script was stored in and executed from the “iCloud Photos” directory, which contains the “Photos” subdirectory holding all of the photo and video files synced to iCloud Photos. Beneath it is some more details on the script.

@echo off
setlocal enableextensions enabledelayedexpansion
set /a count = 0
for %%f in (Photos\*.*) do (
  attrib %%f
  attrib %%f | findstr "^.......O" >nul && (
    attrib +P %%f
    set /a count += 1
    if !count!==50 (
      echo Current time is !time!
      echo Waiting 5 min for previous 50 files to download...
      set /a count = 0
      timeout -t 300 >nul
    )
  )
)

The script’s first for loop scans over all files in the Photos directory. The attrib command is called on its own just to print to the terminal all the files and their attributes as the code progresses. The next attrib call has its output piped to a findstr call that looks for an “O” in the eighth position; for a file with the Offline attribute, the eigth character in the string printed by the attrib call is an “O”, hence the seven periods followed by an “O” in the findstr call. If an “O” is found, the code executes attrib +P to the file and increments the counter variable count for the queued files. The loop proceeds like this until the counter reaches 50. Then, the code prints the current time and waits for 300 seconds, set by the timeout call, to give iCloud time to download the 50 files that had just been pinned and slated for download. The count variable is reset to 0, and then the code proceeds with this loop until all files have been cycled through.

My initial version of the script I came up with is shown below, with its own explanation following. It is similar but is quite a bit slower at scanning through the directory.

@echo off
setlocal enableextensions enabledelayedexpansion
set /a count = 0
for %%f in (Photos\*.*) do (
  for /f "delims=" %%a in ('attrib %%f') do set "readvalue=%%a"
  echo !readvalue!
  set IsOffline=0
  call :CheckAttributes
  if !IsOffline!==1 (
    attrib +P %%f
    set /a count += 1
    echo !count! files in download queue
    timeout -t 1 >nul
  )
  if !count!==50 (
    echo Current time is !time!
    echo Waiting 5 min for previous 50 files to download...
    set /a count = 0
    timeout -t 300 >nul
  )
)
:CheckAttributes
  set /a ichar = 0
  for /F "delims=" %%G in ('cmd /D /U /C  echo !readvalue!^| find /V ""') do (
    set /a ichar += 1
    if "%%G"=="O" (
      set IsOffline=1
      goto :break
    )
    if !ichar!==17 goto :break
  )
:break

The script’s first for loop scans over all files in the Photos directory. The line immediately beneath that is essentially just executing attrib filename.txt but storing the string output of that command into the variable readvalue. IsOffline is initialized as 0, and then the CheckAttributes function (defined at the bottom, outside the main loop) is called, which scans readvalue character by character, checking if any of the characters are "O" (if "%%G"=="O") indicating the presence of the Offline flag and setting IsOffline=1 if an “O” is present. To avoid reading characters in the file path portion of the attrib output, the function terminates and returns to the code’s main loop after checking the first 17 characters (or once an “O” is found).

Then, if IsOffline is 1, the code executes attrib +P to the file and increments the counter variable count for the queued files. The loop proceeds like this until the counter reaches 50. Then, the code prints the current time and waits for 300 seconds, set by the timeout call, to give iCloud time to download the 50 files that had just been pinned and slated for download. The count variable is reset to 0, and then the code proceeds with this loop until all files have been cycled through.

The slowest part of this code is the character-by-character check for the attributes in the CheckAttributes function call. Perhaps there is a faster way, but in my troubleshooting I had difficulty getting other approaches to work. And, as I stated earlier, efficiency wasn’t my greatest concern given how slow iCloud is in the first place.

Also, while my script searches for the presence of the Offline flag, a slightly more robust way would be to search for the absence of the Pinned flag. In the script, renaming IsOffline to IsPinned and changing the logic in the first if statement to be if !IsPinned!==0 would accomplish this. I did not implent it this way myself because this essentially made the default action of the code to “do something” if my attribute checking were to fail in some way, though I now believe it would be perfectly fine.

As a disclaimer, I only tested all of this on iCloud for Windows files; however, iCloud for Windows works in the same way as OneDrive for file syncing attributes. In fact, as I was troubleshooting all of this myself, all of the resources I used referenced OneDrive.