Resolved Enumerate network shares

aaaron

Well-known member
Joined
Jan 23, 2011
Messages
216
Programming Experience
10+
I've been for a long time trying to enumerate network shares on a workgroup (no domain) network with out success.

The last thing I did was to use some C++ and C# code as a guide and produce something that looks like a good start.

I knew there was no way to post a question that was meaningfull enough so I created a short demo completely self-contained.

It contains a couple of work arounds. They are marked with "For now".

I know it's a lot to ask but the code only needs to be pasted into a blank form and run.

I get a Null error when I run it.

Ready To Run Code:
Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' NULL pointer, it retrieves a handle to the root of the network.
        Dim lpnr As NETRESOURCE = Nothing
        If EnumerateFunc(lpnr) = False Then
            Debug.WriteLine("Call to EnumerateFunc failed" & vbLf)
        Else
        End If
    End Sub
    'From (C++ code) https://learn.microsoft.com/en-us/windows/win32/wnet/enumerating-network-resources
    Public Shared Function EnumerateFunc(ByVal lpnr As NETRESOURCE) As Boolean
        Dim errResult As Integer
        Dim hEnum As IntPtr
        Dim numbBytes As Integer = 16384 '16 kilobytes is typical
        Dim pToGlobal As IntPtr 'Pointers to unmanaged memory
        Dim numbResources As Integer = -1
        Dim resources(1) As NETRESOURCE 'The "1" is only to suppress warning message

        'Begin the enumeration.
        errResult = WNetOpenEnum(ResourceScope.GLOBALNET, ResourceType.ANY, 0, lpnr, hEnum) ' Returns hEnum
        If errResult <> ErrorCodes.NO_ERROR Then
            Debug.WriteLine("WnetOpenEnum failed with error: " & errResult)
            Return False
        End If
        Dim ByteArray(numbBytes - 1) As Byte  'Array of numbBytes bytes
        pToGlobal = Marshal.AllocHGlobal(numbBytes) 'Get memory of numbBytes bytes
        'Allocate memory
        If pToGlobal = Nothing Then
            Debug.WriteLine("WnetOpenEnum failed with error: " & errResult)
            Return False
        End If
        Do
            'Zero memory
            Marshal.Copy(ByteArray, 0, pToGlobal, numbBytes)
            ' -1 = enumerate all, numbResource returns actual number, numbBytes = buffer size
            errResult = WNetEnumResource(hEnum, numbResources, pToGlobal, numbBytes)
            If errResult = ErrorCodes.NO_ERROR Then

                MarshalUnmananagedArray2Struct(Of NETRESOURCE)(pToGlobal, numbResources, resources)
                'Loop through the structures
                For i As Integer = 0 To numbResources - 1
                    Debug.WriteLine("==========================")

                    'Display the contents of the NETRESOURCE structures.
                    DisplayStruct(resources(i))
                    Debug.WriteLine("Call the EnumerateFunc function recursively")
                    If ResourceUsage.CONTAINER = (resources(i).dwUsage And ResourceUsage.CONTAINER) Then
                        If Not EnumerateFunc(resources(i)) Then  'Call the EnumerateFunc function recursively
                            Debug.WriteLine("EnumerateFunc returned FALSE")
                        End If
                    End If
                Next i
            ElseIf errResult <> ErrorCodes.ERROR_NO_MORE_ITEMS Then
                Debug.WriteLine("WNetEnumResource failed with error:" & errResult)
                Exit Do
            End If
        Loop While errResult <> ErrorCodes.ERROR_NO_MORE_ITEMS
        Marshal.FreeHGlobal(pToGlobal)
        errResult = WNetCloseEnum(hEnum)
        If errResult <> ErrorCodes.NO_ERROR Then
            Debug.WriteLine("WNetCloseEnum failed with error: " & errResult)
            Return False
        End If
        Return True
    End Function
    Public Shared Sub MarshalUnmananagedArray2Struct(Of T)(hEnum As IntPtr, numb As Integer, ByRef mangagedArray As T())
        Dim size = Marshal.SizeOf(GetType(T))
        mangagedArray = New T(numb - 1) {}
        For I = 0 To numb - 1
            Try
                Dim ins As IntPtr = New IntPtr(hEnum.ToInt64() + I * size)
                mangagedArray(I) = Marshal.PtrToStructure(Of T)(ins)
            Catch
                Exit Sub
            End Try
        Next
    End Sub
    Public Shared Sub DisplayStruct(resource As NETRESOURCE)
        Debug.Write("NETRESOURCE Scope: ")
        Select Case resource.dwScope
            Case (ResourceScope.CONNECTED)
                Debug.WriteLine("connected")
            Case (ResourceScope.GLOBALNET)
                Debug.WriteLine("all resources")
            Case (ResourceScope.REMEMBERED)
                Debug.WriteLine("remembered")
            Case Else
                Debug.WriteLine("unknown scope ", resource.dwScope)
        End Select
        Debug.Write("NETRESOURCE Type: ")
        Select Case resource.dwType
            Case (ResourceType.ANY)
                Debug.WriteLine("any")
            Case (ResourceType.DISK)
                Debug.WriteLine("disk")
            Case (ResourceType.PRINT)
                Debug.WriteLine("print")
            Case Else
                Debug.WriteLine("unknown type ", resource.dwType)
        End Select
        Debug.Write("NETRESOURCE DisplayType: ")
        Select Case resource.dwDisplayType
            Case (ResourceDisplayType.GENERIC)
                Debug.WriteLine("generic")
            Case (ResourceDisplayType.DOMAIN)
                Debug.WriteLine("domain")
            Case (ResourceDisplayType.SERVER)
                Debug.WriteLine("server")
            Case (ResourceDisplayType.SHARE)
                Debug.WriteLine("share")
            Case (ResourceDisplayType.FILE)
                Debug.WriteLine("file")
            Case (ResourceDisplayType.GROUP)
                Debug.WriteLine("group")
            Case (ResourceDisplayType.NETWORK)
                Debug.WriteLine("network")
            Case Else
                Debug.WriteLine("unknown display type: ", resource.dwDisplayType)
        End Select
        Debug.Write("NETRESOURCE Usage: ")
        If (resource.dwUsage And ResourceUsage.CONNECTABLE) <> 0 Then
            Debug.WriteLine("connectable ")
        End If
        If (resource.dwUsage And ResourceUsage.CONTAINER) <> 0 Then
            Debug.WriteLine("container ")
        End If
        Debug.WriteLine("NETRESOURCE Localname: " & resource.lpLocalName)
        Debug.WriteLine("NETRESOURCE Remotename: " & resource.lpRemoteName)
        Debug.WriteLine("NETRESOURCE Comment: " & resource.lpComment)
        Debug.WriteLine("NETRESOURCE Provider: " & resource.lpProvider)
    End Sub
    Public Enum ErrorCodes
        NO_ERROR = 0
        ERROR_NO_MORE_ITEMS = 259
    End Enum
    <StructLayout(LayoutKind.Sequential)>
    Public Class NETRESOURCE
        Public dwScope As ResourceScope
        Public dwType As ResourceType
        Public dwDisplayType As ResourceDisplayType
        Public dwUsage As ResourceUsage
        <MarshalAs(UnmanagedType.LPWStr, SizeConst:=64)>
        Public lpLocalName As String
        <MarshalAs(UnmanagedType.LPWStr, SizeConst:=64)>
        Public lpRemoteName As String
        <MarshalAs(UnmanagedType.LPWStr, SizeConst:=64)>
        Public lpComment As String
        <MarshalAs(UnmanagedType.LPWStr, SizeConst:=64)>
        Public lpProvider As String
        'This is defined as a class rather than a structure, so it's only appropriate for passing (by-value) when a pointer to a structure is needed.
        'Class vs Structure is not a problem, if you changed it to Structure the WNetOpenEnum declaration would need a ByRef NETRESOURCE parameter.
    End Class
    Public Enum ResourceScope
        CONNECTED = 1
        GLOBALNET
        REMEMBERED
        RECENT
        CONTEXT
    End Enum
    Public Enum ResourceType
        ANY
        DISK
        PRINT
        RESERVED
    End Enum
    Public Enum ResourceUsage
        CONNECTABLE = &H1
        CONTAINER = &H2
        NOLOCALDEVICE = &H4
        SIBLING = &H8
        ATTACHED = &H10
        ALL = CONNECTABLE Or CONTAINER Or ATTACHED
    End Enum
    Public Enum ResourceDisplayType
        GENERIC
        DOMAIN
        SERVER
        SHARE
        FILE
        GROUP
        NETWORK
        ROOT
        SHAREADMIN
        DIRECTORY
        TREE
        NDSCONTAINER
    End Enum
    Public Declare Auto Function WNetAddConnection2 Lib "Mpr.dll" (ByRef lpNetResource As NETRESOURCE, lpPassword As String, lpUserName As String, dwFlags As Integer) As Integer
    Public Declare Auto Function WNetCancelConnection2 Lib "Mpr.dll" (lpName As String, dwFlags As Integer, fForce As Integer) As Integer
    Public Declare Auto Function WNetOpenEnum Lib "Mpr.dll" (dwScope As ResourceScope, dwType As ResourceType, dwUsage As ResourceUsage, p As NETRESOURCE, <Out> ByRef lphEnum As IntPtr) As ErrorCodes
    Public Declare Auto Function WNetCloseEnum Lib "Mpr.dll" (hEnum As IntPtr) As ErrorCodes
    Public Declare Auto Function WNetEnumResource Lib "Mpr.dll" (hEnum As IntPtr, ByRef lpcCount As Integer, buffer As IntPtr, ByRef lpBufferSize As UInteger) As ErrorCodes
