1

I'm working on a custom dialog in my WPF application that will allow the user to select a folder anywhere in the network that the user can see. I'm using code that I adapted from this CodeProject article on enumerating network resources.

The code in the original article immediately enumerates all network resources as soon as the EnumerateServers object is instantiated, recursively calling itself whenever it finds a container, and adds each node found to an ArrayList. This is inefficient as:

  1. You can't start enumerating anything until after the network tree has been fully traversed.
  2. The enumeration can take a significant amount of time depending on network traffic & the number of nodes that the current user can see.

I'm displaying the network resources in a WPF TreeView control, so I only want to traverse the nodes that are the direct children of the node that the user has expanded.

With that in mind, I made some changes to the code. Specifically, I modified it so it throws exceptions when errors occur and removed the recursion. The code now looks like this:

[StructLayout( LayoutKind.Sequential )]
internal class NetResource {
    public ResourceScope       Scope       = 0;
    public ResourceType        Type        = 0;
    public ResourceDisplayType DisplayType = 0;
    public ResourceUsage       Usage       = 0;
    public string              LocalName   = null;
    public string              RemoteName  = null;
    public string              Comment     = null;
    public string              Provider    = null;
};

public enum ResourceDisplayType {
    Generic,
    Domain,
    Server,
    Share,
    File,
    Group,
    Network,
    Root,
    ShareAdmin,
    Directory,
    Tree,
    NdsContainer
};

public enum ResourceScope {
    Connected = 1,
    GlobalNet,
    Remembered,
    Recent,
    Context
};

public enum ResourceType {
    Any,
    Disk,
    Print,
    Reserved
};

[Flags]
public enum ResourceUsage {
    Connectible   = 0x00000001,
    Container     = 0x00000002,
    NoLocalDevice = 0x00000004,
    Sibling       = 0x00000008,
    Attached      = 0x00000010,
    All           = Connectible | Container | Attached,
};

public class Share {

    public string Comment { get; private set; }
    public ResourceDisplayType DisplayType { get; private set; }
    public string Name { get; private set; }
    public string Provider { get; private set; }
    public ResourceScope Scope { get; private set; }
    public ResourceType ShareType { get; private set; }
    public ResourceUsage Usage { get; private set; }
    public string UNCPath { get; private set; }

    internal Share( NetResource netResource ) {
        DisplayType = netResource.DisplayType;
        Scope       = netResource.Scope;
        ShareType   = netResource.Type;
        Usage       = netResource.Usage;
        if ( !string.IsNullOrWhiteSpace( netResource.Comment ) ) {
            char[] commentChars = new char[ netResource.Comment.Length ];
            netResource.Comment.CopyTo( 0, commentChars, 0, netResource.Comment.Length );
            Comment = new string( commentChars );
        }
        if ( !string.IsNullOrWhiteSpace( netResource.LocalName ) ) {
            char[] localNameChars = new char[ netResource.LocalName.Length ];
            netResource.LocalName.CopyTo( 0, localNameChars, 0, netResource.LocalName.Length );
            Name = new string( localNameChars );
        }
        if ( !string.IsNullOrWhiteSpace( netResource.Provider ) ) {
            char[] providerChars = new char[ netResource.Provider.Length ];
            netResource.Provider.CopyTo( 0, providerChars, 0, netResource.Provider.Length );
            Provider = new string( providerChars );
        }
        if ( !string.IsNullOrWhiteSpace( netResource.RemoteName ) ) {
            char[] remoteNameChars = new char[ netResource.RemoteName.Length ];
            netResource.RemoteName.CopyTo( 0, remoteNameChars, 0, netResource.RemoteName.Length );
            UNCPath = new string( remoteNameChars );
        }
    }
}

public class ShareEnumerator : IEnumerable<Share> {

    public string Comment { get; set; }
    public ResourceDisplayType DisplayType { get; set; }
    public string Provider { get; set; }
    public string ResourceName { get; set; }
    public string ResourcePath { get; set; }
    public ResourceScope Scope { get; set; }
    public ResourceType ShareType { get; set; }
    public ResourceUsage Usage { get; set; }

