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:
- You can't start enumerating anything until after the network tree has been fully traversed.
- 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.