【问题标题】:Connecting to a remote shared folder results in "multiple connections not allowed" error, but trying to disconnect causes "connection does not exist"连接到远程共享文件夹会导致“不允许多个连接”错误,但尝试断开连接会导致“连接不存在”
【发布时间】:2014-01-25 09:56:28
【问题描述】:

我有一个共享网络文件夹\\some.domain.net\Shared,其中包含多个共享子文件夹,不同用户具有不同的权限。我希望从同一个 Windows 帐户打开与多个子文件夹的连接,但使用不同的凭据 - 这是否可能无需先断开与同一共享的其他连接?

确切地说:在 C# 方法中,我尝试使用 WNetUseConnection() (p/invoke) 以如下方式连接到特定子文件夹:

ConnectToSharedFolder("\\some.domain.net\Shared\Subfolder1", user, password); // calls WNetUseConnection() internally 

只要没有与 root 文件夹(即 \\some.domain.net\Shared)或 另一个 共享子文件夹(或者,一般来说,到\\some.domain.net 上的任何文件夹)在调用WNetUseConnection() 以连接到子文件夹。即,考虑在连接到子文件夹之前,net use 返回:

Status       Local     Remote
------------------------------------------------
OK                     \\some.domain.net\Shared

现在我还想连接到共享子文件夹\\some.domain.net\Shared\Subfolder1,如本文顶部所示。这将导致 Windows 错误 1219:

Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. 

因此,尽管提供了不同的访问凭据,但 Windows (Server 2008 R2) 似乎无法识别 \\some.domain.net\Shared\\some.domain.net\Shared\Subfolder1 之间的区别。但是,在出现错误 1219 时尝试使用

取消连接
WNetCancelConnection2(@"\\some.domain.net\Shared\Subfolder1", 0, true); // error 2250

导致错误 2250:

This network connection does not exist.

所以看来我首先需要手动取消与\\some.domain.net\ 的所有打开连接,因为看起来一次只能打开一个 - 但是,这似乎不是很强大,因为另一个进程可能正在访问同时连接的共享文件夹。

有没有办法解决这个问题并与同一台远程计算机上的多个共享文件夹建立活动连接?

【问题讨论】:

  • 让我直截了当地说:您已连接到根共享并尝试连接到它的子文件夹。您是否对两个连接使用不同的凭据?
  • @simonatrcl:正确,是的。在这两种情况下,我都从同一个 Windows 帐户建立连接,但指定的实际凭据不同:root 文件夹的 user1/password1 和子文件夹的 user2/password2 - 这些凭据与传递给 net use 的凭据相同.
  • 在对此进行了更多研究之后,它似乎确实是一个“设计”问题,尽管它对我来说没有任何意义,看起来像是一个小故障。

标签: c# windows winapi remote-access network-share


【解决方案1】:

这是一个古老的话题,但非常实际且有问题。我会试着解释一下,因为我这几年一直在处理这样的问题。

首先: Windows 不允许您连接到一个网络共享中的多个子文件夹。

其次: Windows 正在通过远程名称识别连接。 因此,您可以使用不同的名称与同一服务器建立多个连接,例如: www.serverName.com 和 123.123.123.123(通过 ip) - 这些将被视为具有不同凭据的单独连接。

所以我的解决方案是将别名 IP 添加到我的服务器。我已经为我的服务器创建了 10 个别名,我的应用程序从列表中获取第一个 IP,然后如果它被阻止,那么接下来等等。

这个解决方案不是很好,但很有效。问题是当您无权访问服务器 IP 时。然后怎样呢?看下一点:

最后: 然后唯一的解决方案是在使用指定的网络共享后断开用户连接,这里开始所有其他问题......连接被许多阻止其他人登录的东西使用。例如,有人从网络共享打开 Word 文档 - 现在您无法断开连接!但是 net.exe 不会显示任何连接!当您在一段时间(大约一分钟)后关闭 Word 文档时,另一个 BUT 连接将自动关闭并允许新连接。

