私たちは解決する必要がある興味深い状況に遭遇しました、そして、私の検索はゼロになりました。したがって、私はSOコミュニティに助けを求めます。
問題はこれです。ドメイン内になく、リモートファイル共有/ UNCを介して信頼できる外部ドメイン内にない共有ファイルにプログラムでアクセスする必要があります。当然、リモートマシンに資格情報を提供する必要があります。
通常、次の2つの方法のいずれかでこの問題を解決します。
Net Use
コマンドまたはNet Use
を複製するWin32関数を使用して行われます。さまざまな理由から、セキュリティ/ネットワークアーキテクトは最初の2つのアプローチを拒否しました。 2番目のアプローチは明らかにセキュリティホールです。リモートコンピューターが侵害された場合、ローカルコンピューターが危険にさらされます。新しくマウントされたドライブは、プログラムによるファイルアクセス中にローカルコンピューター上の他のプログラムが使用できる共有リソースであるため、最初のアプローチは不十分です。これを一時的にすることはかなり可能ですが、彼らの意見にはまだ穴があります。
3番目のオプションは公開されていますが、リモートネットワーク管理者はFTPSではなくSFTPを要求し、FtpWebRequestはFTPSのみをサポートします。 SFTP isよりファイアウォールに優しいオプションであり、そのアプローチに使用できるライブラリがいくつかありますが、可能な場合は依存関係を減らしたいと思います。
リモートファイル共有を使用するマネージド手段またはwin32手段についてMSDNを検索しましたが、有用なものを思い付くことはできませんでした。
だから私は尋ねる:別の方法はありますか?私がしたいことをする極秘のwin32関数を見逃しましたか?または、オプション3のバリエーションを追求する必要がありますか?
問題を解決する方法は、 WNetUseConnection と呼ばれるWin32 APIを使用することです。
この関数を使用して、ドライブをマップするのではなく、認証付きでUNCパスに接続します。
これにより、同じドメイン上にない場合や、ユーザー名とパスワードが異なる場合でも、リモートマシンに接続できます。
WNetUseConnectionを使用すると、同じドメインにいるかのようにUNCパスを介してファイルにアクセスできるようになります。最善の方法は、おそらく管理ビルトイン共有を使用することです。
例:\\ computername\c $\program files\Folder\file.txt
WNetUseConnectionを使用するC#コードのサンプル 。
NetResourceの場合、lpLocalNameおよびlpProviderにnullを渡す必要があります。 dwTypeはRESOURCETYPE_DISKである必要があります。 lpRemoteNameは\\ ComputerNameである必要があります。
迅速な解決策を探している人のために、最近書いたNetworkShareAccesser
を使用できます( この答え に基づいています)。
使用法:
using (NetworkShareAccesser.Access(REMOTE_COMPUTER_NAME, DOMAIN, USER_NAME, PASSWORD))
{
File.Copy(@"C:\Some\File\To\copy.txt", @"\\REMOTE-COMPUTER\My\Shared\Target\file.txt");
}
警告:Dispose
のNetworkShareAccesser
が(アプリがクラッシュした場合でも!)呼び出されることを絶対に確認してください。開いた接続はWindows上に残ります。 cmd
プロンプトを開いてNet Use
と入力すると、開いているすべての接続を確認できます。
コード:
/// <summary>
/// Provides access to a network share.
/// </summary>
public class NetworkShareAccesser : IDisposable
{
private string _remoteUncName;
private string _remoteComputerName;
public string RemoteComputerName
{
get
{
return this._remoteComputerName;
}
set
{
this._remoteComputerName = value;
this._remoteUncName = @"\\" + this._remoteComputerName;
}
}
public string UserName
{
get;
set;
}
public string Password
{
get;
set;
}
#region Consts
private const int RESOURCE_CONNECTED = 0x00000001;
private const int RESOURCE_GLOBALNET = 0x00000002;
private const int RESOURCE_REMEMBERED = 0x00000003;
private const int RESOURCETYPE_ANY = 0x00000000;
private const int RESOURCETYPE_DISK = 0x00000001;
private const int RESOURCETYPE_PRINT = 0x00000002;
private const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
private const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
private const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
private const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
private const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
private const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;
private const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
private const int RESOURCEUSAGE_CONTAINER = 0x00000002;
private const int CONNECT_INTERACTIVE = 0x00000008;
private const int CONNECT_Prompt = 0x00000010;
private const int CONNECT_REDIRECT = 0x00000080;
private const int CONNECT_UPDATE_PROFILE = 0x00000001;
private const int CONNECT_COMMANDLINE = 0x00000800;
private const int CONNECT_CMD_SAVECRED = 0x00001000;
private const int CONNECT_LOCALDRIVE = 0x00000100;
#endregion
#region Errors
private const int NO_ERROR = 0;
private const int ERROR_ACCESS_DENIED = 5;
private const int ERROR_ALREADY_ASSIGNED = 85;
private const int ERROR_BAD_DEVICE = 1200;
private const int ERROR_BAD_NET_NAME = 67;
private const int ERROR_BAD_PROVIDER = 1204;
private const int ERROR_CANCELLED = 1223;
private const int ERROR_EXTENDED_ERROR = 1208;
private const int ERROR_INVALID_ADDRESS = 487;
private const int ERROR_INVALID_PARAMETER = 87;
private const int ERROR_INVALID_PASSWORD = 1216;
private const int ERROR_MORE_DATA = 234;
private const int ERROR_NO_MORE_ITEMS = 259;
private const int ERROR_NO_NET_OR_BAD_PATH = 1203;
private const int ERROR_NO_NETWORK = 1222;
private const int ERROR_BAD_PROFILE = 1206;
private const int ERROR_CANNOT_OPEN_PROFILE = 1205;
private const int ERROR_DEVICE_IN_USE = 2404;
private const int ERROR_NOT_CONNECTED = 2250;
private const int ERROR_OPEN_FILES = 2401;
#endregion
#region PInvoke Signatures
[DllImport("Mpr.dll")]
private static extern int WNetUseConnection(
IntPtr hwndOwner,
NETRESOURCE lpNetResource,
string lpPassword,
string lpUserID,
int dwFlags,
string lpAccessName,
string lpBufferSize,
string lpResult
);
[DllImport("Mpr.dll")]
private static extern int WNetCancelConnection2(
string lpName,
int dwFlags,
bool fForce
);
[StructLayout(LayoutKind.Sequential)]
private class NETRESOURCE
{
public int dwScope = 0;
public int dwType = 0;
public int dwDisplayType = 0;
public int dwUsage = 0;
public string lpLocalName = "";
public string lpRemoteName = "";
public string lpComment = "";
public string lpProvider = "";
}
#endregion
/// <summary>
/// Creates a NetworkShareAccesser for the given computer name. The user will be promted to enter credentials
/// </summary>
/// <param name="remoteComputerName"></param>
/// <returns></returns>
public static NetworkShareAccesser Access(string remoteComputerName)
{
return new NetworkShareAccesser(remoteComputerName);
}
/// <summary>
/// Creates a NetworkShareAccesser for the given computer name using the given domain/computer name, username and password
/// </summary>
/// <param name="remoteComputerName"></param>
/// <param name="domainOrComuterName"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
public static NetworkShareAccesser Access(string remoteComputerName, string domainOrComuterName, string userName, string password)
{
return new NetworkShareAccesser(remoteComputerName,
domainOrComuterName + @"\" + userName,
password);
}
/// <summary>
/// Creates a NetworkShareAccesser for the given computer name using the given username (format: domainOrComputername\Username) and password
/// </summary>
/// <param name="remoteComputerName"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
public static NetworkShareAccesser Access(string remoteComputerName, string userName, string password)
{
return new NetworkShareAccesser(remoteComputerName,
userName,
password);
}
private NetworkShareAccesser(string remoteComputerName)
{
RemoteComputerName = remoteComputerName;
this.ConnectToShare(this._remoteUncName, null, null, true);
}
private NetworkShareAccesser(string remoteComputerName, string userName, string password)
{
RemoteComputerName = remoteComputerName;
UserName = userName;
Password = password;
this.ConnectToShare(this._remoteUncName, this.UserName, this.Password, false);
}
private void ConnectToShare(string remoteUnc, string username, string password, bool promptUser)
{
NETRESOURCE nr = new NETRESOURCE
{
dwType = RESOURCETYPE_DISK,
lpRemoteName = remoteUnc
};
int result;
if (promptUser)
{
result = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_Prompt, null, null, null);
}
else
{
result = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);
}
if (result != NO_ERROR)
{
throw new Win32Exception(result);
}
}
private void DisconnectFromShare(string remoteUnc)
{
int result = WNetCancelConnection2(remoteUnc, CONNECT_UPDATE_PROFILE, false);
if (result != NO_ERROR)
{
throw new Win32Exception(result);
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
this.DisconnectFromShare(this._remoteUncName);
}
}
知る限り、サーバーの資格情報を確立するためにドライブ文字へのUNCパスをmapする必要はありません。私は定期的に次のようなバッチスクリプトを使用しました。
Net Use \\myserver /user:username password
:: do something with \\myserver\the\file\i\want.xml
Net Use /delete \\my.server.com
ただし、プログラムと同じアカウントで実行されているプログラムは、username:password
がアクセスできるすべてのものにアクセスできます。考えられる解決策は、独自のローカルユーザーアカウントでプログラムを分離することです(UNCアクセスは、Net Use
を呼び出したアカウントに対してローカルです)。
注:ドメイン間でSMBを使用することは、IMOテクノロジーの適切な使用方法ではありません。セキュリティがそれほど重要である場合、SMBが暗号化を欠いているという事実は、それ自体が少しダンパーです。
私は自分自身を知りませんが、私は確かに#2が間違っていることを望みます...私はWindowsがログイン情報(すべてのパスワードの少なくとも!)を任意のマシンに自動的に提供しないと思いたい、私の信頼の一部ではないものは言うまでもありません。
とにかく、偽装アーキテクチャを検討しましたか?コードは次のようになります。
using (System.Security.Principal.WindowsImpersonationContext context = System.Security.Principal.WindowsIdentity.Impersonate(token))
{
// Do network operations here
context.Undo();
}
この場合、token
変数はIntPtrです。この変数の値を取得するには、アンマネージLogonUser Windows API関数を呼び出す必要があります。 pinvoke.net に簡単にアクセスすると、次の署名が得られます。
[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken
);
ユーザー名、ドメイン、およびパスワードはかなり明白に見えるはずです。 dwLogonTypeおよびdwLogonProviderに渡すことができるさまざまな値を見て、ニーズに最適な値を決定してください。
確認できる2番目のドメインがないので、このコードはテストされていませんが、うまくいけば正しい軌道に乗れるはずです。
WNetUseConnectionではなく、 NetUseAdd をお勧めします。 WNetUseConnectionは、WNetUseConnection2とWNetUseConnection3に取って代わられたレガシー関数ですが、これらの関数はすべて、Windowsエクスプローラーに表示されるネットワークデバイスを作成します。 NetUseAddは、DOSプロンプトでNet Useを呼び出してリモートコンピューターで認証するのと同じです。
NetUseAddを呼び出すと、その後のディレクトリへのアクセス試行は成功するはずです。
ほとんどのSFTPサーバーもSCPをサポートしているため、ライブラリの検索がはるかに簡単になります。 PuTTY に含まれるpscpのようなコードから既存のクライアントを呼び出すことさえできます。
作業しているファイルの種類がテキストファイルやXMLファイルのような単純なものである場合、.NET RemotingやWebサービスなどを使用してファイルを操作する独自のクライアント/サーバー実装を作成することさえできます。
JScape tools で実装されたオプション3は、非常に簡単な方法で見ました。試してみてください。それは無料ではありませんが、その仕事をします。
ここでは、最小限のPOCクラスとすべての不要な要素を削除しました
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
public class UncShareWithCredentials : IDisposable
{
private string _uncShare;
public UncShareWithCredentials(string uncShare, string userName, string password)
{
var nr = new Native.NETRESOURCE
{
dwType = Native.RESOURCETYPE_DISK,
lpRemoteName = uncShare
};
int result = Native.WNetUseConnection(IntPtr.Zero, nr, password, userName, 0, null, null, null);
if (result != Native.NO_ERROR)
{
throw new Win32Exception(result);
}
_uncShare = uncShare;
}
public void Dispose()
{
if (!string.IsNullOrEmpty(_uncShare))
{
Native.WNetCancelConnection2(_uncShare, Native.CONNECT_UPDATE_PROFILE, false);
_uncShare = null;
}
}
private class Native
{
public const int RESOURCETYPE_DISK = 0x00000001;
public const int CONNECT_UPDATE_PROFILE = 0x00000001;
public const int NO_ERROR = 0;
[DllImport("mpr.dll")]
public static extern int WNetUseConnection(IntPtr hwndOwner, NETRESOURCE lpNetResource, string lpPassword, string lpUserID,
int dwFlags, string lpAccessName, string lpBufferSize, string lpResult);
[DllImport("mpr.dll")]
public static extern int WNetCancelConnection2(string lpName, int dwFlags, bool fForce);
[StructLayout(LayoutKind.Sequential)]
public class NETRESOURCE
{
public int dwScope;
public int dwType;
public int dwDisplayType;
public int dwUsage;
public string lpLocalName;
public string lpRemoteName;
public string lpComment;
public string lpProvider;
}
}
}
\\server\share\folder
w/WNetUseConnection
を直接使用できます。事前に\\server
部分にストリップする必要はありません。
imvb.net codebrian リファレンスに基づいて添付
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Public Class PinvokeWindowsNetworking
Const NO_ERROR As Integer = 0
Private Structure ErrorClass
Public num As Integer
Public message As String
Public Sub New(ByVal num As Integer, ByVal message As String)
Me.num = num
Me.message = message
End Sub
End Structure
Private Shared ERROR_LIST As ErrorClass() = New ErrorClass() {
New ErrorClass(5, "Error: Access Denied"),
New ErrorClass(85, "Error: Already Assigned"),
New ErrorClass(1200, "Error: Bad Device"),
New ErrorClass(67, "Error: Bad Net Name"),
New ErrorClass(1204, "Error: Bad Provider"),
New ErrorClass(1223, "Error: Cancelled"),
New ErrorClass(1208, "Error: Extended Error"),
New ErrorClass(487, "Error: Invalid Address"),
New ErrorClass(87, "Error: Invalid Parameter"),
New ErrorClass(1216, "Error: Invalid Password"),
New ErrorClass(234, "Error: More Data"),
New ErrorClass(259, "Error: No More Items"),
New ErrorClass(1203, "Error: No Net Or Bad Path"),
New ErrorClass(1222, "Error: No Network"),
New ErrorClass(1206, "Error: Bad Profile"),
New ErrorClass(1205, "Error: Cannot Open Profile"),
New ErrorClass(2404, "Error: Device In Use"),
New ErrorClass(2250, "Error: Not Connected"),
New ErrorClass(2401, "Error: Open Files")}
Private Shared Function getErrorForNumber(ByVal errNum As Integer) As String
For Each er As ErrorClass In ERROR_LIST
If er.num = errNum Then Return er.message
Next
Try
Throw New Win32Exception(errNum)
Catch ex As Exception
Return "Error: Unknown, " & errNum & " " & ex.Message
End Try
Return "Error: Unknown, " & errNum
End Function
<DllImport("Mpr.dll")>
Private Shared Function WNetUseConnection(ByVal hwndOwner As IntPtr, ByVal lpNetResource As NETRESOURCE, ByVal lpPassword As String, ByVal lpUserID As String, ByVal dwFlags As Integer, ByVal lpAccessName As String, ByVal lpBufferSize As String, ByVal lpResult As String) As Integer
End Function
<DllImport("Mpr.dll")>
Private Shared Function WNetCancelConnection2(ByVal lpName As String, ByVal dwFlags As Integer, ByVal fForce As Boolean) As Integer
End Function
<StructLayout(LayoutKind.Sequential)>
Private Class NETRESOURCE
Public dwScope As Integer = 0
Public dwType As Integer = 0
Public dwDisplayType As Integer = 0
Public dwUsage As Integer = 0
Public lpLocalName As String = ""
Public lpRemoteName As String = ""
Public lpComment As String = ""
Public lpProvider As String = ""
End Class
Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String) As String
Return connectToRemote(remoteUNC, username, password, False)
End Function
Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String, ByVal promptUser As Boolean) As String
Dim nr As NETRESOURCE = New NETRESOURCE()
nr.dwType = ResourceTypes.Disk
nr.lpRemoteName = remoteUNC
Dim ret As Integer
If promptUser Then
ret = WNetUseConnection(IntPtr.Zero, nr, "", "", Connects.Interactive Or Connects.Prompt, Nothing, Nothing, Nothing)
Else
ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, Nothing, Nothing, Nothing)
End If
If ret = NO_ERROR Then Return Nothing
Return getErrorForNumber(ret)
End Function
Public Shared Function disconnectRemote(ByVal remoteUNC As String) As String
Dim ret As Integer = WNetCancelConnection2(remoteUNC, Connects.UpdateProfile, False)
If ret = NO_ERROR Then Return Nothing
Return getErrorForNumber(ret)
End Function
Enum Resources As Integer
Connected = &H1
GlobalNet = &H2
Remembered = &H3
End Enum
Enum ResourceTypes As Integer
Any = &H0
Disk = &H1
Print = &H2
End Enum
Enum ResourceDisplayTypes As Integer
Generic = &H0
Domain = &H1
Server = &H2
Share = &H3
File = &H4
Group = &H5
End Enum
Enum ResourceUsages As Integer
Connectable = &H1
Container = &H2
End Enum
Enum Connects As Integer
Interactive = &H8
Prompt = &H10
Redirect = &H80
UpdateProfile = &H1
CommandLine = &H800
CmdSaveCred = &H1000
LocalDrive = &H100
End Enum
End Class
使用方法
Dim login = PinvokeWindowsNetworking.connectToRemote("\\ComputerName", "ComputerName\UserName", "Password")
If IsNothing(login) Then
'do your thing on the shared folder
PinvokeWindowsNetworking.disconnectRemote("\\ComputerName")
End If