【c#】リモートディスクトップ接続を検知してSlackに投稿するWindowsサービスを作成する

# 概要

WindowsPCにリモートディスクトップ接続した場合に、接続者がいるとキックしてしまうのでSlackに通知して誰が接続しているのかを投稿して管理するとわかりやすいのではないかと思い作成してみました。

Windowsサービス作成自体は [チュートリアル: Windows サービス アプリを作成する](https://docs.microsoft.com/ja-jp/dotnet/framework/windows-services/walkthrough-creating-a-windows-service-application-in-the-component-designer) と同じ手順で行っています。

# 開発環境

Visual Studio 2019
.Net Framework 4.7.2

# Windowsサービスプロジェクトを作成する

・Visual Studio 2019を起動して新しいプロジェクトの作成から`Windows サービス(.NET Framwork)`を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/4023e053-101d-de64-e3fa-039439086da4.png)

・プロジェクト名と保存先を指定して作成

|項目|内容|
|:-:|:-:|
|プロジェクト名|好きな名前|
|場所|好きな保存先にしましょう|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/19a2cac2-81b5-5fab-428e-3657d24f9b17.png)

# サービス名の変更

### テンプレートのファイル名を変更

・作成すると`Service1.cs`のファイルがあるので名前を変更しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/04b0958f-6ba7-4cad-ad19-ba5024b3386e.png)

・エラーが出ない限り好きな名前でよいです今回は`Prj_RemoteDesktopWindows`にしています。
・名前を変更すると`参照をすべて変更しますか`と出るので`はい`を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/ae9bd625-e138-c3f8-8ce5-b0dce28f8bde.png)

### デザインのServiceNameを変更

・先ほど変更した`Prj_RemoteDesktopWindows.cs`ファイルを右クリックして`デザイナーの表示`を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/99171909-8a94-f786-9d34-26486a3761ca.png)

・プロパティの`ServiceName`を`Prj_RemoteDesktopWindows`に変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/98ff6f56-1f23-2f94-2bfa-50d201d499fa.png)

### サービス保留の状態を実装する

・[チュートリアル: Windows サービス アプリを作成する](https://docs.microsoft.com/ja-jp/dotnet/framework/windows-services/walkthrough-creating-a-windows-service-application-in-the-component-designer)の通りにサービス保留の状態を実装しておきます。

・`Prj_RemoteDesktopWindows.cs`のコードを開いて

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/1f052572-24b3-8676-65fd-34092716ad73.png)

<details><summary>サービス保留の状態を実装コード.cs</summary><div>

```Prj_RemoteDesktopWindows.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace Prj_RemoteDesktopWindows
{
    public partial class Prj_RemoteDesktopWindows : ServiceBase
    {
        public Prj_RemoteDesktopWindows()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            // Update the service state to Start Pending.
            ServiceStatus serviceStatus = new ServiceStatus();
            serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
            serviceStatus.dwWaitHint = 100000;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);

            // Update the service state to Running.
            serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);

        }

        protected override void OnStop()
        {
        }

        public enum ServiceState
        {
            SERVICE_STOPPED = 0x00000001,
            SERVICE_START_PENDING = 0x00000002,
            SERVICE_STOP_PENDING = 0x00000003,
            SERVICE_RUNNING = 0x00000004,
            SERVICE_CONTINUE_PENDING = 0x00000005,
            SERVICE_PAUSE_PENDING = 0x00000006,
            SERVICE_PAUSED = 0x00000007,
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ServiceStatus
        {
            public int dwServiceType;
            public ServiceState dwCurrentState;
            public int dwControlsAccepted;
            public int dwWin32ExitCode;
            public int dwServiceSpecificExitCode;
            public int dwCheckPoint;
            public int dwWaitHint;
        };

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool SetServiceStatus(System.IntPtr handle, ref ServiceStatus serviceStatus);


    }
}
```

</div></details>

### セクションが変更したときに取得する(リモート接続を検知する)

・ここからチュートリアルと少し違ってきます。


・[ServiceBase.OnSessionChange(SessionChangeDescription) メソッド](https://docs.microsoft.com/ja-jp/dotnet/framework/windows-services/walkthrough-creating-a-windows-service-application-in-the-component-designer)
・OnSessionChangeの注釈を見るとそのままでは動かないので`CanHandleSessionChangeEvent`を`有効`にします。
・[ServiceBase.CanHandleSessionChangeEvent プロパティ](https://docs.microsoft.com/ja-jp/dotnet/api/system.serviceprocess.servicebase.canhandlesessionchangeevent?view=dotnet-plat-ext-5.0)

<details><summary>リモート接続を検知実装コード.cs</summary><div>

```Prj_RemoteDesktopWindows.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace Prj_RemoteDesktopWindows
{
    public partial class Prj_RemoteDesktopWindows : ServiceBase
    {
        public Prj_RemoteDesktopWindows()
        {
            InitializeComponent();
            // OnSessionChange関数をの実行を有効にするため、CanHandleSessionChangeEventをtrueに
            CanHandleSessionChangeEvent = true;
        }

        protected override void OnStart(string[] args)
        {
            // Update the service state to Start Pending.
            ServiceStatus serviceStatus = new ServiceStatus();
            serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
            serviceStatus.dwWaitHint = 100000;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);

            // Update the service state to Running.
            serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);

        }

        protected override void OnStop()
        {
        }

        public enum ServiceState
        {
            SERVICE_STOPPED = 0x00000001,
            SERVICE_START_PENDING = 0x00000002,
            SERVICE_STOP_PENDING = 0x00000003,
            SERVICE_RUNNING = 0x00000004,
            SERVICE_CONTINUE_PENDING = 0x00000005,
            SERVICE_PAUSE_PENDING = 0x00000006,
            SERVICE_PAUSED = 0x00000007,
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ServiceStatus
        {
            public int dwServiceType;
            public ServiceState dwCurrentState;
            public int dwControlsAccepted;
            public int dwWin32ExitCode;
            public int dwServiceSpecificExitCode;
            public int dwCheckPoint;
            public int dwWaitHint;
        };

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool SetServiceStatus(System.IntPtr handle, ref ServiceStatus serviceStatus);

        /// <summary>
        /// 変更イベントがターミナル サーバー セッションから受信された場合に実行します。
        /// </summary>
        /// <param name="changeDescription"></param>
        protected override void OnSessionChange(SessionChangeDescription changeDescription)
        {
            switch (changeDescription.Reason)
            {
                case SessionChangeReason.ConsoleConnect:
                    break;
                case SessionChangeReason.ConsoleDisconnect:
                    break;
                case SessionChangeReason.RemoteConnect:
                    break;
                case SessionChangeReason.RemoteDisconnect:
                    break;
                case SessionChangeReason.SessionLogon:
                    break;
                case SessionChangeReason.SessionLogoff:
                    break;
                case SessionChangeReason.SessionLock:
                    break;
                case SessionChangeReason.SessionUnlock:
                    break;
                case SessionChangeReason.SessionRemoteControl:
                    break;
                default:
                    break;
            }
        }
    }
}
```

</div></details>

# サービスにインストーラーを追加する

### インストーラーを追加

・まずはデザイナーを表示します。(`Prj_RemoteDesktopWindows.csファイルを右クリック`して`デザイナーの表示`を選択)
・`バックグランドを右クリック`して`インストーラーの追加`を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/bd389663-a324-2bc5-423a-8b09c3b0ea69.png)

・`ProjectInstaller`コンポーネントクラスが生成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/0dde0a82-ec4e-77ec-b607-ae2f5a3b0f4e.png)

### serviceInstaller1のプロパティ設定

・デザインビューの`serviceInstaller1`を選択、プロパティのServiceNameが`Prj_RemoteDesktopWindows`になっていることを確認しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/966d3dea-ccea-1a58-0beb-075dae8f09eb.png)

・`StartType`やサービス一覧に表示される`サービス名`や`説明`を追記します。

|項目|内容|
|:-:|:-:|
|Description|説明|
|DisplayName|サービス名|
|StartType|Automatic|


### 最終的なserviceInstaller1のプロパティウィンドウ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/06cc5246-5585-17dc-475f-c0f9985c9ab5.png)

### serviceProcessInstaller1のプロパティ設定

・ローカルシステムアカウントを使用してサービスがインストール、実行するようにするため、デザインビューの`serviceProcessInstaller1`を選択してプロパティの`User`を`LocalSystem`にします。

|項目|内容|
|:-:|:-:|
|User|LocalSystem|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/83cc99b4-2961-ca7f-248e-f17e18cf53ce.png)

# リモート接続したときにSlackに投稿する

作成したプロジェクトでインストールできるように修正してSlackに投稿できるようにします。
Slackに投稿するは[【c#】Slackに投稿してスレッドに返信できるようにする。](https://qiita.com/im0039kp/items/e06fe2bba5c4bbc6d46c)を参考にして下さい。

今回はSlackSendMessage.csで追加しました。

```SlackSendMessage.cs
using System.Collections.Specialized;
using System.Net;
using System.Text;
using System.Text.Json;

namespace Prj_RemoteDesktopWindows
{
    class SlackSendMessage
    {
        private static WebClient s_webClient = new WebClient();
        public static Rootobject SendMessage(string text, string thread_ts = "")
        {
            var data = new NameValueCollection();
            data["token"] = "xoxb-";
            data["channel"] = "#個人";
            data["text"] = text;
            data["thread_ts"] = thread_ts;
            var response = s_webClient.UploadValues("https://slack.com/api/chat.postMessage", "POST", data);
            string responseInString = Encoding.UTF8.GetString(response);
            return JsonSerializer.Deserialize<Rootobject>(responseInString);
        }
    }


    public class Rootobject
    {
        public bool ok { get; set; }
        public string channel { get; set; }
        public string ts { get; set; }
        public Message message { get; set; }
    }

    public class Message
    {
        public string bot_id { get; set; }
        public string type { get; set; }
        public string text { get; set; }
        public string user { get; set; }
        public string ts { get; set; }
        public string team { get; set; }
        public Bot_Profile bot_profile { get; set; }
    }

    public class Bot_Profile
    {
        public string id { get; set; }
        public bool deleted { get; set; }
        public string name { get; set; }
        public int updated { get; set; }
        public string app_id { get; set; }
        public Icons icons { get; set; }
        public string team_id { get; set; }
    }

    public class Icons
    {
        public string image_36 { get; set; }
        public string image_48 { get; set; }
        public string image_72 { get; set; }
    }
}
```

### JsonSerializerが見つからない。

・NuGetコンソールから
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/1ed47265-9362-9cf6-a669-a90b2a286114.png)

・下記のコマンドを実行してインストールしましょう。

```
PM> Install-Package System.Text.Json
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/94020163-1b6b-d155-6140-d0ddf06d374d.png)



# リモート元の接続情報を取得する

```
System.Environment.GetEnvironmentVariable("CLIENTNAME");
```
こちらで取得できるという記事がありましたが管理者権限で実行しているアプリケーションではうまく取れなかったので
Windows Terminal Services API.にアクセスできるライブラリ[Cassia](https://www.nuget.org/packages/Cassia)こちらを使わせていただきます。

NuGetコンソールで

```
Install-Package Cassia -Version 2.0.0.60
```

SessionManagerクラスを追加して名前を取得できるようにしました。

```SessionManager.cs
using Cassia;
using System;
using System.Text;

namespace Prj_RemoteDesktopWindows
{
    class SessionManager
    {
        private static ITerminalServicesManager s_TerminalServicesManager = new TerminalServicesManager();

        public static bool IsClientName(int sessionId, out string clientName)
        {
            using (ITerminalServer ts = s_TerminalServicesManager.GetLocalServer())
            {
                ts.Open();
                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"{sessionId}");
                foreach (ITerminalServicesSession session in ts.GetSessions())
                {
                    if (sessionId == session.SessionId && session.ConnectionState == ConnectionState.Active)
                    {
                        clientName = session.ClientName;
                        return true;
                    }
                }
            }
            clientName = String.Empty;
            return false;
        }
    }
}

```

# 最終的なPrj_RemoteDesktopWindows.csのコード

・`OnStart()`時にSlackに通知してスレッド投稿するために`ts`を保持
・`RemoteDisconnect`のタイミングではclientNameが`空白`になっているのでstringで保持
・なんとなく`try-catch`

```Prj_RemoteDesktopWindows.cs
using System;
using System.Runtime.InteropServices;
using System.ServiceProcess;