我现在的工作是查找哪些系统元素正在阻止连接并通知用户:关闭 Word,您将能够登录。 希望它可以做到。

PS。我正在使用 WinApi,因为 net.exe 的运行速度要慢得多,并且提供的选项更少。

如果有人需要源代码:

public ServerWinProcessor(string serverAddress)
  : base(serverAddress)
{

}

[DllImport("mpr.dll")]
public static extern int WNetAddConnection2(ref NETRESOURCE netResource, string password, string username, uint flags);

[DllImport("mpr.dll")]
public static extern int WNetCancelConnection2(string lpName, int dwFlags, bool fForce);

[DllImport("mpr.dll")]
public static extern int WNetOpenEnum(int dwScope, int dwType, int dwUsage, NETRESOURCE2 lpNetResource, out IntPtr lphEnum);

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

[DllImport("mpr.dll")]
private static extern int WNetEnumResource(IntPtr hEnum, ref uint lpcCount, IntPtr buffer, ref uint lpBufferSize);

public OperationResult LoginToNetworkShare(string userName, string password, string shareName)
{
  return LoginToNetworkShare(userName, password, shareName, null);
}

public OperationResult LoginToNetworkShare(string userName, string password, string shareName, string shareDrive)
{
  NETRESOURCE nr = new NETRESOURCE();
  nr.dwType = RESOURCETYPE_DISK;
  nr.lpLocalName = shareDrive;
  nr.lpRemoteName = @"\\" + ServerAddress + @"\" + shareName;

  int result = WNetAddConnection2(ref nr, password, userName, CONNECT_TEMPORARY);
  return new OperationResult(result);
}

public Task<OperationResult> LoginToNetworkShareAsync(string userName, string password, string shareName, string shareDrive)
{
  return Task.Factory.StartNew(() =>
  {
    return LoginToNetworkShare(userName, password, shareName, shareDrive);
  });
}

public OperationResult LogoutFromNetworkSharePath(string sharePath)
{
  int result = WNetCancelConnection2(sharePath, CONNECT_UPDATE_PROFILE, true);
  return new OperationResult(result);
}

public OperationResult LogoutFromNetworkShare(string shareName)
{
  int result = WNetCancelConnection2(@"\\" + ServerAddress + @"\" + shareName, CONNECT_UPDATE_PROFILE, true);
  return new OperationResult(result);
}

public OperationResult LogoutFromNetworkShareDrive(string driveLetter)
{
  int result = WNetCancelConnection2(driveLetter, CONNECT_UPDATE_PROFILE, true);
  return new OperationResult(result);
}

private ArrayList EnumerateServers(NETRESOURCE2 pRsrc, int scope, int type, int usage, ResourceDisplayType displayType)
{
  ArrayList netData = new ArrayList();
  ArrayList aData = new ArrayList();
  uint bufferSize = 16384;
  IntPtr buffer = Marshal.AllocHGlobal((int)bufferSize);
  IntPtr handle = IntPtr.Zero;
  int result;
  uint cEntries = 1;

  result = WNetOpenEnum(scope, type, usage, pRsrc, out handle);

  if (result == NO_ERROR)
  {
    do
    {
      result = WNetEnumResource(handle, ref cEntries, buffer, ref bufferSize);

      if (result == NO_ERROR)
      {
        Marshal.PtrToStructure(buffer, pRsrc);

        if (string.IsNullOrWhiteSpace(pRsrc.lpLocalName) == false && pRsrc.lpRemoteName.Contains(ServerAddress))
          if (aData.Contains(pRsrc.lpLocalName) == false)
          {
            aData.Add(pRsrc.lpLocalName);
            netData.Add(new NetworkConnectionInfo(null, pRsrc.lpLocalName));
          }

        if (aData.Contains(pRsrc.lpRemoteName) == false && pRsrc.lpRemoteName.Contains(ServerAddress))
        {
          aData.Add(pRsrc.lpRemoteName);
          netData.Add(new NetworkConnectionInfo(pRsrc.lpRemoteName, null));
        }

        if ((pRsrc.dwUsage & RESOURCEUSAGE_CONTAINER) == RESOURCEUSAGE_CONTAINER)
          netData.AddRange(EnumerateServers(pRsrc, scope, type, usage, displayType));
      }
      else if (result != ERROR_NO_MORE_ITEMS)
        break;
    } while (result != ERROR_NO_MORE_ITEMS);

    WNetCloseEnum(handle);
  }

  Marshal.FreeHGlobal(buffer);
  return netData;
}

public void CloseAllConnections()
{
  NETRESOURCE2 res = new NETRESOURCE2();
  ArrayList aData = EnumerateServers(res, RESOURCE_CONNECTED, 0, 0, ResourceDisplayType.RESOURCEDISPLAYTYPE_NETWORK);

  foreach (NetworkConnectionInfo item in aData)
  {
    if (item.IsRemoteOnly)
      LogoutFromNetworkSharePath(item.RemoteName);
    else
      LogoutFromNetworkShareDrive(item.LocalName);
  }
}
}

