ユーザーの職場に関する情報を取得します

画像







0.はじめに



それはすべて、誇らしげに言ったユーザーからの次の呼び出しで始まりました。







解決策は、熱狂するほどシンプルで、ひざまずくように計画されました。 ほとんどの従業員はWindowsで働いており、すべてのワークステーションがドメインに含まれているため、ソリューション検索ベクトルが設定されました。 最初は、小さなスクリプトを書くことが計画されていました。 彼の仕事は、システムとこのシステムで働く従業員に関する基本情報を収集することでした。 情報のセットは最小限です。 すなわち:ログイン、ワークステーションの名前とそのIP。 作業の結果はサーバーに保存され、スクリプト自体はGPOを介してユーザーに「ハング」します。







この実装では、次の形式で重大な欠点がありました。









熟考後、テレグラムでボットを使用するという決定が下されました。 少し手間をかけたため、スクリプトは、サーバー上のファイルに「退屈な」記録の場所のために、チャットに情報を送信するための小さなプログラムに書き直されました。 (+ボットに回復するパラメーターがいくつか追加されました)







画像

PS画像に表示されているデータは、企業秘密を保持するために検閲されています。







しかし、このようなアプローチは、情報の可用性に関する問題のみを解決し、古いアプローチの残りの欠点を保持しました。







何かを変える必要がありました。 本格的なクライアント/サーバーアプリケーションを作成することが決定されました。

コンセプトはシンプルです。 クライアントからの着信接続を処理し、要求された情報を彼に送信するサーバーを作成しています。







1.サーバーを作成します



まず、「通信」用のプロトコルを選択します。 選択は素晴らしいものではありません-UDP / TCP。 私はTCPを支持することにしました。 利点は明らかです。









ユーザークラスを作成することから始めましょう。







public class User { public string Name { get; set; } public string PC { get; set; } public string IP { get; set; } public string Version { get; set; } public byte[] Screen { get; set; } }
      
      





最初は、3つのプロパティしかありませんでした。 しかし、開発プロセス中に、ビューサーバーのコードが変更されました。 新しい機能がありました。 クライアントとサーバーの互換性のためにバージョンが必要になりました。 アセンブリからバージョンを取得せず、冗長であると判断しました。 また、ユーザーの画面の画面を作成することが可能になりました。







コンストラクター:







 public User(string name, string pc, string ip, string version) { this.Name = name; this.PC = pc; this.IP = ip; this.Version = version; }
      
      





スクリーンショットを常に転送する必要はありません。 したがって、コンストラクターのオーバーロードを作成します。







 public User(string name, string pc, string ip, string version, byte[] screen) { this.Name = name; this.PC = pc; this.IP = ip; this.Version = version; this.Screen = screen; }
      
      





少し先を行くと、最初はデータがBinaryWriterを介して「行ごと」に送信され、共通のデータ型にキャストされなかったと言います。 これは、アプリケーションに新しい機能を追加するときに非常に不便でした。 データ送信機能の書き換えにより、シリアル化する機能が追加されました。 これで、Userオブジェクトは3つの形式で表すことができます。









 [Serializable, DataContract] public class User { [DataMember] public string Name { get; set; } [DataMember] public string PC { get; set; } [DataMember] public string IP { get; set; } [DataMember] public string Version { get; set; } [DataMember] public byte[] Screen { get; set; } public User(string name, string pc, string ip, string version) { this.Name = name; this.PC = pc; this.IP = ip; this.Version = version; } public User(string name, string pc, string ip, string version, byte[] screen) { this.Name = name; this.PC = pc; this.IP = ip; this.Version = version; this.Screen = screen; } public byte[] GetBinary() { BinaryFormatter formatter = new BinaryFormatter(); using (MemoryStream stream = new MemoryStream()) { formatter.Serialize(stream, this); return stream.ToArray(); } } public byte[] GetXML() { XmlSerializer formatter = new XmlSerializer(typeof(User)); using (MemoryStream stream = new MemoryStream()) { formatter.Serialize(stream, this); return stream.ToArray(); } } public byte[] GetJSON() { DataContractJsonSerializer jsonFormatter = new DataContractJsonSerializer(typeof(User)); using (MemoryStream stream = new MemoryStream()) { jsonFormatter.WriteObject(stream, this); return stream.ToArray(); } } }
      
      





バイナリUserオブジェクトをデシリアライズできるようにするには、それを別のライブラリに転送し、それを通してプログラムで使用する必要がありました。







また、出力で取得するストリームにも注意を払いたいと思います。 バイトの配列は、ToArrayメソッドを介して返されます。 そのマイナス-メモリ内にストリームのコピーを作成します。 しかし、これは重要ではありません。GetBufferメソッドを使用すると、クリーンなデータ配列は返されませんが、ストリーム全体(ストリームに割り当てられたメモリが完全にいっぱいにならない可能性がある)が返されるため、結果として配列が増加します。 残念ながら、私はこのニュアンスをすぐには見ませんでした。 ただし、データの詳細な分析が必要です。







ClientObjectクラスは、接続の処理を担当します。







 public class ClientObject { public TcpClient client; [Flags] enum Commands : byte { GetInfoBin = 0x0a, GetInfoJSON = 0x0b, GetInfoXML = 0x0c, GetScreen = 0x14, GetUpdate = 0x15, GetTest = 0xff } public ClientObject(TcpClient tcpClient) { client = tcpClient; } protected void Sender(TcpClient client, byte[] data) { try { Logger.add("Sender OK 0xFF"); BinaryWriter writer = new BinaryWriter(client.GetStream()); writer.Write(data); writer.Flush(); writer.Close(); } catch (Exception e) { Logger.add(e.Message + "0xFF"); } } protected byte[] _Info () { return new User.User(Environment.UserName, Environment.MachineName, GetIp(), Settings.Version, _Screen()).GetBinary(); } protected byte[] _Info(string type) { User.User tmp = new User.User(Environment.UserName, Environment.MachineName, GetIp(), Settings.Version); switch (type) { case "bin": return tmp.GetBinary(); case "json": return tmp.GetJSON(); case "xml": return tmp.GetXML(); } return (new byte[1] { 0x00 }); } protected byte[] _Screen() { Bitmap bm = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); Graphics gr = Graphics.FromImage(bm as Image); gr.CopyFromScreen(0, 0, 0, 0, bm.Size); using (MemoryStream stream = new MemoryStream()) { bm.Save(stream, ImageFormat.Jpeg); return stream.ToArray(); } } protected byte[] _Test() { return Encoding.UTF8.GetBytes("Test send from server"); } public void CmdUpdate(Process process) { Logger.add("Command from server: Update"); try { string fileName = "Update.exe", myStringWebResource = null; WebClient myWebClient = new WebClient(); myStringWebResource = Settings.UrlUpdate + fileName; myWebClient.DownloadFile(myStringWebResource, fileName); Process.Start("Update.exe", process.Id.ToString()); } catch (Exception e) { Logger.add(e.Message); } finally { Logger.add("Command end"); } } public void _Process() { try { BinaryReader reader = new BinaryReader(this.client.GetStream()); byte cmd = reader.ReadByte(); Logger.add(cmd.ToString()); switch ((Commands)cmd) { case Commands.GetInfoBin: Sender(this.client, _Info("bin")); break; case Commands.GetInfoJSON: Sender(this.client, _Info("json")); break; case Commands.GetInfoXML: Sender(this.client, _Info("xml")); break; case Commands.GetScreen: Sender(this.client, _Screen()); break; case Commands.GetUpdate: CmdUpdate(Process.GetCurrentProcess()); break; case Commands.GetTest: Sender(this.client, _Test()); break; default: Logger.add("Incorrect server command "); break; } reader.Close(); } catch (Exception e) { Logger.add(e.Message + " 0x2F"); } finally { Logger.add("Client close connect"); this.client.Close(); MemoryManagement.FlushMemory(); } } static string GetIp() { IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName()); return host.AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString(); } }
      
      





クライアントから送信されるすべてのコマンドについて説明します。 コマンドは非常に簡単に実装されます。 クライアントは、任意のデバイス、プログラム、または処理サーバーである可能性があることが理解されました。 したがって、応答の性質は1バイトを受信することによって決定されます。







 [Flags] enum Commands : byte { GetInfoBin = 0x0a, GetInfoJSON = 0x0b, GetInfoXML = 0x0c, GetScreen = 0x14, GetUpdate = 0x15, GetTest = 0xff }
      
      





新しい機能をすばやく追加したり、ビットマスクを使用して1バイトのみで決定される複雑な動作ロジックを構築したりできます。 便宜上、すべてのバイトは読み取り可能なコマンドに割り当てられます。