    public ShareEnumerator() { }

    public ShareEnumerator( Share aShare ) {
        Comment      = aShare.Comment;
        DisplayType  = aShare.DisplayType;
        Provider     = aShare.Provider;
        ResourceName = aShare.Name;
        ResourcePath = aShare.UNCPath;
        Scope        = aShare.Scope;
        ShareType    = aShare.ShareType;
        Usage        = aShare.Usage;
    }

    public IEnumerator<Share> GetEnumerator() {
        NetResource netResource = new NetResource {
            Comment     = this.Comment,
            DisplayType = this.DisplayType,
            LocalName   = this.ResourceName,
            Provider    = this.Provider,
            RemoteName  = this.ResourcePath,
            Scope       = this.Scope,
            Type        = this.ShareType,
            Usage       = this.Usage
        };
        uint        bufferSize = 16384;
        IntPtr        buffer     = IntPtr.Zero;
        uint        cEntries   = 1;
        IntPtr        handle     = IntPtr.Zero;
        ErrorCodes  result;

        try {
            buffer = Marshal.AllocHGlobal( (int) bufferSize );
            result = WNetOpenEnum( Scope, ShareType, Usage, netResource, out handle );
            if ( result != ErrorCodes.NO_ERROR ) {
                throw new InvalidOperationException( string.Format( "The call to WNetOpenEnum failed: the result code was {0:x}", (int) result ) );
            }

            try {
                do {
                    result = WNetEnumResource( handle, ref cEntries, buffer, ref bufferSize );
                    if ( result == ErrorCodes.NO_ERROR ) {
                        // It was.  Marshal the buffer into the NetResource object.
                        Marshal.PtrToStructure( buffer, netResource );

                        if ( netResource.DisplayType == DisplayType || netResource.DisplayType == ResourceDisplayType.Domain ) {
                            // We do. Convert it into a Share & enumerate it.
                            yield return new Share( netResource );
                        }
                    } else if ( result == ErrorCodes.ERROR_NO_MORE_ITEMS ) {
                        break;
                    } else {
                        throw new InvalidOperationException( string.Format( "The call to WNetEnumResource failed: the result code was {0:x}", (int) result ) );
                    }
                } while ( result == ErrorCodes.NO_ERROR );
            } finally {
                WNetCloseEnum( (IntPtr) buffer );
            }
        } finally {
            if ( buffer != IntPtr.Zero ) {
                // VERY IMPORTANT! Deallocate the buffer to prevent memory leaks!!
                Marshal.FreeHGlobal( buffer );
            }
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }

    private enum ErrorCodes {
        NO_ERROR = 0,
        ERROR_NO_MORE_ITEMS = 259
    };

    [DllImport( "Mpr.dll", EntryPoint = "WNetOpenEnumA", CallingConvention = CallingConvention.Winapi )]
    private static extern ErrorCodes WNetOpenEnum( ResourceScope dwScope, ResourceType dwType, ResourceUsage dwUsage, NetResource p, out IntPtr lphEnum );

    [DllImport( "Mpr.dll", EntryPoint = "WNetCloseEnum", CallingConvention = CallingConvention.Winapi )]
    private static extern ErrorCodes WNetCloseEnum( IntPtr hEnum );

    [DllImport( "Mpr.dll", EntryPoint = "WNetEnumResourceA", CallingConvention = CallingConvention.Winapi )]
    private static extern ErrorCodes WNetEnumResource( IntPtr hEnum, ref uint lpcCount, IntPtr buffer, ref uint lpBufferSize );
}

}

When the user clicks on the "Entire Network" node in the TreeView for a network node, this code runs:

private void NetworkExpanded( object sender, RoutedEventArgs e ) {
    if ( !ShareScanner.IsBusy ) {
        OriginalCursor = LayoutRoot.Cursor;
        LayoutRoot.Cursor = Mouse.OverrideCursor = Cursors.Wait;

        TreeViewItem networkItem = sender as TreeViewItem;
        if ( networkItem.Items.Count == 1 && networkItem.Items[ 0 ] == dummyNode ) {
            networkItem.Items.Clear();
            ShareScanner.RunWorkerAsync( new ShareScannerArgs( networkItem, networkItem.Tag as Share, ShareScannerTypes.Computers ) );
        }
    }
    e.Handled = true;
}

Underneath the Entire Network node will be nodes for specific computers that the user can see. When they expand one of these nodes, this code is run:

private void NetworkComputerExpanded( object sender, RoutedEventArgs e ) {
    if ( !ShareScanner.IsBusy ) {
        OriginalCursor = LayoutRoot.Cursor;
        LayoutRoot.Cursor = Mouse.OverrideCursor = Cursors.Wait;

        TreeViewItem computerItem = sender as TreeViewItem;

        if ( computerItem.Items.Count == 1 && computerItem.Items[ 0 ] == dummyNode ) {
            computerItem.Items.Clear();
                ShareScanner.RunWorkerAsync( new ShareScannerArgs( computerItem, computerItem.Tag as Share, ShareScannerTypes.Shares ) );
        }
    }
    e.Handled = true;
}

Below the computer nodes there will be "Share" nodes. When they click on a "Share" node in the TreeView, this code runs:

private void NetworkShareExpanded( object sender, RoutedEventArgs e ) {
    if ( !ShareScanner.IsBusy ) {
        OriginalCursor = LayoutRoot.Cursor;
        LayoutRoot.Cursor = Mouse.OverrideCursor = Cursors.Wait;

        TreeViewItem shareItem = sender as TreeViewItem;

            if ( shareItem.Items.Count == 1 && shareItem.Items[ 0 ] == dummyNode ) {
                shareItem.Items.Clear();
                ShareScanner.RunWorkerAsync( new ShareScannerArgs( shareItem, shareItem.Tag as Share, ShareScannerTypes.Folders ) );
        }
    }
    e.Handled = true;
}

There will be nodes for individual folders below the shares, but I haven't written any code for that level yet as I'm trying to get the other levels to work.

As you can see from the code I've posted, I'm using a BackgroundWorker to actually do the work. The UI becomes unresponsive while the calls to WNetOpenEnum and WNetEnumResource execute. My UI has to remain responsive, so I use the BackgroundWorker to do the waiting & keep the UI responsive.

Here's the BackgroundWorker's DoWork event handler:

private void ShareScanner_DoWork( object sender, DoWorkEventArgs e ) {
    BackgroundWorker worker = sender as BackgroundWorker;
    ShareScannerArgs info = e.Argument as ShareScannerArgs;

    ShareEnumerator enumerator = null;

    switch ( info.WhatToScanFor ) {
        case ShareScannerTypes.Computers:
            enumerator = new ShareEnumerator {
                DisplayType = ResourceDisplayType.Network,        // Retrieve Servers only
                Scope       = ResourceScope.GlobalNet,            // Retrieve only objects the user can see
                ShareType   = ResourceType.Disk,                  // Retrieve only Disk shares
                Usage       = ResourceUsage.All                   // Retrieve all Connectible, Container & Attached nodes.
            };
            break;

        case ShareScannerTypes.Shares:
        case ShareScannerTypes.Folders:
            enumerator = new ShareEnumerator( info.ParentShare );
            break;

        default:
            // Should never get here!
            throw new InvalidOperationException( string.Format( "Unknown ShareScannerType: {0}", info.WhatToScanFor ) );
    }

    try {
        foreach ( Share share in enumerator ) {
            if ( worker.CancellationPending ) {
                e.Cancel = true;
                return;
            }

            worker.ReportProgress( 0, new NodeArgs( info.Parent, share, info.WhatToScanFor ) );
        }
    } catch ( Exception ) { }
}

Here's the code for the ProgressChanged event handler:

private void ShareScanner_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
    NodeArgs nodeArgs = e.UserState as NodeArgs;
    Share parentShare = nodeArgs.Tag as Share;

    TreeViewItem item = new TreeViewItem {
        Header = parentShare.UNCPath,
        Tag    = nodeArgs.Tag
    };

    switch ( nodeArgs.NodeToBuild ) {
        case ShareScannerTypes.Computers:
            item.Items.Add( dummyNode );
            item.Expanded += new RoutedEventHandler( NetworkComputerExpanded );
            break;

        case ShareScannerTypes.Shares:
            item.Items.Add( dummyNode );
            item.Expanded += new RoutedEventHandler( NetworkShareExpanded );
            break;

        case ShareScannerTypes.Folders:
            break;

        default:
            // Should never get here!
            throw new InvalidOperationException( string.Format( "Unknown ShareScannerType: : {0}", nodeArgs.NodeToBuild ) );
    }

    nodeArgs.Parent.Items.Add( item );
}

Finally, the code for the RunWorkerCompleted event:

private void ShareScanner_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
    Mouse.OverrideCursor = null;
    LayoutRoot.Cursor = OriginalCursor;
}

The code produces a tree with the 'Entire Network' node. When you expand it, I get entries for "Microsoft Terminal Services", "Microsoft Windows Network", and "Web Client Network". When I expand any of these, WNetOpenEnum fails, returning 57 as the result.

What am I doing wrong? The code looked pretty straight forward but obviously I've missed something.

Tony Vitabile
  • 8,298
  • 15
  • 67
  • 123
  • have you thought about using a different approach for example using `WMI` – MethodMan Aug 13 '14 at 21:04
  • I've never used WMI. Don't even know what it is. Can you point me to some articles? – Tony Vitabile Aug 13 '14 at 21:07
  • I don't believe WMI will let you enumerate UNC shares. Take a look at this question and answer -- SO seems to have quite a few C#-enumerate network shares-UNC questions going on: http://stackoverflow.com/questions/8918477/how-can-i-enumerate-network-shares-and-web-folders-in-c – Jason Aug 13 '14 at 21:08

1 Answers1

1

I finally got my dialog working as I wanted, but not using any of the code posted in the original question. Instead, I replaced all of that code with code taken from two other articles on CodeProject. Here are the comments from the code in my application with the information about where the code came from:

The `ComputerEnumerator` class originated as the `NetworkBrowser.cs` class from the 
"Retrieving a List of Network Computer Names Using C#" article, which can be found at:
http://www.codeproject.com/Articles/16113/Retreiving-a-list-of-network-computer-names-using

The ShareEnumerator class originated as the ServerEnum.cs class from the
"Network Shares and UNC paths" article, which can be found at:
http://www.codeproject.com/Articles/2939/Network-Shares-and-UNC-paths

The first class makes use of the NetServerEnum function to retrieve all of the computers available on the network, while the second class uses the NetShareEnum method to retrieve the shares available on a particular server. The code from the first article worked correctly as it was. There were a couple of issues with the code in the second article, though.

The biggest problem was in the DllImport for the NetApiBufferFree function. When my code called it in the debugger when I stepped over the call, the function must have errored or something because the debugger never regained control and the code in the method that restored the mouse cursor never ran. When I replaced the definition of that API with the one from the first article, everything worked.

The less serious problem is that the second article created an enum type called ShareType that was decorated with the [Flags] attribute. The problem with this is that the values for the enum are taken from the LMShares.h file and are sequential values, not powers of 2. Such an enum will not work as a set of flags. There should be no enum identifier with the value 3, for example, because that value should be formed by bit-wise ORing the enum values for 1 & 2 together. But the enum identifier for the IPC type, sure enough, is 3.

This problem would (and did) cause problems if you try to use the values in this enum to filter the shares returned. To fix this latter problem, I removed the [Flags] attribute from that type & do not use is with any bit-wise operations.

It is amazing how quickly these functions return values. I'm sure they're cached somewhere and they may take longer to return values in certain circumstances. But in any case I have the code working as I'd like it to work.

Tony Vitabile
  • 8,298
  • 15
  • 67
  • 123