和其他类:

public static class Consts
  {
    public const int RESOURCETYPE_DISK = 0x1;
    public const int CONNECT_TEMPORARY = 0x00000004;
    public const int CONNECT_UPDATE_PROFILE = 0x00000001;
    public const int RESOURCE_GLOBALNET = 0x00000002;
    public const int RESOURCE_CONNECTED = 0x00000001;
    public const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
    public const int RESOURCEUSAGE_CONTAINER = 0x00000002;

    public const int NO_ERROR = 0x000;
    public const int ERROR_NOT_CONNECTED = 0x8CA;
    public const int ERROR_LOGON_FAILURE = 0x52E;
    public const int ERROR_SESSION_CREDENTIAL_CONFLICT = 0x4C3;
    public const int ERROR_ALREADY_ASSIGNED = 0x55;
    public const int ERROR_INVALID_PASSWORD = 0x56;
    public const int ERROR_INVALID_PARAMETER = 0x57;
    public const int ERROR_NO_MORE_ITEMS = 0x103;
    //public const int ERROR_BAD_PROFILE = 0x4B6;
    //public const int ERROR_CANNOT_OPEN_PROFILE = 0x4B5;
    //public const int ERROR_DEVICE_IN_USE = 0x964;
    //public const int ERROR_EXTENDED_ERROR = 0x4B8;
    //public const int ERROR_OPEN_FILES = 0x961;

    public enum ResourceDisplayType
    {
      RESOURCEDISPLAYTYPE_GENERIC,
      RESOURCEDISPLAYTYPE_DOMAIN,
      RESOURCEDISPLAYTYPE_SERVER,
      RESOURCEDISPLAYTYPE_SHARE,
      RESOURCEDISPLAYTYPE_FILE,
      RESOURCEDISPLAYTYPE_GROUP,
      RESOURCEDISPLAYTYPE_NETWORK,
      RESOURCEDISPLAYTYPE_ROOT,
      RESOURCEDISPLAYTYPE_SHAREADMIN,
      RESOURCEDISPLAYTYPE_DIRECTORY,
      RESOURCEDISPLAYTYPE_TREE,
      RESOURCEDISPLAYTYPE_NDSCONTAINER
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct NETRESOURCE
    {
      public int dwScope;
      public int dwType;
      public int dwDisplayType;
      public int dwUsage;
      public string lpLocalName;
      public string lpRemoteName;
      public string Comment;
      public string lpProvider;
    }

    [StructLayout(LayoutKind.Sequential)]
    public class NETRESOURCE2
    {
      public int dwScope = 0;
      public int dwType = 0;
      public ResourceDisplayType dwDisplayType = 0;
      public int dwUsage = 0;
      public string lpLocalName = null;
      public string lpRemoteName = null;
      public string lpComment = null;
      public string lpProvider = null;
    };
  }

最后一个:

public class NetworkConnectionInfo
  {
    public string RemoteName { get; set; }
    public string LocalName { get; set; }

    public bool IsRemoteOnly { get; set; }

    public NetworkConnectionInfo(string remoteName, string localName)
    {
      RemoteName = remoteName;
      LocalName = localName;

      if (string.IsNullOrWhiteSpace(localName))
        IsRemoteOnly = true;
    }
  }

你不需要 OperationResult 它只是简单的错误容器,不需要。 基类 ServerProcessorBase 只包含一个字段 serverAddress。

重要提示:当您无法正确设置时,这会造成问题:CONNECT_TEMPORARY 选项。如果未设置,则 Windows 将记住已安装的驱动器,并在计算机重新启动后尝试连接它们导致错误:无法连接某些驱动器 :) 很烦 :)