namespace Prj_RemoteDesktopWindows
{
    public partial class Prj_RemoteDesktopWindows : ServiceBase
    {
        private string m_ts;
        private string m_tempClientName;

        public Prj_RemoteDesktopWindows()
        {
            InitializeComponent();
            // OnSessionChange関数をの実行を有効にするため、CanHandleSessionChangeEventをtrueに
            CanHandleSessionChangeEvent = true;
        }

        protected override void OnStart(string[] args)
        {
            // Update the service state to Start Pending.
            ServiceStatus serviceStatus = new ServiceStatus();
            serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
            serviceStatus.dwWaitHint = 100000;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);

            // Update the service state to Running.
            serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
            SetServiceStatus(this.ServiceHandle, ref serviceStatus);

            // スラックに投稿
            Rootobject rootobject = SlackSendMessage.SendMessage("リモート接続監視");
            m_ts = rootobject.message.ts;
        }

        protected override void OnStop()
        {
        }

        public enum ServiceState
        {
            SERVICE_STOPPED = 0x00000001,
            SERVICE_START_PENDING = 0x00000002,
            SERVICE_STOP_PENDING = 0x00000003,
            SERVICE_RUNNING = 0x00000004,
            SERVICE_CONTINUE_PENDING = 0x00000005,
            SERVICE_PAUSE_PENDING = 0x00000006,
            SERVICE_PAUSED = 0x00000007,
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ServiceStatus
        {
            public int dwServiceType;
            public ServiceState dwCurrentState;
            public int dwControlsAccepted;
            public int dwWin32ExitCode;
            public int dwServiceSpecificExitCode;
            public int dwCheckPoint;
            public int dwWaitHint;
        };

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool SetServiceStatus(System.IntPtr handle, ref ServiceStatus serviceStatus);

        /// <summary>
        /// 変更イベントがターミナル サーバー セッションから受信された場合に実行します。
        /// </summary>
        /// <param name="changeDescription"></param>
        protected override void OnSessionChange(SessionChangeDescription changeDescription)
        {

            try
            {
                SessionChangeReason sessionChangeReason = changeDescription.Reason;
                SessionManager.IsClientName(changeDescription.SessionId, out string clientName);
                switch (sessionChangeReason)
                {
                    case SessionChangeReason.ConsoleConnect:
                        break;
                    case SessionChangeReason.ConsoleDisconnect:
                        break;
                    case SessionChangeReason.RemoteConnect:
                        m_tempClientName = clientName;
                        SlackSendMessage.SendMessage($"リモート接続【{m_tempClientName}】", m_ts);
                        break;
                    case SessionChangeReason.RemoteDisconnect:
                        SlackSendMessage.SendMessage($"リモート切断【{m_tempClientName}】", m_ts);
                        break;
                    case SessionChangeReason.SessionLogon:
                        break;
                    case SessionChangeReason.SessionLogoff:
                        break;
                    case SessionChangeReason.SessionLock:
                        break;
                    case SessionChangeReason.SessionUnlock:
                        break;
                    case SessionChangeReason.SessionRemoteControl:
                        break;
                    default:
                        break;
                }
                

            }
            catch (Exception e)
            {
                SlackSendMessage.SendMessage($"エラーが発生しました。\n {e.Message}", m_ts);
            }


        }


    }


}

```

# ビルドする

・`Prj_RemoteDesktopWindows`プロジェクトのプロパティのアプリケーションタブからスタートアップオブジェクトを`Prj_RemoteDesktopWindows.Program`にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/dcba9352-a63a-0933-760c-8666fb949c8f.png)

・Ctrl+Shift+Bでビルドします。(または、Prj_RemoteDesktopWindowsプロジェクトを右クリックして [ビルド] を選択します)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/f0fde88a-7f7c-9841-9365-989ea5a06cfa.png)

・exeが生成されます。(Debugでビルドしているとbinの中にはDebugフォルダが生成されます。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/426a2dcf-c511-a66a-8e09-6e9ccacdefa3.png)



# サービスをインストールする

やっていることは同じですが2パターンのインストール方法を記載しておきます。
<b>前提としてインストールするためには管理者として実行する必要があります。</b>

###　方法1 Visual Studio 2019からインストール

・管理者として実行して、作成したプロジェクトを再度開きましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/c6c3e609-8874-5c17-ae0f-02f85b1d5103.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/05bb2b49-20f8-4657-d185-a99f16360980.png)

・`ツール`メニュー`コマンドライン`から`開発者コマンドプロンプト`を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/115a15b6-3964-fdf0-b430-265603a03deb.png)

・コマンド実行

```
installutil D:\Tools\Prj_RemoteDesktopWindows\bin\Debug\Prj_RemoteDesktopWindows.exe
```
exeまでのパスはとりあえずフルパスがいいと思います。

・installutil.exeがない場合は自身のPCにインストールされているか確認してフルパスを指定しましょう

>システムで installutil.exe を見付けることができない場合は、コンピューター上に存在することを確認してください。 このツールは、.NET Framework と共に %windir%\Microsoft.NET\Framework[64]\<framework version> フォルダーにインストールされます。 たとえば、64 ビット バージョンでの既定のパスは %windir%\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe です。

###　方法2 batを作り管理者として実行する

・InstallUtil.exe が必要なので自分のPCのインストール先のパスを指定したbatを作成（すべてフルパスで指定しています）

```インストール.bat
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe D:\Tools\Prj_RemoteDesktopWindows\bin\Debug\Prj_RemoteDesktopWindows.exe
pause
```

・右クリックで`管理者として実行`します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/d4d7890b-1e98-a52a-e75b-05aa28dc7bda.png)

### サービスインストール時によく見るエラー


>System.Security.SecurityException: ソースが見つかりませんでしたが、いくつかまたはすべてのログを検索できませんでした。アクセス不可能なログ: Security


管理者として実行しましょう。

>インストール段階で例外が発生しました。
System.ComponentModel.Win32Exception: 指定されたサービスは既に開始されています。

アンインストールしてからインストールしましょう。
・アンインストールコマンド

```
installutil.exe /u D:\Tools\Prj_RemoteDesktopWindows\bin\Debug\Prj_RemoteDesktopWindows.exe
```

>エラー		"obj\Debug\Prj_RemoteDesktopWindows.exe" を "bin\Debug\Prj_RemoteDesktopWindows.exe" にコピーできませんでした。10 回の再試行回数を超えたため、失敗しました。このファイルは "RemoteConnectSlackBot (1544)" によってロックされています。

・サービスが開始されています、まずはアンインストールしましょう。

# サービスの開始する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/16fd5643-86d7-e2fc-fc18-d54367ee0d68.png)

・右クリックから`開始`を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/129008cb-b976-374e-36f4-342bba272a0e.png)

# リモート接続してみる。

・確認のためにmacbookairでWindowsPCリモート接続してスレッドに返信されていることを確認しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/0bab36c7-e6cf-83db-d892-123c4f43eca9.png)

# 最後に

`Debugビルド`のままですが最終的には`Releaseビルド`にしてください。
`サービス保留の状態を実装する`など行っていますが実際に必要なのかなどわかっていない部分もあります（チュートリアルが実装していたのでとりあえず追加している程度です）
はじめてWindowsサービスを作ったのですが、実際にリモート接続しないといけなかったり管理者権限でインストールしないと行けなかったりデバッグ方法が面倒でした、また`OnStart()`でSlackに投稿をしているので何かしらの更新で再度`OnStart()`が呼ばれるかもしれないなと思いました。（確認していませんが、リモート先のPCを再起動するなど）

また、人によってはエラー内容が違っていたりするので参考にプロジェクトをGithubなどに上げられたらよかったですが、アクセストークンなどもあるのでローカルで開発しています。
見たい方がいて問題なさそうならあげてもよいのですが、とりあえず構成だけ張っておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175167/da68d50f-c2bd-e207-ba03-212acd43d88c.png)

リモートを検知してSlackに投稿するなどの需要があるのかわかりませんが、参考にしていただければと思います。

### 参考

[チュートリアル: Windows サービス アプリを作成する](https://docs.microsoft.com/ja-jp/dotnet/framework/windows-services/walkthrough-creating-a-windows-service-application-in-the-component-designer) 
[リモート接続先で接続元の情報を C# で取得する](https://tech.tanaka733.net/entry/how-to-retrieve-terminal-serssion)