Search code examples
powershellmicrosoft-distributed-file-system

Get DFS Server name and local path from UNC path


I'm working on Windows Server 2012 which comes by default with various DFS CmdLets.

What I'm trying to do is get the ComputerName and the local path of a specific UNC share, that is hosted by other DFS servers (Win Srv 2008 R2) on the domain.

An example:

Get-DfsnFolderTarget -Path '\\domain.net\share\folder\'

Expected result:

ComputerName = 'Server1'
Path         = 'E\Home\folder'

I'm not really a network engineer, but I can't seem to find a way to retrieve this information based on the UNC path. Every time I try the CmdLet above I get an error:

Get-DfsnFolderTarget : Cannot get DFS folder properites on "\\domain.net\share\folder\"
At line:1 char:1
+ Get-DfsnFolderTarget -Path '\\domain.net\share\folder\'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (MSFT_DfsNamespaceFolderTarget:ROOT\Microsoft\...aceFolderTarget) [Ge 
   t-DfsnFolderTarget], CimException
    + FullyQualifiedErrorId : Windows System Error 1168,Get-DfsnFolderTarget

Get-DfsnFolderTarget : The requested object could not be found.
At line:1 char:1
+ Get-DfsnFolderTarget -Path '\\domain.net\share\folder\'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (MSFT_DfsNamespaceFolderTarget:ROOT\Microsoft\...aceFolderTarget) [Ge 
   t-DfsnFolderTarget], CimException
    + FullyQualifiedErrorId : MI RESULT 6,Get-DfsnFolderTarget

When trying a Test-Path \\domain.net\share\folder\ it does say that it can be found. So I don't really understand.

I'm trying to retrieve this information (ComputerName and local path): enter image description here


