2

I am trying to implement a function in powershell that can reliably tell me if the directories referenced by two valid path names are indeed the same. It needs to be able to handle directories which are referenced by maps, by UNC paths, by junctions, possibly by junction on the file server, etc. etc.

Given the plethora of ways to refer to a directory, it seems the most reliable method of doing this is to write an empty file to the directory using one directory pathname, and then use the second pathname to test if the temporary file can be found under the second pathname. However, it turns out this may not be reliable.

We have a file server with a directory S:\ that is shared out as \\server\share. On the server we have the directories S:\rootDir1, S:\rootDir1\subDir, S:\rootDir2, and a junction S:\rootDir2\subDir => S:\rootDir1\subDir.

On an app server, I map M:\ => \\server\share, so the app server sees M:\rootDir1\subDir and M:\rootDir2\subDir

In powershell on the appserver, I create a file in M:\rootDir1\subDir via

 New-Item -ItemType File -Path `M:\rootDir1\subDir\testFile.txt`

We then immediately execute the two tests

$l_ret1 = test-path `M:\rootDir1\subDir\testFile.txt`
$l_ret2 = test-path `M:\rootDir2\subDir\testFile.txt`

The returned result $l_ret1 is consistently $true, while the result $l_ret2 is inconsistent without a significant time delay between creating the test file and testing for it. Is there any way to force Windows to "update" or "refresh" the information is has about files in M:\rootDir2\subDir? I realize this may be the fault of the file server (window file server), but then the question just changes to, is there any way to force the file server to update its information?

2 Answers 2

2

To determine if they are the same directory, try using:

GetFileInformationByHandle https://msdn.microsoft.com/en-us/library/aa364952(v=vs.85).aspx

Remarks sections there says "...You can compare the VolumeSerialNumber and FileIndex members returned in the BY_HANDLE_FILE_INFORMATION structure to determine if two paths map to the same target; for example, you can compare two file paths and determine if they map to the same directory..."

1
2

Expanding on Warren's answer: the following powershell code can be used in Powershell:

function Global:LoadCode()
{
    Add-Type -MemberDefinition @"

    [StructLayout(LayoutKind.Sequential)]
    public struct BY_HANDLE_FILE_INFORMATION
    {
        public uint FileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
        public uint VolumeSerialNumber;
        public uint FileSizeHigh;
        public uint FileSizeLow;
        public uint NumberOfLinks;
        public uint FileIndexHigh;
        public uint FileIndexLow;
    };

    [DllImport("kernel32.dll", SetLastError = true)]
     private static extern bool GetFileInformationByHandle(IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);

    [DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
     public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
     IntPtr SecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);

    private static SafeFileHandle MY_GetFileHandle(string dirName)
    {
        const int FILE_ACCESS_NEITHER = 0;
        const int FILE_SHARE_READ = 1;
        const int FILE_SHARE_WRITE = 2;
        const int CREATION_DISPOSITION_OPEN_EXISTING = 3;
        const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
        return CreateFile(dirName, FILE_ACCESS_NEITHER, (FILE_SHARE_READ | FILE_SHARE_WRITE), System.IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, System.IntPtr.Zero);
    }

    private static BY_HANDLE_FILE_INFORMATION? MY_GetFileInfo(SafeFileHandle directoryHandle)
    {
        BY_HANDLE_FILE_INFORMATION objectFileInfo;
        if ((directoryHandle == null) || (!GetFileInformationByHandle(directoryHandle.DangerousGetHandle(), out objectFileInfo)))
        {
            return null;
        }
        return objectFileInfo;
    }

    public static bool MY_AreDirsEqual(string dirName1, string dirName2)
    { //
        bool bRet = false;
        //NOTE: we cannot lift the call to GetFileHandle into GetFileInfo, because we _must_
        // have both file handles open simultaneously in order for the objectFileInfo comparison
        // to be guaranteed as valid.
        using (SafeFileHandle directoryHandle1 = MY_GetFileHandle(dirName1), directoryHandle2 = MY_GetFileHandle(dirName2))
        {
            BY_HANDLE_FILE_INFORMATION? objectFileInfo1 = MY_GetFileInfo(directoryHandle1);
            BY_HANDLE_FILE_INFORMATION? objectFileInfo2 = MY_GetFileInfo(directoryHandle2);
            bRet = objectFileInfo1 != null
                    && objectFileInfo2 != null
                    && (objectFileInfo1.Value.FileIndexHigh == objectFileInfo2.Value.FileIndexHigh)
                    && (objectFileInfo1.Value.FileIndexLow == objectFileInfo2.Value.FileIndexLow)
                    && (objectFileInfo1.Value.VolumeSerialNumber == objectFileInfo2.Value.VolumeSerialNumber);
        }
        return bRet;
    }
"@ -Name Win32 -NameSpace System -UsingNamespace System.Text,Microsoft.Win32.SafeHandles,System.ComponentModel
}

function Global:Get_AreDirsEqual([string]$p_source, [string]$p_target)
{   Mcc
    if( ( ([System.Management.Automation.PSTypeName]'System.Win32').Type -eq $null)  -or ([system.win32].getmethod('MY_AreDirsEqual') -eq $null) )
    {
        LoadCode
    }
    [System.Win32]::MY_AreDirsEqual($p_source, $p_target)
}

Note that this essentially implements the functionality in c#.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .