Teams退席中対策 イマイルヨ

![Teams退席中対策 イマイルヨ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/3cb0a898-0642-bfc0-dd0f-388594e899dd.png)

# ➊はじめに
在宅勤務などで調べごとをしているときなど、定期的にキーボードやマウス操作をしないと、**Teamsアプリ**が**退席中表示**となってしまい`イマイナイ`ことになってしまうことが多々あります。
<img width="700" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/73e895a1-d4ec-162d-e109-8239ae4a0cc1.png">


# ➋ Teamsで退席中となるのはどんなとき？
Teamsで退席中となるのは概ね以下の場合です。ただし、この挙動はTeamsのバージョンや組織のポリシーによって異なる可能性があるとのことです。

- **手動でステータスを変更した場合**
> ユーザーが手動でステータスを「退席中」に変更することもできます。これは、ユーザーが一時的に利用できない状態であることを示すために使用されます。まぁこれは良いですね。

- **キーボードやマウスの操作が一定時間ない（アイドル状態）**
> ユーザーが一定時間、キーボードやマウスを操作しない場合、自動的にステータスが「退席中」に変更されることがあります。これは、ユーザーがデスクにいないことを示すための機能です。

- **デスクトップ画面がロックされた場合**
> 特定の環境や組織において、デスクトップ画面がロックされると、Teamsのステータスが「退席中」に変更される場合があります。これは、ユーザーがデバイスから離れていることを示すための自動的な挙動です。


# ➌ Teamsで退席中防止方法
Teamsで退席中とならないための防止策としては、以下で対処できそうです。
- **1人で会議を開き参加する**
- **マウスを動かしたりキーボードを操作して、アクティブな状態を維持する**
などなど

# ➍ お悩み
> とは言え、会議ばかりやっていると怪しまれるし、自動でマウスを動かしたりキーボードを操作するアプリはないものか？

皆さん、一度はこういうことを考え、一度は「`Mouse Jiggler`」や「`Auto Clicker`」など検索したことがあるでしょう...でも、会社貸与のPCだと、アプリのインストールやダウンロードもできないし、お金もかけたくないし...
　　　︙
　　う～ん
　　　︙
ならば、作ってしまえ！
ということで、**イマイルヨ**の悩みを**PowerShell**、または、**Windows Script Host** で吹っ飛ばしたいと思います😉

# ➎ イマイルヨ （PowerShell）
PowerShellでプログラムを実行可能かどうかの確認をしてから、プログラムを作成しましょう。PowerShellでのプログラムが実行不可能だった場合は、「[➏ Windows Script Host（for VBScript）](https://qiita.com/PoodleMaster/items/4c2ad1501e615b164f7f#-%E3%82%A4%E3%83%9E%E3%82%A4%E3%83%AB%E3%83%A8windows-script-host)」をお試しください。

## (１) プログラムが実行可能か確認
まずは、PowerShellでプログラムを実行できるか、**実行ポリシーについて確認しましょう**。
👉️PowerShellを**通常権限**で開いて、「`Get-ExecutionPolicy`」を実行してください。
```powershell:PowerShell
> Get-ExecutionPolicy
Restricted
```
**PowerShell の実行ポリシーは、以下のセキュリティ設定を管理するために使用されます。**
|実行ポリシー種別|説明|
|:-|:-|
|Restricted（制限付き）| すべてのスクリプトが実行不可（デフォルト設定）|
|RemoteSigned|ローカルスクリプトは制限なく実行でき、外部からダウンロードしたスクリプトには署名が必要|
|AllSigned|信頼された発行元から署名されたスクリプトのみが実行可能|
|Unrestricted|すべてのスクリプトが実行可能ですが、外部からダウンロードしたスクリプトには警告が表示|

「`Get-ExecutionPolicy`」の結果が、「`Restricted`」である場合は、作ったPowerShellのプログラムが動作しませんので、設定を変更します。

## (２) プログラムを実行可能に設定する
PowerShell の実行ポリシーが、「`Restricted`」となっていた場合は、以下のコマンドにて実行ポリシーの変更が必要となります。

👉️PowerShellを**管理者として実行**してアプリを開き、「`Set-ExecutionPolicy RemoteSigned`」を実行してください。

::: note warn 
こちらの設定変更を行う場合は、**管理者権限**が必要となります。
:::

```powershell:PowerShell
> Set-ExecutionPolicy RemoteSigned
```

## (３) プログラムを作成
（１）、(２)でPowerShellの起動準備ができたら、ソースコードを打ち込みましょう（コピペ推奨です）。ファイルは、「**イマイルヨ.ps1**」として保存してください。

:::note info
こちらは、10秒毎に「`Windowsキー`」を押下したことにし、退席中を防止する**イマイルヨ**のプログラムです。
:::

### ❖ imairuyo.ps1
```powershell:PowerShell ： imairuyo.ps1
Add-Type -AssemblyName System.Windows.Forms

[Console]::Write("Running...")

while ($true) {
    [System.Windows.Forms.SendKeys]::SendWait('^{ESC}')
    Start-Sleep -Seconds 10
}
```

※**イマイルヨ**は、**PowerShell Ver5.1**（Windowsにインストール済みのバージョンです）で動作確認済みです。PowerShellのバージョンは、「Get-Host」コマンドで確認できます。

## (４) 起動方法を工夫してみる
「`Set-ExecutionPolicy`」を実施できない場合でも…まだ諦めないで🤐

「`ps1ファイル`」を起動するような「`batファイル`」を作成するとPowerShell実行ポリシーが「`Restricted（制限付き）`」でも、一時的に制限を「`RemoteSigned`」へ昇格することで、実行できる場合があります。この方法も試してみてください。

### ❖ imairuyo.bat
```bat:バッチファイル ： imairuyo.bat
powershell -ExecutionPolicy RemoteSigned -File imairuyo.ps1
```
👉️この場合の起動方法は、「`imairuyo.bat`」をダブルクリックで問題ありません。

## (５) PowerShell 実行結果
「**imairuyo.ps1**」ファイルを右クリックして、「PowerShellで実行」を選択してください。

:::note warn
通常「.ps1ファイル」をダブルクリックすると、PowerShellのエディタが開いてしまうことがありますので、必ず**右クリ**からPowerShellを実行してくださいね。
:::
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/34dc0012-5281-2f02-90de-a13318ef5cb0.png">

PowerShellスクリプトが起動し、10秒ごとに「`Windowsキー`」が押され、Windowsの「`スタートメニュー`」がパタパタ動き始めます（ON⇔OFFを繰り返します）。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/ba327054-8c63-9bf4-8682-eef7a70c203b.png)

## (６) PowerShell カスタマイズの仕方
### (６-１) 繰り返し間隔変更
勘の良い方ならもう分かっていると思いますが、「Start-Sleep -Seconds 」の引数の '10' は10秒間隔という意味です。例えば60秒間隔で「`スタートメニュー`」をパタパタさせたい場合は、以下のように値を '60' に修正してください。
```powershell:PowerShell
Start-Sleep -Seconds 60
```

### (６-２) 押下ボタン変更
「`スタートメニュー`」のパタパタはウザくてイヤだよ。「`シフトキー`」押下でイイよっていう場合は、以下のようにSendKeysの値を '+' に修正してください。
```powershell:PowerShell
[System.Windows.Forms.SendKeys]::SendWait('+')
```
どのキーにするかは、[👉こちらの資料](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.sendkeys?view=windowsdesktop-7.0)を参照してください。


### (６-３) イマイルヨ改
上記2つを修正したものがこちらになります。

:::note info
こちらは、60秒毎に「`シフトキー`」を押下したことにし、退席中を防止する**イマイルヨ**のプログラムです。
:::

### ❖ imairuyo_ShiftPush.ps1
```powershell:PowerShell ： imairuyo_ShiftPush.ps1
Add-Type -AssemblyName System.Windows.Forms

[Console]::Write("Running...")

while ($true) {
    [System.Windows.Forms.SendKeys]::SendWait('+')
    Start-Sleep -Seconds 60
}
```

### (６-４) 時刻表示を行うカスタマイズ例
「PowerShellが起動しているから**イマイルヨ**が起動しているんだろうけど、プログラムが正常に動作しているのか分かるようにしてくんない？」っちゅー熱いご要望にもお答えして、以下のようにカスタマイズしてみました。1秒毎に日時が更新されるので、プログラムが正常に動作していることが分かります。

:::note info
こちらは、1秒毎に「シフトキー」を押下したことにし、退席中を防止する**イマイルヨ**のプログラムです。日時が更新されるので、動作していることが分かりやすいです。
:::

### ❖ imairuyo_time.ps1
```powershell:PowerShell ： imairuyo_time.ps1
Add-Type -AssemblyName System.Windows.Forms

$prefix = "Running... :"
$textLength = [System.Text.Encoding]::UTF8.GetByteCount($prefix)

[Console]::Clear()
[Console]::ForegroundColor = [ConsoleColor]::Yellow
[Console]::Write($prefix)

while ($true) {
    [System.Windows.Forms.SendKeys]::SendWait('+')
    $datetime = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
    [Console]::ForegroundColor = [ConsoleColor]::White
    [Console]::Write($datetime)
    Start-Sleep -Seconds 1
    [System.Console]::SetCursorPosition($textLength, 0)
}
```
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/7e0ac471-478a-840f-0cf6-d35045a4aa2f.png)

# ➏ イマイルヨ（Windows Script Host）

**Windows Script Host**でも同様のことができるので、こちらもプログラムを記載しておきます。**PowerShell**でうまくいかない人は、こちらをお試しください😊
## (１) イマイルヨのプログラムを考える
イマイルヨのプログラムを超カンタンに考えてみます。
まず、ウインドウキーを程よく連打し続けるプログラムを考えました。

### ❖ imairuyo_simple_start.vbs
```vb:Windows Script Host ： imairuyo_simple_start.vbs
Set objShell = CreateObject("WScript.Shell")

Do
    objShell.SendKeys "^{ESC}"
    WScript.Sleep 10000 ' 10 seconds
Loop
```

次に、WSHは「WScript.exe」で動作するので、このプロセスを終了させるプログラムを考えました。

### ❖ imairuyo_simple_stop.vbs
```vb:Windows Script Host ： imairuyo_simple_stop.vbs
' wscript.exe プロセスを終了する
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Name = 'wscript.exe'")
For Each objProcess in colProcesses
    objProcess.Terminate
Next
```

::: note warn
大体これでも問題ありませんが、他のツールなどで「WScript.exe」を利用している場合（競合）を考え、起動したプロセスのみを正しく終了させること、また、本ツールは1個起動すれば十分なので、重複起動させない機能を盛り込みたいと思います。
:::

## (２) イマイルヨのプログラム

### ❖ imairuyo_start.vbs
**imairuyo_simple_start.vbs** に機能追加したものが以下 **imairuyo_start.vbs** です。
ロックファイル（`imairuyo_lock.lck`）を用いることで重複起動させない機能を追加し、起動した **imairuyo_start.vbs** のプロセスのみ、**imairuyo_stop.vbs** で停止させるように「**imairuyo_start.vbs**のプロセスID」をログファイル（`imairuyo_start.log`）に保存するように機能追加したものになります。

```vb:Windows Script Host ： imairuyo_start.vbs
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set objShell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")

' ログファイルのパス
Dim logFilePath
logFilePath = fso.GetSpecialFolder(2) & "\imairuyo_start.log"

' ロックファイルのパス
Dim lockFilePath
lockFilePath = fso.GetSpecialFolder(2) & "\imairuyo_lock.lck"

' ロックファイルが存在する場合、すでに実行中とみなして終了
If fso.FileExists(lockFilePath) Then
    MsgBox "Another instance is already running.", vbExclamation, "Error"
    WScript.Quit
End If

' ロックファイルを作成して、他のインスタンスからの起動を防止
Set lockFile = fso.CreateTextFile(lockFilePath, True)
lockFile.Close

' 自身のプロセスIDを取得
Dim processID
processID = GetCurrentProcessID()

' 起動時にPIDをメッセージボックスに表示
MsgBox "imairuyo_start: PID = " & processID, vbInformation, "Process ID"

' ログファイルにプロセスIDを記録
Dim logFile
Set logFile = fso.CreateTextFile(logFilePath, True)
logFile.WriteLine "imairuyo_start: PID = " & processID
logFile.Close

' メインループ
Do
    objShell.SendKeys "^{ESC}"
    WScript.Sleep 10000 ' 10秒
Loop

' 現在のプロセスIDを取得する関数
Function GetCurrentProcessID()
    Dim colProcesses, objProcess
    Set colProcesses = objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Name = 'wscript.exe'")
    For Each objProcess In colProcesses
        ' 自身のスクリプト名で絞り込む
        If InStr(LCase(objProcess.CommandLine), LCase(WScript.ScriptFullName)) > 0 Then
            GetCurrentProcessID = objProcess.ProcessId
            Exit Function
        End If
    Next
End Function
```