Solution

  • I solved my own problem by hours of Googling and tweaking some stuff. This is what I ended up with for anyone else interested:

    Function Get-DFSDetails {
    <# 
        .SYNOPSIS   
            Gets DFS details for a UNC path.
        
        .DESCRIPTION
            The Get-DFSDetails CmdLet gets DFS details like DFS Server name, DFS Share name and the local path on the DFS Server for a specific UNC path.
        
        .PARAMETER Credentials 
            PowerShell credential object used to connect to the DFS Server to retrieve the local path on the server.
        
        .PARAMETER Path 
            Specifies a UNC path for the folder.
        
        .EXAMPLE
            Get-DFSDetails -Path '\\domain.net\HOME\Bob' -Credentials $Credentials
            Gets the DFS details for the UNC path '\\domain.net\HOME\Bob'
        
            Path         : \\domain.net\HOME\Bob
            ComputerName : SERVER1.DOMAIN.NET
            ComputerPath : E:\HOME\Bob
            ShareName    : HOME
        
        .EXAMPLE
            '\\domain.net\HOME\Mike', '\\domain.net\HOME\Jake' | Get-DFSDetails -Credentials $Credentials
            Gets the DFS details for the UNC paths '\\domain.net\HOME\Mike' and '\\domain.net\HOME\Jake'
        
            Path         : \\domain.net\HOME\Mike
            ComputerName : SERVER1.DOMAIN.NET
            ComputerPath : E:\HOME\Mike
            ShareName    : HOME 
                
            Path         : \\domain.net\HOME\Jake
            ComputerName : SERVER2.DOMAIN.NET
            ComputerPath : E:\HOME\Jake
            ShareName    : HOME    
        
        .NOTES
            CHANGELOG
            2015/10/27 Function born #>
        
        [CmdLetBinding()]
        Param (
            [Parameter(Mandatory, Position=0)]
            [PSCredential]$Credentials,
            [Parameter(Mandatory, ValueFromPipeline, Position=1)]
            [ValidateScript({
                if (Test-Path -LiteralPath $_ -PathType Container) {$true}
                else {throw "Could not find path '$_'"}
            })]
            [String[]]$Path
        )
        
        Begin {
    $signature = @'
    using System;
    using System.Collections.Generic;
    using System.Management.Automation;
    using System.Runtime.InteropServices;
        
    public class Win32Api
    {
        [DllImport("netapi32.dll", SetLastError = true)]
        private static extern int NetApiBufferFree(IntPtr buffer);
        
        [DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern int NetDfsGetClientInfo
        (
        [MarshalAs(UnmanagedType.LPWStr)] string EntryPath,
        [MarshalAs(UnmanagedType.LPWStr)] string ServerName,
        [MarshalAs(UnmanagedType.LPWStr)] string ShareName,
        int Level,
        ref IntPtr Buffer
        );
        
        public struct DFS_INFO_3
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string EntryPath;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string Comment;
            public UInt32 State;
            public UInt32 NumberOfStorages;
            public IntPtr Storages;
        }
        public struct DFS_STORAGE_INFO
        {
            public Int32 State;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string ServerName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string ShareName;
        }
        
        public static List<PSObject> NetDfsGetClientInfo(string DfsPath)
        {
            IntPtr buffer = new IntPtr();
            List<PSObject> returnList = new List<PSObject>();
        
            try
            {
                int result = NetDfsGetClientInfo(DfsPath, null, null, 3, ref buffer);
        
                if (result != 0)
                {
                    throw (new SystemException("Error getting DFS information"));
                }
                else
                {
                    DFS_INFO_3 dfsInfo = (DFS_INFO_3)Marshal.PtrToStructure(buffer, typeof(DFS_INFO_3));
        
                    for (int i = 0; i < dfsInfo.NumberOfStorages; i++)
                    {
                        IntPtr storage = new IntPtr(dfsInfo.Storages.ToInt64() + i * Marshal.SizeOf(typeof(DFS_STORAGE_INFO)));
        
                        DFS_STORAGE_INFO storageInfo = (DFS_STORAGE_INFO)Marshal.PtrToStructure(storage, typeof(DFS_STORAGE_INFO));
        
                        PSObject psObject = new PSObject();
        
                        psObject.Properties.Add(new PSNoteProperty("State", storageInfo.State));
                        psObject.Properties.Add(new PSNoteProperty("ServerName", storageInfo.ServerName));
                        psObject.Properties.Add(new PSNoteProperty("ShareName", storageInfo.ShareName));
        
                        returnList.Add(psObject);
                    }
                }
            }
            catch (Exception e)
            {
                throw(e);
            }
            finally
            {
                NetApiBufferFree(buffer);
            }
            return returnList;
        }
    }
    '@
        
            if (-not ('Win32Api' -as [Type])) {
                Add-Type -TypeDefinition $signature
            }
        }
        
        Process {
            foreach ($P in $Path) {
                Try {
                    # State 6 notes that the DFS path is online and active
                    $DFS = [Win32Api]::NetDfsGetClientInfo($P) | Where-Object { $_.State -eq 6 } | 
                        Select-Object ServerName, ShareName
        
                    $SessionParams = @{
                        Credential    = $Credentials
                        ComputerName  = $DFS.ServerName
                        SessionOption = New-CimSessionOption -Protocol Dcom
                    }
                    $CimParams = @{
                        CimSession = New-CimSession @SessionParams
                        ClassName  = 'Win32_Share'
                    }
                    $LocalPath = Get-CimInstance @CimParams | Where-Object Name -EQ $DFS.ShareName | 
                        Select-Object -ExpandProperty Path
        
                    [PSCustomObject][Ordered]@{
                        Path         = $P
                        ComputerName = $DFS.ServerName
                        ComputerPath = $LocalPath + ($P -split $DFS.ShareName, 2)[1]
                        ShareName    = $DFS.ShareName
                    }
                }
                Catch {
                    Write-Error $Error[0].Exception.Message
                    $Global:Error.Remove($Global:Error[0])
                }
            }
        }
    }