0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C#】外部サーバーへ接続するためのサンプルコード

Last updated at Posted at 2024-12-31

概要

C#でデスクトップアプリから外部サーバーへアクセスするためのサンプルコードを記載しました。NET USEを使用して接続する方法と、WindowsAPIを使用して接続する方法を記載しています。

コード

業務で行った対応を元にサンプルコードを記載しています。
業務ではNET USEコマンドでは現在ログイン中のユーザーIDを用いて接続、WindowsAPIではあらかじめ決められたユーザーIDとパスワードを用いて接続、接続できなければユーザーIDとパスワードを入力する画面を表示、という形で実装しましたので、サンプルコードもその形で記載しています。

1. NET USEコマンドを使用して外部サーバーに接続する

NET USEコマンドとは

Windowsコマンドの一つで、ネットワーク上の外部サーバーの共有フォルダに接続したり、切断することができるコマンドです。netコマンドというネットワーク関連を管理できるコマンドの一つです。

ServerConnectorByNetUse.cs
    public class ServerConnectorByNetUse
    {
        /// <summary>UNCパス</summary>
        private string UncPath { get; set; }

        /// <summary>ユーザーID</summary>
        private string UserId { get; set; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="uncPath">UNCパス</param>
        /// <param name="userId">ユーザーID</param>
        public ServerConnectorByNetUse(string uncPath, string userId)
        {
            UncPath = uncPath;
            UserId = userId;
        }

        /// <summary>
        /// サーバーに接続する
        /// </summary>
        /// <returns>結果 (true : 成功、false : 失敗)</returns>
        public bool TryConnect()
        {
            try
            {
                using (var open = new Process())
                {
                    // 既に接続済みの場合がある為、一旦接続を解除する
                    Disconnect();

                    open.StartInfo.FileName = "cmd.exe";                                // コマンド名
                    open.StartInfo.Arguments = "/c";                                    // 引数①
                    open.StartInfo.Arguments += $@"net use {UncPath} /user:{UserId}";   // 引数② (接続コマンド)
                    open.StartInfo.CreateNoWindow = true;                               // DOSプロンプトの黒い画面を非表示
                    open.StartInfo.UseShellExecute = false;                             // プロセスを新しいウィンドウで起動するか否か
                    open.StartInfo.RedirectStandardOutput = true;                       // 標準出力をリダイレクトで取得する
                    open.StartInfo.RedirectStandardError = true;                        // エラー出力をリダイレクトで取得する
                    open.Start();

                    if (!open.WaitForExit(1000))
                    {
                        // タイムアウトした場合
                        return false;
                    }

                    string standerdOutput = open.StandardOutput.ReadToEnd(); // 標準出力のメッセージを取得 (ここではこの変数は使用していません)
                    string standardError = open.StandardError.ReadToEnd();   // エラー出力のメッセージを取得
                    int exitCode = open.ExitCode;

                    if (!string.IsNullOrEmpty(standardError))
                    {
                        // エラー出力がある場合

                        string strErrorCode = GetErrorCode(standardError);
                        if (int.TryParse(strErrorCode, out int errorCode))
                        {
                            // エラーコードをログ出力 (省略)

                            // 他ユーザーアカウントで既に接続が確立されている場合は接続OKとする
                            if (errorCode == 1219) return true;
                        }
                    }

                    return exitCode == 0;

                }
            }
            catch (Exception)
            {
                // エラーハンドル処理 (省略)
            }
        }

        /// <summary>
        /// サーバーへの接続を解除する
        /// </summary>
        public void Disconnect()
        {
            try
            {
                using (var open = new Process())
                {

                    open.StartInfo.FileName = "cmd.exe";                                // コマンド名
                    open.StartInfo.CreateNoWindow = true;                               // DOSプロンプトの黒い画面を非表示
                    open.StartInfo.UseShellExecute = false;                             // プロセスを新しいウィンドウで起動するか否か
                    open.StartInfo.RedirectStandardOutput = true;                       // 標準出力をリダイレクトで取得したい
                    open.StartInfo.RedirectStandardError = true;                        // エラー出力をリダイレクトで取得したい
                    open.StartInfo.Arguments = "/c";                                    // 引数①
                    open.StartInfo.Arguments += $@"net use {UncPath} /delete /no";      // 引数② (切断コマンド)
                    open.Start();
                    if (!open.WaitForExit(1000))
                    {
                        // タイムアウトした場合
                        return;
                    }

                    string standerdOutput = open.StandardOutput.ReadToEnd(); // 標準出力のメッセージを取得 (ここではこの変数は使用していません)
                    string standardError = open.StandardError.ReadToEnd();   // エラー出力のメッセージを取得
                    int exitCode = open.ExitCode;

                    if (!string.IsNullOrEmpty(standardError))
                    {
                        // エラー出力がある場合
                        string strErrorCode = GetErrorCode(standardError);
                        if (int.TryParse(strErrorCode, out int errorCode))
                        {
                            // エラーコードをログ出力 (省略)
                        }
                    }
                }
            }
            catch (Exception)
            {
                // エラーハンドル処理 (省略)
            }
        }

        /// <summary>
        /// エラーメッセージからエラーコードを返却する
        /// </summary>
        /// <param name="errorMessage">エラーメッセージ</param>
        /// <returns>エラーコード</returns>
        private string GetErrorCode(string errorMessage)
        {
            var errorMessageList = errorMessage.Split(' ', '\n').ToList();
            foreach (string targetMessage in errorMessageList)
            {
                if (int.TryParse(targetMessage, out _)) return targetMessage;
            }

            return string.Empty;
        }
    }

補足

  • 正常に接続が成功すると、int exitCode = open.ExitCode;0が返却されます。

  • 以下のように

    open.StartInfo.RedirectStandardOutput = true;
    open.StartInfo.RedirectStandardError = true;
    

    を行っていないと

    string standerdOutput = open.StandardOutput.ReadToEnd();
    string standardError = open.StandardError.ReadToEnd();
    

    の部分でエラーとなります。

NET USE接続時のエラーコードについて

エラー出力 (コード上のopen.StandardError.ReadToEnd();で得られる文字列) がある場合、エラー出力の文字列からエラーコードを抽出しています。(GetErrorCode()メソッドの部分)
エラーコードは沢山ありますが、よくある?のは下記かと思います。

エラーコード 説明
-99 サーバー接続時にエラーが発生しました。
-1 サーバー接続時にタイムアウトしました。
53 ネットワークパスが見つかりません。
67 ネットワーク名が見つかりません。
87 パラメーターが間違っています。
1219 同じユーザーによる、サーバーまたは共有リソースへの複数のユーザー名での複数の接続は許可されません。サーバーまたは共有リソースへの以前の接続をすべて切断してから、再試行してください。
1223 この操作はユーザーによって取り消されました。
1312 指定されたログオン セッションは存在しません。そのセッションは既に終了している可能性があります。
1326 ユーザー名またはパスワードが正しくありません。
2250 このネットワーク接続はありません。

参考:システム エラー コード

2. WindowsAPIを使用して外部サーバーに接続する

WNetAddConnection2について

WindowsAPIに「WNetAddConnection2」というメソッドが用意されており、このメソッドを使用することでネットワーク接続が可能です。

ServerConnectorByAPI.cs
    public class ServerConnectorByAPI
    {
        private NETRESOURCE netResource;

        [DllImport("mpr.dll", EntryPoint = "WNetAddConnection2", CharSet = CharSet.Unicode)]
        private static extern int WNetAddConnection2(ref NETRESOURCE lpNetResource, string lpPassword, string lpUsername, Int32 dwFlags);

        [DllImport("mpr.dll", EntryPoint = "WNetCancelConnection2", CharSet = CharSet.Unicode)]
        private static extern int WNetCancelConnection2(string lpName, Int32 dwFlags, bool fForce);


        /// <summary>UNCパス</summary>
        private string UncPath { get; set; }
        /// <summary>ユーザーID</summary>
        private string UserID { get; set; }
        /// <summary>パスワード</summary>
        private string Password { get; set; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="uncPath">UNCパス</param>
        /// <param name="userId">ユーザーID</param>
        /// <param name="password">パスワード</param>
        public ServerConnectorByAPI(string uncPath, string userId, string password)
        {
            UncPath = uncPath;
            UserID = userId;
            Password = password;
        }

        /// <summary>
        /// ネットワーク情報
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct NETRESOURCE
        {
            public int dwScope;
            public int dwType;
            public int dwDisplayType;
            public int dwUsage;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpLocalName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpRemoteName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpComment;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpProvider;
        }

        /// <summary>
        /// サーバーに接続する
        /// </summary>
        /// <returns>結果 (true : 成功、false : 失敗)</returns>
        public bool TryConnect()
        {
            try
            {
                netResource.dwScope = 0;                    // 列挙の範囲
                netResource.dwType = 1;                     // リソースタイプ
                netResource.dwDisplayType = 0;              // 表示オブジェクト
                netResource.dwUsage = 0;                    // リソースの使用方法
                netResource.lpLocalName = null;             // ローカルデバイス名
                netResource.lpRemoteName = UncPath;         // UNCパス
                netResource.lpProvider = null;              // プロバイダ名

                // 既に接続済みの場合がある為、一旦接続を解除する
                WNetCancelConnection2(UncPath, 0, true);

                // 接続
                int ret = WNetAddConnection2(ref netResource, Password, UserID, 8);

                // 他ユーザーアカウントで既に接続が確立されている場合 (1219) は接続OKとする
                return ret == 0 || ret == 1219;
            }
            catch (Exception)
            {
                // エラーハンドル処理 (省略)
            }
        }

        /// <summary>
        /// サーバーへの接続を解除する
        /// </summary>
        /// <returns>結果 (true : 成功、false : 失敗)</returns>
        public void TryDisConnect()
        {
            try
            {
                WNetCancelConnection2(UncPath, 0, true);
            }
            catch (Exception)
            {
                // エラーハンドル処理 (省略)
            }
        }
    }

補足

  • このメソッドでは引数で指定したログインIDとパスワードを使用していますが、接続できなかった場合は資格情報を入力する画面が表示され、ログインIDやパスワードを手入力できます。
  • もし資格情報を入力する画面でキャンセルした場合は、WNetCancelConnection2()の戻り値としてエラーコード"1223"が返却されます。

使用方法

  • NET USEを使用する方法
    // 現在ログインしているユーザーIDを使用しNET USEで接続
    var connectorByNeteUse = new ServerConnectorByNetUse(serverPath, Environment.UserName);
    bool ret = connectorByNeteUse.TryConnect();
  • WindowsAPIを使用する方法
    // 既定のID/PWを使用しWindowsAPIで接続
    // 既定のID/PWでログインできなければ入力画面を表示
    var ConnectorByAPI = new ServerConnectorByAPI(toServerPath, userID, password);
    bool ret = ConnectorByAPI.TryConnect();

終わりに

既に接続済みの場合は、NET USEやWindowsAPIともに、戻り値が0ではなく、1219で返却されます。戻り値が0以外でも挙動上は問題無い場合があるので、戻り値の知識も最低限は必要になってくるかなと感じました。サーバーへアクセスした後は、サーバー内のファイルの情報を読み取ったり、サーバー内のファイルをコピーしてきたりができると思います。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?