### ❖ imairuyo_stop.vbs
**imairuyo_simple_stop.vbs** に機能追加したものが以下 **imairuyo_stop.vbs** です。
ログファイル（`imairuyo_start.log`）から「起動した **imairuyo_start.vbs** のプロセスID情報」を取得し、**imairuyo_stop.vbs** で対象プロセスIDのみ停止するようにしています。また、最後にロックファイル（`imairuyo_lock.lck`）を削除しています。

```vb:Windows Script Host ： imairuyo_stop.vbs
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set fso = CreateObject("Scripting.FileSystemObject")
Dim logFilePath, lockFilePath, targetProcessID

' ログファイルとロックファイルのパス
logFilePath = fso.GetSpecialFolder(2) & "\imairuyo_start.log"
lockFilePath = fso.GetSpecialFolder(2) & "\imairuyo_lock.lck"

' ログファイルからPIDを取得
If fso.FileExists(logFilePath) Then
    Dim logFile, line, tempPID
    Set logFile = fso.OpenTextFile(logFilePath, 1)
    line = Trim(logFile.ReadLine) ' 余分な空白・改行を削除
    logFile.Close

    ' PIDを抽出
    If InStr(line, "PID = ") > 0 Then
        tempPID = Trim(Split(line, "PID = ")(1)) ' PID部分を取り出す
        If IsNumeric(tempPID) Then
            targetProcessID = CLng(tempPID) ' 数値に変換
            MsgBox "Extracted PID: " & targetProcessID, vbInformation, "PID Retrieved"
        Else
            MsgBox "PID is not numeric: " & tempPID, vbCritical, "Error"
            WScript.Quit
        End If
    Else
        MsgBox "Log file format invalid.", vbCritical, "Error"
        WScript.Quit
    End If
Else
    MsgBox "Log file not found.", vbCritical, "Error"
    WScript.Quit
End If

' WMIを使って該当プロセスを終了
On Error Resume Next
Dim colProcesses, objProcess
Set colProcesses = objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE ProcessId = " & targetProcessID)

Dim processFound
processFound = False

For Each objProcess In colProcesses
    objProcess.Terminate
    processFound = True
    MsgBox "Terminated process with PID: " & targetProcessID, vbInformation, "Success"
Next

If Not processFound Then
    MsgBox "Process with PID " & targetProcessID & " not found.", vbExclamation, "Not Found"
End If
On Error GoTo 0

' プロセス終了待機
WScript.Sleep 2000 ' 2秒待機して完全終了を確認

' ロックファイルを削除
If fso.FileExists(lockFilePath) Then
    fso.DeleteFile(lockFilePath)
    MsgBox "Lock file deleted.", vbInformation, "Clean Up"
End If
```
## (２) イマイルヨの開始のさせ方
「`imairuyo_start.vbsファイル`」を右クリックしてメニューを出し、「`プログラムから開く`」➔「`Microsoft Windows Based Script Host`」を選択してください。
これで**イマイルヨ**が、起動できます。ウィンドウなどはできませんが、プログラムは常駐し動作します。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/928ff279-edc5-06f8-9247-0eb763173fc5.png">

* 本プログラムは、メニューが10秒毎にパタパタとON⇔OFFを繰り返すようになります（10秒ごとにWindowsキーを押下している状態）

## (３) イマイルヨの終了のさせ方
「`imairuyo_stop.vbsファイル`」を右クリックしてメニューを出し、「`プログラムから開く`」➔「`Microsoft Windows Based Script Host`」を選択してください。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/12887868-554b-80ec-19fe-aedc6222366a.png">

## (４) イマイルヨの手動終了方法
基本的にイマイルヨの終了は、「`imairuyo_stop.vbs`」で問題ありません。
何か問題があった場合に備えて、手動終了手順を記載しておきます。

### ❖ 「プロセスID」の確認方法
コマンドプロンプトから以下のコマンドを実行してください。
```sh:コマンドプロンプト
> cd %TEMP%
> type imairuyo_start.log
imairuyo_start: 34408
```

上記実行例の場合、「`34408`」が、起動したイマイルヨの「プロセスID」となります。

### ❖ タスクマネージャーからの終了方法

**イマイルヨ**のプロセスIDを確認後、「`タスクバー右クリック`」➔「`タスクマネージャー`」を起動し、タスクマネージャーの「`詳細`」を押下し、検索バーに「`script`」等を入力してください。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/ba4fdc52-a0b3-77e3-dd8d-4d747597ff0e.png)
先ほど探したプロセスID（`imairuyo_start: 34408`）と同じプロセスIDがあるので、これが**イマイルヨ**のプロセスになりますので、これを右クリックし「`タスクの終了`」を選択し、プロセスを終了させます。

