スクリーンセーバ抑止PowerShell

## やりたい事
- スクリーンセーバうざい

## 制約
- Windows10
- 外部ソフト持ち込まない

## できた
- PowerShell
    - 多分PowerShell2=Win7以降使えると思う
    - 外見はbat
    - 内部はC#
- マウスぷるぷる

## 先人の知恵 - Qiita
- 作ってから記事見つけたけど多分２番の記事なら同等の制約のとき使える

1. [スクリーンセーバの起動を抑制する - mpgバージョン](https://qiita.com/tlab/items/90b6fbb4c75ec1bdceda)
2. [スクリーンセーバの起動を抑制する - vbsバージョン](https://qiita.com/tlab/items/91aea996f68667b0a0ee)
3. [スクリーンセーバの起動を抑制する - Arduino Pro Micro(の互換機)を使用](https://qiita.com/tlab/items/c83981ab5a13b858a7da)
3. [マウスをぷるぷる動かしてスクリーンセーバを抑制するアプリ](https://qiita.com/sugarless_morris/items/90ca200305d1fea656a4)
4. [やむを得ない事情でスクリーンセーバーが解除できない時に出させないようにするには？](https://qiita.com/DogFortune/items/c1eba59c418f46aabfc9)
5. [３分ごとにマウスを動かす](https://qiita.com/dsjnjobm/items/fe4bcffb8415e503c865)

## 実装

```c#:PreventScreenSaver.bat
@powershell -NoProfile -ExecutionPolicy Unrestricted "&([ScriptBlock]::Create((cat -encoding utf8 \"%~f0\" | ? {$_.ReadCount -gt 2}) -join \"`n\"))" %*
@exit /b

Param($minutes = 240)

$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
if (! $myWindowsPrincipal.IsInRole($adminRole))
{
  $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
  $newProcess.Arguments = $myInvocation.MyCommand.Definition;
  $newProcess.Verb = "runas";
  [System.Diagnostics.Process]::Start($newProcess); # 
}

$cs = @"
using System;
using System.Runtime.InteropServices;

public class Win32SendInput {
 [DllImport("user32.dll")]
 static extern uint SendInput(
  uint nInputs,
  ref INPUT pInputs,
  int cbSize
 );

 [StructLayout(LayoutKind.Sequential)]
 struct INPUT
 {
  public int type;
  public MOUSEINPUT mi;
 }

 [StructLayout(LayoutKind.Sequential)]
 struct MOUSEINPUT
 {
  public int dx;
  public int dy;
  public uint mouseData;
  public uint dwFlags;
  public uint time;
  public IntPtr dwExtraInfo;
 }

 public static void PreventScreenSaver()
 {
  INPUT input = new INPUT();
  input.type = 0; // INPUT_MOUSE;
  input.mi = new MOUSEINPUT();

  input.mi.dwExtraInfo = IntPtr.Zero;
  input.mi.dx = 0;
  input.mi.dy = 0;
  input.mi.time = 0;
  input.mi.mouseData = 0;
  input.mi.dwFlags = 0x0001; // MOVE (RELATIVE)
  int cbSize = Marshal.SizeOf(typeof(INPUT));
  uint r = SendInput(1, ref input, cbSize);
 }
}
"@
Add-Type -Language CSharp -TypeDefinition $cs

for ($i = 0; $i -lt $minutes; $i++) {
  Write-Host $i
  Start-Sleep -Seconds 60
  [Win32SendInput]::PreventScreenSaver() # 
}
```

## 解説
### DOSバッチ から PowerShell 実行
- 頭２行はDOSバッチです。１行目でPowerShell起動、２行目で終了
- PowerShell起動のパラメータでこのファイル自身を読み込み先頭２行を無視して解釈してます

```sh:line1-2
@powershell -NoProfile -ExecutionPolicy Unrestricted "&([ScriptBlock]::Create((cat -encoding utf8 \"%~f0\" | ? {$_.ReadCount -gt 2}) -join \"`n\"))" %*
@exit /b
```
### PowerShell 本体
- 上記解説の通り3行目以降はPowerShellです
- デフォルト240分うごかすという設定、パラメータ受け取りで変更できるようにしときます

```powershel:line4
Param($minutes = 240)
```

### 管理者として自身を実行
- [参考：A self elevating PowerShell script - Microsoft Blog](https://docs.microsoft.com/en-us/archive/blogs/virtual_pc_guy/a-self-elevating-powershell-script)

```powershell:line6-15
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
if (! $myWindowsPrincipal.IsInRole($adminRole))
{
  $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
  $newProcess.Arguments = $myInvocation.MyCommand.Definition;
  $newProcess.Verb = "runas";
  [System.Diagnostics.Process]::Start($newProcess); # 
}
```

### CS 埋め込み
- PowerShellではC#が埋め込めます
- ここではヒアドキュメントつかって変数にぶち込んでます
- 解説できませんがマウス動かすコードです、拾い物を継ぎ接ぎしました
- 座標0動かしてるので実際は動いてません

```C#:line17-63
$cs = @"
using System;
// コード省略
"@
```

### CS コンパイル
- この一行でC#をコンパイルしてスクリプトで使えるになっちゃいます

```powershell:line64
Add-Type -Language CSharp -TypeDefinition $cs
```

### CS 実行 ループ
- 240回のループ中で
- 1分のスリープと
- C#で作ったマウスぷるぷると
- コンソール出力（動作していることを確認用）

```powershell:line66-70
for ($i = 0; $i -lt $minutes; $i++) {
  Start-Sleep -Seconds 60
  [Win32SendInput]::PreventScreenSaver() # 
  Write-Host $i
}
```

## 敢えて対処しない
- キーワード「リモートデスクトップ アイドルタイマー 2分後に切断」

## おまけ
- とある環境の人には役立つと思うなにか
 - コマンドプロンプトが起動できない環境
 - メモ帳が起動できない環境
 - ブラウザはアドレスバーが表示できない環境
 - ブラウザF12も無効な環境
- およそプログラム的なことが出来なさそうな環境で、定期的に無操作でないようにしたい場合

```javascript:ブラウザ→右クリック→要素の検査→コンソール
var s=new ActiveXObject("WScript.Shell");var f=function(){s.sendkeys("^{ESC}");};setInterval("f()",300000);
```


以上