【讨论】:

  • 您好,您是在 HOSTS 文件中添加此别名,还是在应用中添加别名?
【解决方案2】:

好的 - this 是问题所在。它提供了几个建议的解决方案;对我来说,两者都听起来有点笨拙,但对你来说可能没问题。听起来这种行为是设计使然(可能是出于安全考虑)。

干杯-

【讨论】:

  • 谢谢,可惜方法一并不能解决问题,方法二我也无法接受。
  • 微软:“这种行为是设计使然”。感谢微软的糟糕设计。我同意建议的方法 1 或 2 都是脏的。
【解决方案3】:

我想分享我用于错误代码 1219 的解决方案,同时使用 WNetCancelConnection2() 映射具有共享路径的驱动器,即使这是一个不同的函数调用,我觉得这种方法可能会解决。

首先,您需要确定您的计算机在网络中的组织方式。

如果在域中:

您的用户名应该是 [DomainName]\[UserName],有时您可以简单地使用 [UserName]。

var userName = string.IsNullOrEmpty(credentials.Domain) ? credentials.UserName : string.Format(@"{0}\{1}", credentials.Domain, credentials.UserName);

如果在工作组中:

您的用户名应为 [ServerName]\[UserName],切勿使用 [UserName]。

这里的 ServerName 是您的共享路径的主机名。

var userName = string.Format(@"{0}\{1}", serverMachineName, credentials.UserName);

注意:只有当传递的用户名是当前登录用户名时,工作组解决方案才有效。如果您使用的是 Windows 服务,那么只需使用特定用户凭据更改登录方式

【讨论】:

    【解决方案4】:

    这确实很烦人,微软早就应该解决的问题。

    但是,这个不好的功能并不总是出现。我已经能够使用来自同一个 Windows 用户(Windows 10/64 位专业版)的不同远程凭据映射同一服务器中的三个不同子目录,并且它工作了多年,每次启动时都会出现。我使用了文件资源管理器的设备映射工具。当它起作用时,它就会起作用,但是一旦发生变化,例如其中一个远程用户的密码被更改,那么这个问题也可能发生。它还会出现,当 Windows 无法映射特定驱动器时,因为密码不正确,然后它会获取下一个远程用户凭据并尝试设备映射。如果服务器确认此用户,Windows 将无法映射下一个远程用户凭据所针对的设备。观察到有时可能由同一 Windows 用户使用不同甚至相同的远程用户凭据映射服务器子目录,这表明此行为不受 Windows 严格控制。

    很抱歉,这会使 Windows 的可用性降低。

    【讨论】:

    • 一种避免此问题的方法适用于特定情况:当您需要在同一服务器中映射多个基于服务器的资源时,创建或请求具有该服务器的用户,该服务器提供所需的权限用于收集要映射的资源的服务器。然后,使用此基于服务器的凭据为 Windows 用户映射所有这些资源。此外,不需要登录的资源必须使用相同的基于服务器的凭据进行映射。这至少适用于 Buffalo TeraStation 5400。
    猜你喜欢
    • 1970-01-01
    • 2014-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-28
    • 1970-01-01
    • 1970-01-01
    • 2017-06-02
    相关资源
    最近更新 更多