### ❖ ロックファイル手動削除方法

コマンドプロンプトからのロックファイルの確認方法、及び、手動削除は方法は、以下の通りです。
```sh:コマンドプロンプト
> cd %TEMP%
> dir imairuyo_lock.lck
> DEL imairuyo_lock.lck
```
> ※「`%TEMP%`」とは、「`C:\Users\Username\AppData\Local\Temp`」のことです。
> ※ PowerShellの場合、該当ディレクトリへの移動は「`cd $env:TEMP`」としてください。

::: note warn
タスクマネージャーから「`タスクの終了`」を選択した場合は、ロックファイル（`imairuyo_lock.lck`）が残るため、「`imairuyo_start.vbs`」の新規起動ができなくなってしまいます。そのため、必ずロックファイルの削除をお願いいたします。ロックファイルは手動で削除する他、「`imairuyo_stop.vbs`」または「`imairuyo_recover.vbs`」を実行しても、ロックファイルの削除が可能です。
:::


## (５) トラブルシューティング
何か問題が発生したときは、「`imairuyo_recover.vbs`」を実行してください。実行の仕方は、アイコンを右クリックして「`PowerShell で実行`」を選択し起動してください。
実行すると以下3つの処理を実行します。
1. WScript.exe プロセスをすべて終了（「`imairuyo_recover.vbs`」自身のプロセスは除外）
1. ロックファイル（`imairuyo_lock.lck`）を削除
1. 「`imairuyo_recover.vbs`」自身のプロセスを終了

### ❖ imairuyo_recover.vbs
```vb:imairuyo_recover.vbs
' wscript.exe プロセスを終了する 
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set fso = CreateObject("Scripting.FileSystemObject")

' ロックファイルのパス
Dim lockFilePath
lockFilePath = fso.GetSpecialFolder(2) & "\imairuyo_lock.lck"

' WScript.exe プロセスをすべて終了（自身のプロセスは除外）
MsgBox "(1) Terminated process start", vbInformation, "Check point"
Set colProcesses = objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Name = 'wscript.exe'")
For Each objProcess In colProcesses
    ' 自身のプロセスを除外
    If objProcess.ProcessId <> GetCurrentProcessID() Then
        objProcess.Terminate
        MsgBox "Terminated process with PID: " & objProcess.ProcessId, vbInformation, "Process Terminated"
    End If
Next

' プロセス終了待機
WScript.Sleep 2000 ' 2秒待機して完全終了を確認
MsgBox "(1) Terminated process end", vbInformation, "Check point"

' ロックファイルを削除
MsgBox "(2) Lock file deleted process start", vbInformation, "Check point"
If fso.FileExists(lockFilePath) Then
    fso.DeleteFile(lockFilePath)
    MsgBox "(2) Lock file deleted.", vbInformation, "Lock File Deleted"
Else
    MsgBox "(2) Lock file not found.", vbExclamation, "Lock File Missing"
End If

' 最後に自身のプロセスを終了
MsgBox "Recovery completed. Exiting the script.", vbInformation, "Recovery Complete"

' 自身のプロセスを終了
WScript.Quit


' 現在のプロセスIDを取得する関数
Function GetCurrentProcessID()
    Dim colProcesses, objProcess
    Set colProcesses = objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Name = 'wscript.exe'")
    For Each objProcess In colProcesses
        ' 自身のスクリプト名で絞り込む
        If InStr(LCase(objProcess.CommandLine), LCase(WScript.ScriptFullName)) > 0 Then
            GetCurrentProcessID = objProcess.ProcessId
            Exit Function
        End If
    Next
End Function
```

# ➐ プログラムのダウンロード
プログラムは、Githubで公開しています。
こちらからダウンロードしてください。

https://github.com/PoodleMaster/Imairuyo

# ➑ 終わりに
Teamsの退席中表示問題に悩んでいる方は多いと思います。悩んだ結果、PowerShellとWSH(VBS)の勉強もできました。**イマイルヨを貴方なりのカスタマイズで、貴方なりのTeamsライフをお過ごしください**。他にもこんなやり方ありますよとかあれば、コメント欄で教えてくださいね。

それでは、お疲れさまでした😊