Senderメソッドはデータを送信し、TcpClientオブジェクトとデータのセットをバイトの配列として受け取ります。







 protected void Sender(TcpClient client, byte[] data) { try { Logger.add("Sender OK"); BinaryWriter writer = new BinaryWriter(client.GetStream()); writer.Write(data); writer.Flush(); writer.Close(); } catch (Exception e) { Logger.add(e.Message); } }
      
      





すべてが非常に抑制されています。 TcpClientからのストリームからBinaryWriterを作成し、それにバイトの配列を書き込み、それを消去して閉じます。







Userオブジェクトは、オーバーロードを持つ._Infoメソッドの作成を担当します







 protected byte[] _Info () { return new User.User(Environment.UserName, Environment.MachineName, GetIp(), Settings.Version, _Screen()).GetBinary(); } protected byte[] _Info(string type) { User.User tmp = new User.User(Environment.UserName, Environment.MachineName, GetIp(), Settings.Version); switch (type) { case "bin": return tmp.GetBinary(); case "json": return tmp.GetJSON(); case "xml": return tmp.GetXML(); } return (new byte[1] { 0x00 }); }
      
      





Userの新しいインスタンスを初期化し、コンストラクターに入力し、すぐに.GetBinaryメソッドを呼び出してシリアル化されたデータを取得します。 どのタイプのデータを受け取りたいかを明示的に示したい場合、オーバーロードが必要です。







._Screenメソッドは、デスクトップのスクリーンショットを作成します。







面白いの。 ここで、CmdUpdateメソッドを強調表示できます。 彼は入り口に連れて行く

現在のプロセス:







 CmdUpdate(Process.GetCurrentProcess());
      
      





このメソッドは、クライアントのコマンドでサーバーの更新を実装します。 その中に、WebClientオブジェクトが作成されます。これは、アシスタントプログラムによってダウンロードされます サーバー/サイト サーバー自体の更新に必要な指定されたソース。 次に、それを起動し、現在のプロセスのIDを入力パラメーターとして渡します。







 string fileName = "Update.exe", myStringWebResource = null; WebClient myWebClient = new WebClient(); myStringWebResource = Settings.UrlUpdate + fileName; myWebClient.DownloadFile(myStringWebResource, fileName); Process.Start("Update.exe", process.Id.ToString());
      
      





ハンドラーのエントリポイントは._Processです。 BinaryReaderを作成し、そこからコマンドバイトを読み取ります。 受信したバイトに応じて、1つまたは別の操作が実行されます。 最後に、クライアントの作業を完了し、記憶を空にします。







obj TcpClientを取得するには、.AcceptTcpClientを使用して永久ループでTcpListenerを使用します。 受信したクライアントオブジェクトをハンドラーに渡します。 メインスレッドのブロックを回避するために、新しいスレッドで起動する







 static TcpListener listener; try { listener = new TcpListener(IPAddress.Parse("127.0.0.1"), Settings.Port); listener.Start(); Logger.add("Listener start"); while (true) { TcpClient client = listener.AcceptTcpClient(); ClientObject clientObject = new ClientObject(client); Task clientTask = new Task(clientObject._Process); clientTask.Start(); MemoryManagement.FlushMemory(); } } catch (Exception ex) { Logger.add(ex.Message); } finally { Logger.add("End listener"); if (listener != null) { listener.Stop(); Logger.add("Listener STOP"); } }
      
      





サーバーには、ロガーと設定のヘルパークラスもいくつかあります。







 static public class Settings { static public string Version { get; set; } static public string Key { set; get; } static public string UrlUpdate { get; set; } static public int Port { get; set; } static public bool Log { get; set; } static public void Init(string version, string key, string urlUpdate, int port, bool log) { Version = version; Key = key; UrlUpdate = urlUpdate; Port = port; Log = log; } }
      
      





将来的には、設定を保存してファイルから読み取る予定です。







Loggerクラスを使用すると、プログラムの実行中に発生したイベントをファイルに保存できます。 設定を介してロギングを無効にすることができます。







 static class Logger { static Stack<string> log_massiv = new Stack<string>(); static string logFile = "log.txt"; static public void add(string str) { log_massiv.Push(time() + " - " + str); write(log_massiv, logFile, Settings.Log); } private static void write(Stack<string> strs, string file, bool log) { if (log) { File.AppendAllLines(file, strs); log_massiv.Clear(); } } private static string time() { return DateTime.Now.Day + "." + DateTime.Now.Month + "." + DateTime.Now.Year + " " + DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second; } }
      
      





2.クライアント



なりますが、少し後で。







GitHubのプロジェクトソース








All Articles