8

I need to be able to check if the local OneDrive folder is in sync/up to date.

Can I check this by looking at any of the file/folder properties(in C# code) Without using any of One Drive APIs?

Owen Pauling
  • 11,349
  • 20
  • 53
  • 64
monika pathak
  • 91
  • 1
  • 4

2 Answers2

5

I was stuck with that and tought to check the main folder Icon.

EDITED

The point is to extract the synced folder icon and get the overlay CLSID.

You first need a class to extract the info you need :

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32;


namespace MyApp.Classes
{
    public class ExtractIconInfo
    {
        [Flags]
        private enum SHGFI : uint
        {
            SHGFI_ICON = 0x000000100,
            SHGFI_DISPLAYNAME = 0x000000200,
            SHGFI_TYPENAME = 0x000000400,
            SHGFI_ATTRIBUTES = 0x000000800,
            SHGFI_ICONLOCATION = 0x000001000,
            SHGFI_EXETYPE = 0x000002000,
            SHGFI_SYSICONINDEX = 0x000004000,
            SHGFI_LINKOVERLAY = 0x000008000,
            SHGFI_SELECTED = 0x000010000,
            SHGFI_ATTR_SPECIFIED = 0x000020000,
            SHGFI_LARGEICON = 0x000000000,
            SHGFI_SMALLICON = 0x000000001,
            SHGFI_OPENICON = 0x000000002,
            SHGFI_SHELLICONSIZE = 0x000000004,
            SHGFI_PIDL = 0x000000008,
            SHGFI_USEFILEATTRIBUTES = 0x000000010,
            SHGFI_ADDOVERLAYS = 0x000000020,
            SHGFI_OVERLAYINDEX = 0x000000040
        }

        [Flags]
        private enum FileAttributes : uint
        {
            Readonly = 0x00000001,
            Hidden = 0x00000002,
            System = 0x00000004,
            Directory = 0x00000010,
            Archive = 0x00000020,
            Device = 0x00000040,
            Normal = 0x00000080,
            Temporary = 0x00000100,
            SparseFile = 0x00000200,
            ReparsePoint = 0x00000400,
            Compressed = 0x00000800,
            Offline = 0x00001000,
            NotContentIndexed = 0x00002000,
            Encrypted = 0x00004000,
            Virtual = 0x00010000
        }

        [Flags]
        public enum ISIOI : uint
        {
            ISIOI_ICONFILE = 0x00000001,
            ISIOI_ICONINDEX = 0x00000002
        }

        private SortedDictionary<string, Guid> ShellIconOverlayIdentifiers;

        public string IconName { get; private set; }
        public Icon Icon { get; private set; }
        public int IconOverlayIndex { get; private set; }
        public Guid IconOverlayGuid { get; private set; }

        public ExtractIconInfo(FileInfo fi)
        {
            ExtractIcon(fi.FullName, false);
        }
        public ExtractIconInfo(DirectoryInfo di)
        {
            ExtractIcon(di.FullName, true);
        }
        public ExtractIconInfo(string path, bool isFolder)
        {
            ExtractIcon(path, isFolder);
        }

        [ComVisible(false)]
        [ComImport]
        [Guid("0C6C4200-C589-11D0-999A-00C04FD655E1")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IShellIconOverlayIdentifier
        {
            [PreserveSig]
            int IsMemberOf([MarshalAs(UnmanagedType.LPWStr)] string path, uint attributes);
            [PreserveSig]
            int GetOverlayInfo([MarshalAs(UnmanagedType.LPWStr)] string iconFileBuffer, int iconFileBufferSize, out int iconIndex, out uint flags);
            [PreserveSig]
            int GetPriority(out int priority);
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct SHFILEINFO
        {
            internal IntPtr hIcon;
            internal int iIcon;
            internal uint dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            internal string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
            internal string szTypeName;
        };

        [DllImport("shell32.dll")]
        private static extern IntPtr SHGetFileInfo(
            string pszPath,
            uint dwFileAttributes,
            ref SHFILEINFO psfi,
            uint cbSizeFileInfo,
            uint uFlags
        );

        [DllImport("shell32.dll")]
        private static extern int SHGetIconOverlayIndex(
            string pszIconPath,
            int iIconIndex
        );

        [DllImport("user32.dll")]
        public static extern int DestroyIcon(IntPtr hIcon);

        private void ExtractIcon(string path, bool isFolder)
        {
            SHFILEINFO shIconInfo = new SHFILEINFO();
            SHGetFileInfo(path, isFolder ? (uint)FileAttributes.Directory : (uint)FileAttributes.Normal, ref shIconInfo, (uint)Marshal.SizeOf(shIconInfo), (uint)SHGFI.SHGFI_ICON | (uint)SHGFI.SHGFI_LARGEICON | (uint)SHGFI.SHGFI_DISPLAYNAME | (uint)SHGFI.SHGFI_ADDOVERLAYS | (uint)SHGFI.SHGFI_OVERLAYINDEX);
            Icon = (Icon)Icon.FromHandle(shIconInfo.hIcon).Clone();
            IconName = shIconInfo.szDisplayName;
            IconOverlayIndex = (shIconInfo.iIcon >> 24) & 0xFF;
            GetOverlayIconGuid();

            DestroyIcon(shIconInfo.hIcon);
        }

        private void GetOverlayIconGuid()
        {
            IconOverlayGuid = Guid.Empty;
            GetRegistryShellIconOverlayIdentifiers();
            foreach (string key in ShellIconOverlayIdentifiers.Keys)
            {
                string AIconFileName = string.Empty;
                int AIconIndex = 0;
                Guid value = ShellIconOverlayIdentifiers[key];
                GetOverlayIconInfo(value, out AIconFileName, out AIconIndex);
                if (SHGetIconOverlayIndex(AIconFileName, AIconIndex) == IconOverlayIndex)
                    IconOverlayGuid = value;
            }
        }

        private void GetOverlayIconInfo(Guid CLSID, out string path, out int index)
        {
            try
            {
                path = new string(' ', 256);
                uint flags = 0;
                IShellIconOverlayIdentifier SHIOI = Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID, true), true) as IShellIconOverlayIdentifier;
                SHIOI.GetOverlayInfo(path, path.Length, out index, out flags);
                Marshal.ReleaseComObject(SHIOI);

                if ((flags & (uint)ISIOI.ISIOI_ICONFILE) != 0)
                {
                    if ((flags & (uint)ISIOI.ISIOI_ICONINDEX) == 0) index = 0;
                }
                else
                {
                    path = string.Empty;
                    index = 0;
                }
                path = path.Substring(0, path.IndexOf('\0'));
            }
            catch
            {
                path = string.Empty;
                index = 0;
            }
        }

        private void GetRegistryShellIconOverlayIdentifiers()
        {
            ShellIconOverlayIdentifiers = new SortedDictionary<string, Guid>();

            using (RegistryKey key32 = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, Environment.MachineName, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers"))
                foreach (string subKey in key32.GetSubKeyNames())
                {
                    Guid value = Guid.Parse((string)key32.OpenSubKey(subKey).GetValue(null));
                    if (!ShellIconOverlayIdentifiers.ContainsKey(subKey))
                        ShellIconOverlayIdentifiers.Add(subKey, value);
                }

            using (RegistryKey key64 = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, Environment.MachineName, RegistryView.Registry64).OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers"))
                foreach (string subKey in key64.GetSubKeyNames())
                {
                    Guid value = Guid.Parse((string)key64.OpenSubKey(subKey).GetValue(null));
                    if (!ShellIconOverlayIdentifiers.ContainsKey(subKey))
                        ShellIconOverlayIdentifiers.Add(subKey, value);
                }
        }
    }
}

The relevant info here is IconOverlayGuid.

Then compare :

public enum SyncStatus { Off, Unknown, Syncing, Synced, SharedSyncing, SharedSynced, Error }

private SyncStatus CheckIfSync(DirectoryInfo di)
{
    ExtractIconInfo IcoInfo = new ExtractIconInfo(di);
    if (IcoInfo != null)
    {
        if (IcoInfo.IconOverlayGuid == Guid.Empty) return SyncStatus.Off;
        else if (IcoInfo.IconOverlayGuid == new Guid("{BBACC218-34EA-4666-9D7A-C78F2274A524}")) return SyncStatus.Error;
        else if (IcoInfo.IconOverlayGuid == new Guid("{F241C880-6982-4CE5-8CF7-7085BA96DA5A}")) return SyncStatus.Synced;
        else if (IcoInfo.IconOverlayGuid == new Guid("{A0396A93-DC06-4AEF-BEE9-95FFCCAEF20E}")) return SyncStatus.Syncing;
        else if (IcoInfo.IconOverlayGuid == new Guid("{5AB7172C-9C11-405C-8DD5-AF20F3606282}")) return SyncStatus.SharedSynced;
        else if (IcoInfo.IconOverlayGuid == new Guid("{A78ED123-AB77-406B-9962-2A5D9D2F7F30}")) return SyncStatus.SharedSyncing;
        else return SyncStatus.Unknown;
    }
    else return SyncStatus.Unknown;
}

The Overlay Icons are stored in FileSyncShell.dll. Their CLSID are stored in the registry : HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers and HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers.

REGEDIT

  • OneDrive1 : Sync in error
  • OneDrive2 : Synced Shared
  • OneDrive3 : Syncing Shared
  • OneDrive4 : Synced
  • OneDrive5 : Syncing

Hope that helps !

(I spent way too much time on that one, but I learned a lot !)

DKH
  • 452
  • 7
  • 15
  • Have you tested this? Does this actually realiably work? – user3700562 Jul 11 '18 at 17:49
  • @user3700562 Yes, tested and now in production – DKH Jul 19 '18 at 08:33
  • 1
    Uhm, I used this to test my OneDrive folder and it is always return empty guid for icon (for both folder and files). May be OneDrive version does matter? – ndh103 Jul 27 '18 at 07:19
  • 1
    This is used to extract the overlay icon guid. If you have an overlay icon on a folder, it should return a guid regardless of the software. – DKH Aug 17 '18 at 09:42
  • This wouldn't work for me. I get an exception - Retrieving the COM class factory for component with CLSID {BBACC218-34EA-4666-9D7A-C78F2274A524} failed due to the following error: 80040154 Class not registered – colinbashbash May 17 '19 at 16:40
0

Probably comparing the LastModifiedDateTime.

...Drive.Root.ItemWithPath(file).Request().GetAsync()).FileSystemInfo.LastModifiedDateTime
iikem
  • 43
  • 8
  • Well...don't think this is gonna help. – monika pathak Dec 21 '16 at 06:44
  • 2
    My intention is to check local file/folder's properties to see if they are in sync with onedrive instead of being dependent on OneDrive APIs. Something must be changing in the file's properties when the icon changes by itself to indicate out of sync/syncing/in-sync status. Want to grab that locally. – monika pathak Dec 21 '16 at 06:47