End Class
End Class
 
Last edited:
Solution
Another option for network discovery is use Shell to query the Network folder (the one in Windows Explorer). When I do this I see the same results as in Explorer. I can also see "discovery method" for each device, which lists things like SSDP, WSD and WCN. These are function discovery providers along Netbios that Wnet uses, see here Built-in Providers
The NetBIOS provider enumerates NetBIOS discoverable devices using the WNet functions.
Here's my Shell code, need to Add COM reference for the Microsoft Shell Controls And Automation library.
VB.NET:
'Imports Shell32
'Imports System.Runtime.InteropServices

Private Sub ScanNetworkFolder()
    Dim shell As New Shell
    Dim folder =...
At line 37 you're passing hEnum, but pointer to the buffer that received the enumeration results is pToGlobal

String members in NETRESOURCE must be marshaled as UnmanagedType.LPTStr
 
String members in NETRESOURCE must be marshaled as UnmanagedType.LPTStr
How? Is this related to the below?

Thanks, your first comment got around the exception but code still does not work(I don't think). Maybe because of the second comment?
It runs but the result means nothing to me.
I was hoping I it would show all the shares and then I could play around until it showed all on the WORKGROUP.
All this is strange to me and I'm trying to learn by doing.
Anyway, I converted the rest of the example and added it to the above code which is how I saw the meaningles(to me) results.
 
Last edited:
You haven't specified marshaling of the string members, default is BSTR, but they are LPTStr.

Class vs Structure is not a problem, if you changed it to Structure the WNetOpenEnum declaration would need a ByRef NETRESOURCE parameter.

I was hoping I it would show all the shares and then I could play around until it showed all on the WORKGROUP
With WNetOpenEnum(ResourceScope.GLOBALNET, ResourceType.ANY) I get the root networks only (for example "Microsoft Windows Network"), no further discovery on the workgroup.
With WNetOpenEnum(ResourceScope.CONNECTED, ResourceType.DISK) I get current network shares.
I think the network discovery part of the Wnet API is broken, some online research seems to suggest this is related to Netbios or smb v1. Even "NET VIEW" doesn't work on my Win11 computer.
 
Another option for network discovery is use Shell to query the Network folder (the one in Windows Explorer). When I do this I see the same results as in Explorer. I can also see "discovery method" for each device, which lists things like SSDP, WSD and WCN. These are function discovery providers along Netbios that Wnet uses, see here Built-in Providers
The NetBIOS provider enumerates NetBIOS discoverable devices using the WNet functions.
Here's my Shell code, need to Add COM reference for the Microsoft Shell Controls And Automation library.
VB.NET:
'Imports Shell32
'Imports System.Runtime.InteropServices

Private Sub ScanNetworkFolder()
    Dim shell As New Shell
    Dim folder = shell.NameSpace(ShellSpecialFolderConstants.ssfNETWORK)
    Dim columns = GetHeaders(folder)

    For Each item As FolderItem In folder.Items
        ShowItem(item, columns)

        If item.IsFolder Then
            ScanDevice(CType(item.GetFolder, Folder))
        End If
    Next

    Marshal.ReleaseComObject(shell)
End Sub

Private Sub ScanDevice(folder As Folder)
    Dim columns = GetHeaders(folder)

    For Each item As FolderItem In folder.Items
        ShowItem(item, columns)
    Next
End Sub

Private Sub ShowItem(item As FolderItem, columns As Dictionary(Of String, Integer))
    Debug.WriteLine(Environment.NewLine & $"path: {item.Path}")
    For Each key In columns.Keys
        Dim value = CType(item.Parent, Folder).GetDetailsOf(item, columns(key))
        If Not String.IsNullOrEmpty(value) Then
            Debug.WriteLine($"  {key}: {value}")
        End If
    Next
End Sub

Private Function GetHeaders(folder As Folder) As Dictionary(Of String, Integer)
    Dim columns As New Dictionary(Of String, Integer)
    For i As Integer = 0 To Short.MaxValue
        Dim header = folder.GetDetailsOf(folder.Items, i)
        If String.IsNullOrEmpty(header) Then
            Exit For 'no more columns
        Else
            columns(header) = i
        End If
    Next
    Return columns
End Function
 
Solution
Back
Top