Windows10の開発環境を再構築したので、パッケージ管理やらPowerShellやら開発ツールの導入やらやらをまとめてみた

[ACCESS Advent Calendar 2020](https://qiita.com/advent-calendar/2020/access) の10日目の記事です。
@aTomoyaKubo が担当します。公開はたぶん10日に間に合っていません。結果は記事の一番下に記載しました。

普段開発でWindows10を利用しています。過去はLinuxやMacOS Xなども利用していました。
過去に「[あるソフトウェアエンジニアのWindows10との付き合い方](https://qiita.com/aTomoyaKubo/items/1e0b2145066eee4394de)」という記事を公開しました。
公開から月日が流れ、開発PCも買い替え、開発環境を再構築しました。
再構築の内容をまとめたいと思います。

過去の記事にも細かな環境設定や便利なショートカットなどをまとめています。

# 想定する読者

- 普段Windows10を使っていて他の人の環境が気になる人
- Windows10に移行しようとして参考にしたい人
- 他のOSを使っていてこの記事を見かけた人
- PowerShellの扱いが気になる人

# 利用環境

- Windows 10(バージョン: 1909, OSビルド: 18363.1256)  
※まだ2004/20H2が降ってきません...
- Let's Note QV9(メモリ: 16GB, ストレージ: 1TB)

# 環境構築のポリシー

クリーンな環境を保ちたいのでOSが標準機能をできるだけ利用し、導入するアプリはできるだけ抑えたい派です。

# パッケージマネージャ

ソフトウェアを管理するうえで、パッケージマネージャは欠かせません。
手動でダウンロードしてくるのも、アップデートするのも面倒なので手間がかからないようにしたいです。

パッケージマネージャはWindows向けも複数あります。
Microsoft Store, [Chocolatey](https://chocolatey.org/), [Scoop](https://scoop.sh/)及び[Ninite](https://ninite.com/), [AppGet](https://appget.net/), [Npackd](https://www.npackd.org/)など多数存在します。
2020年5月に [winget](https://github.com/microsoft/winget-cli) の開発も開始され話題となりました。
環境の再構築が5月ごろでwingetは出始めだったので導入は見送りました。

パッケージマネージャの構成をまずはまとめたいと思います。

## 利用するパッケージマネージャ

今回環境構築で採用したパッケージマネージャは役割ごとに下記の3つです。

- Microsoft Store
- Chocolatey
- Scoop

### Microsoft Store

Microsoft StoreはWindows10で標準に導入されているストアです。
ストアに存在するソフトウェアはこちらから導入します。
Chocolateyに寄せてしまってもよかったのですが、標準ストアに寄せたいという気持ちが強く利用しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103216/427856b4-cf32-5b1d-b0bd-a8953f6f0ba9.png)

### Chocolatey

Chocolateyはパッケージの種類が豊富で更新も早いので継続して利用することとしました。
Chocolatey GUIから利用します。
Microsoft Storeに公開されていないものを管理するのに利用します。

しかしコマンドライン関連のツール導入には難があり、パスがうまく通らないといったことがありました。
また管理者権限も必要で、gitやvimなどのツールを入れるだけなのに必要になるのも負担に感じていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103216/ef0be344-4cd5-ead4-269b-5508009d21b4.png)

### Scoop

コンソール向けツールのパッケージマネージャとしてScoopを検討し、
パッケージのインストールに管理者権限が必要なくパスの管理も問題なく動作したので導入を決定しました。
vimやgit, Anacondaなどのツールの導入にはこちらを利用します。
パスをリセットするコマンドもあり、パスが壊れてしまっても修復することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103216/4adc2e5f-7027-f410-cd0a-9eeefa13eb77.png)

## パッケージマネージャを利用しないソフトウェア

パッケージマネージャを利用していて悩ましいのが、ソフトウェア自体にバージョンアップ機能が設けられてい場合です。
そういったソフトウェアもChocolateyから導入して利用していたこともありましたが、
Chocolateyの公開の方が遅かったり、ソフトウェアが自動的にバージョンアップしてしまいChocolateyと食い違うなどなどしてまとまりが悪くなりました。

結論として、ChromeやFirefoxなど自動アップデート機能が搭載されているものは手動でインストールすることとしました。
JetBrains製品は[JetBrains Toolbox App](https://www.jetbrains.com/ja-jp/toolbox-app/)を利用します。


以上がパッケージマネージャの構成です。
Microsoft Store、Chocolatey GUI、Scoopの3つを利用しています。
3つも利用するのは多いので今後は2つまでは減らしたいと思いつつ、今まで問題に感じていたパスの問題などなどは解決しました。

# コンソール環境の構築

## ターミナル

Windows Terminalを利用しています。Microsoft Storeから導入可能です。
描画も高速ですしタブも利用できます。ショートカットなどカスタマイズ性も高いです。
Comderなどいろいろなターミナルがありましたが、細かい不満がありました。
Windows Terminalの誕生により解決しました。

`"launchMode": "maximized"` を設定して起動時に全画面になるようにしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103216/d2328ef1-f15a-fc77-c24c-7c45db1c40ab.png)

## シェル

以前はCygwinを利用していましたが、IOが遅いしWindowsの他の開発ツールとの連携やパスの扱いなど多くの問題がありました。
WSL2の登場によりLinuxベースの開発環境の構築は容易になり利用している方も多いと思います。
しかしPyCharmやWebStormといったWindows環境にあるIDEと組み合わせようとするとWSLとWindowsにそれぞれ実行環境が必要になるといった不整合が発生します。
またほしいのはLinuxではないのにLinuxが動いているのも余計に感じていました。

そこでPowerShellの利用を検討することとしました。
現在はMicrosoft Storeから導入でき、自動でWindows Terminalに追加されます。
// PowerShell 7.1より前は`C-c`でPowerShell自身が終了してしまうバグがありWindows PowerShellを利用していました。

PowerShellのカスタマイズについてまとめます。

### シェルのテーマ設定

Scoopの[ドキュメント](https://github.com/lukesampson/scoop/wiki/Theming-Powershell)に紹介されていたツールを利用しています。
pshazzではsteeefのテーマが気に入っています。

### Emacsなキーバインドにする

`$PROFILE` に下記を追記する。
参考: https://secondlife.hatenablog.jp/entry/2020/08/17/070735

```
Set-PSReadLineOption -EditMode Emacs
Set-PSReadLineOption -BellStyle None
# 標準だと Ctrl+d は DeleteCharOrExit のため、うっかり端末が終了することを防ぐ
Set-PSReadLineKeyHandler -Chord 'Ctrl+d' -Function DeleteChar
```

### Bashやらやらで書かれたスクリプトがあるので実行したい

Windowsでの開発が前提でなかったりすると、スクリプトがbashで書かれていたりします。
そういった場合はbashが必要です。
互換性を重視してWSLでコマンドを実行する方針としました。

bashに移動してスクリプトを実行するとシェルの移動が頻発してしまうので避けたいのと、主体はあくまでもPowerShellとしたかったので、
下記のように実行するシェルを指定するスタイルで実行することとしました。

```
> bash xxxx.sh
```

パスを渡す場合には変換が必要になるので`$PROFILE`に下記を追加して変換しています。

- `*` についてもWindows側で展開されると困るのでバッククオート付けて渡すことで防ぎます。
- `bash` でWSLが起動する挙動も維持したいので引数の数で動作を変更しています。
- `--login` の意図は後述します。

```
function bash
{
    if ($args.length -gt 0)
    {
        $args = $args -replace "\\","/" -replace "\*","\*"
        bash.exe --login -c "$args"
    }
    else
    {
        bash.exe
    }
}
```

### WSL経由でスクリプトを実行するときに、WindowsにもWSLにもパッケージをインストールするのは避けたい

bashなどのスクリプトをWSL経由して実行する場合にスクリプト内で何かしらのコマンドを呼び出すことがあります。
その場合、WSLにもインストールが必要になります(python使うやnpmスクリプトを呼び出すなど。そもそも最初からpythonやnpmに閉じておけという話はある)。
しかしWindows側にすでに存在するものをWSLにもインストールするといった事態は2重管理になりますし極力避けたいです。

WSLはWindowsのバイナリも実行可能です。
しかし、`.exe` を付ける必要があるので`python`と書かれたままでは起動されません。
コマンドが存在しない場合に自動的にWindows側のコマンドを実行することとしました。
参考: https://github.com/microsoft/WSL/issues/2003

下記スクリプトを `.bashrc` の先頭に追加してWSLでコマンドが見つからない場合にWindows側のバイナリを呼び出すようにします。
※先頭の方に置くのは非インタラクティブモードで読み込まないようにする処理が入っているためです(Ubuntu20.04に限った話かもしれません)
※先ほど `--login` を指定したのは `.bashrc` を読み込むためです。

```
# for Windows binary
command_not_found_handle()
{
    cmd=$1
    shift
    args=( "$@" )


    saveIFS="$IFS"
    IFS=:
    for dir in $PATH; do
        for executable in "$dir/$cmd.exe" "$dir/$cmd.com" "$dir/$cmd.bat"; do
            if [ -x $executable ]; then
                IFS="$saveIFS"
                "$executable" "${args[@]}"
                return
            fi
        done
    done


    IFS="$saveIFS"
    if [ -x /usr/lib/command-not-found ]; then
        /usr/lib/command-not-found -- "$cmd" "${args[@]}"
        return $?
    elif [ -x /usr/share/command-not-found/command-not-found ]; then
        /usr/share/command-not-found/command-not-found -- "$1" "${args[@]}"
        return $?
    else
        printf "%s: command not found\n" "$cmd" >&2
        return 127
    fi
}


export -f command_not_found_handle
```

### やっぱりfindやfgrepなど慣れ親しんだLinuxなツールを使いこともある

`cat` や `uniq` と言ったコマンドはPowerShellでも再現できますが、findやfgrepなどは簡単にはいきません。
そういったコマンドをPowerShellにしたからといって使いたいこともあります。
前方で記載した`bash`コマンド経由する方法や、`wsl`を経由することでも呼び出すことは可能です。
しかし、いちいち先頭につけるのは面倒です。

```
> bash "find -type f | xargs fgrep Windows" # これでもできるけど、
> find -type f | xargs fgrep Windows # こうやりたい。
```

そこで下記のようなスクリプトを `$PROFILE` に追加して特定のコマンドはWSL側から呼び出すようにしました。
参考(この記事は他にもいろいろと参考になりました): https://secon.dev/entry/2020/08/17/070735/

```
@"
  arch, base32, base64, basename, cat, cksum, comm, cp, cut, date, df, dircolors, dirname,
  echo, env, expand, expr, factor, false, fmt, fold, hashsum, head, hostname, join, link, ln,
  ls, md5sum, mkdir, mktemp, more, mv, nl, nproc, od, paste, printenv, printf, ptx, pwd,
  readlink, realpath, relpath, rm, rmdir, sed, seq, sha1sum, sha224sum, sha256sum, sha3-224sum,
  sha3-256sum, sha3-384sum, sha3-512sum, sha384sum, sha3sum, sha512sum, shake128sum,
  shake256sum, shred, shuf, sleep, sort, split, sum, sync, tac, tail, tee, test, touch, tr,
  true, truncate, tsort, unexpand, uniq, wc, whoami, yes
"@ -split ',' |
ForEach-Object { $_.trim() } |
Where-Object { ! @('tee', 'sort', 'sleep').Contains($_) } |
ForEach-Object {
    $cmd = $_
    if (Test-Path Alias:$cmd) { Remove-Item -Path Alias:$cmd }
    $fn = '$args = $args -replace "\\","/" -replace "\*","\*"; $input | bash.exe --login -c "' + $cmd + ' $args"'
    Invoke-Expression "function global:$cmd { $fn }"
}
```

このままだとパイプ(`|`)で渡して `xargs` ができないので下記のようにパイプで渡ってきたものを文字列で連結してコマンドに渡すようにしました。

```
function xargs
{
    $param = $input -join ' '
    Invoke-Expression "$args $param"
}
```

これでそれっぽくPowerShellからでも呼び出せるようになりました。
ただこのxargsの実装はかなりテキトーなのでエラーが発生するケースも多々あります。今後改善していきたいです。

```
> find -type f | xargs fgrep windows
./text.txt:fgerp de windows
./text2.txt:fgerp de windows
```

### プロファイル全体

```
# Env
Set-Item env:LANG -Value ja_JP.UTF-8

function bash
{
    if ($args.length -gt 0) 
    {
        $args = $args -replace "\\","/" -replace "\*","\*"
        bash.exe --login -c "$args"
    }
    else
    {
        bash.exe
    }
}

function xargs
{
    $param = $input -join ' '
    Invoke-Expression "$args $param"
}

Set-Alias grep Select-String
Set-Alias uniq Get-Unique

# プロファイルに追加
@"
  arch, base32, base64, basename, cat, cksum, comm, cp, cut, date, df, dircolors, dirname,
  echo, env, expand, expr, factor, false, fgrep, find, fmt, fold, hashsum, head, hostname, join, link, ln,
  ls, md5sum, mkdir, mktemp, more, mv, nl, nproc, od, paste, printenv, printf, ptx, pwd,
  readlink, realpath, relpath, rm, rmdir, seq, sha1sum, sha224sum, sha256sum, sha3-224sum,
  sha3-256sum, sha3-384sum, sha3-512sum, sha384sum, sha3sum, sha512sum, shake128sum,
  shake256sum, shred, shuf, sleep, sort, split, sum, sync, tac, tail, tee, test, touch, tr,
  true, truncate, tsort, unexpand, uniq, wc, whoami, yes
"@ -split ',' |
ForEach-Object { $_.trim() } |
Where-Object { ! @('tee', 'sort', 'sleep').Contains($_) } |
ForEach-Object {
    $cmd = $_
    if (Test-Path Alias:$cmd) { Remove-Item -Path Alias:$cmd }
    $fn = '$args = $args -replace "\\","/" -replace "\*","\*"; $input | bash.exe --login -c "' + $cmd + ' $args"'
    Invoke-Expression "function global:$cmd { $fn }" 
}

# Key Bind
Set-PSReadLineOption -EditMode Emacs
Set-PSReadLineOption -BellStyle None
# 標準だと Ctrl+d は DeleteCharOrExit のため、うっかり端末が終了することを防ぐ
Set-PSReadLineKeyHandler -Chord 'Ctrl+d' -Function DeleteChar

```

以上まででコマンドラインシェルとしてPowerShellをほぼ不便なく利用できるようになりました。
WSL2を経由するようなケースは私の場合はほとんどなく基本的にはPowerShellで閉じているので、
WSL1の互換性を気にしなくてよく、WSL2のメモリ誇大化やWindows側へのファイルアクセスが遅い問題などを気にしないで済むようになりました。


# 開発ツールの導入方法

ざっと導入元や方法など。

## JetBrains

JetBrains Toolbox App経由でインストールしています。

## Dockerの導入

ChocolateyからDocker Desktopをインストールています。
バックエンドはWSL2を利用しています。
WSL2の登場によりWindowsでのDockerの運用がかなり軽量になりました。

利用していないときは190MB程度とメモリの使用量も小さく快適です。
ファイルキャッシュでメモリが拡張されてしまいますが、時間とともにWindows側に返します。

## その他開発ツール

- VScode => Chocolateyで導入
- git => scoopで導入  
システム文字コードをUTF-8に設定している場合は下記環境変数の設定が必要でした。  
`Set-Item env:LANG -Value ja_JP.UTF-8`  
※プロファイルで設定する例
- sudo => scoopで導入  
これで管理者権限でWindows Terminalを起動しなくても管理者権限で実行できるようになります。
- vim => scoopで導入

# その他ツールの導入方法

## ブラウザ

- Chrome => 手動で導入
- Firefox => 手動で導入

## SNSツール

- Twitter => Microsoft Storeから導入
- Slack => Microsoft Storeから導入
- LINE => Microsoft Storeから導入

## 便利ツール

- Evernote => Microsoft Storeから導入
- 7-Zip => Chocolatey GUIから導入
- PowerToy => Chocolatey GUIから導入  
※マイクロソフトが開発しているベンリーツール集
- Adobe Acrobat Reader DC => Chocolatey GUIから導入
- WizTree => Chocolatey GUIから導入  
※ディスクの容量を占めているディレクトリやファイルの可視化、削除が行える
- EdgeDeflector => Chocolateyから導入  
Windows Searchからの検索で

## システム情報

- CPU-Z => Chocolatey GUIから導入
- CrystalDiskInfo => Chocolatey GUIから導入
- CrystalDiskMark => Chorolatey GUIから導入
- HWiNFO => Chocolatey GUIから導入  
※ハードウェア構成やセンサー情報が閲覧可能なアプリ

# 最後に

Windows10の開発環境構築をまとめました。
ほとんどPowerShellとの向き合い方でしたが、パッケージマネージャ含め以前構築した環境よりも扱いはよくなりました。
シンプルに扱えはしますがそこそこ複雑になってしまったのでもう少し構成を簡単にしたい。


10日公開は何とか間に合いましたが徹夜になりました...