Scoop と PackageManagement を使った Windows 環境の構築

# tl;dr

- パッケージマネージャ マジ便利
- **Scoop** と **PackageManagement** は Windows で使えるパッケージマネージャ
    - **PackageManagement**
        - Microsoft製のパッケージマネージャ
        - 後ろで動いてるのは、NuGet や Chocolatey
    - **Scoop**
        - 主にCLI環境を整えるのにフォーカスしているパッケージマネージャ
        - 未登録パッケージでも、独自にBucketというリポジトリを登録すれば管理できる
- この2つがあれば、大概のことは何とかなる

# パッケージマネージャって何？

そもそも、Windows だけを使っていると、パッケージマネージャと言うものにあまり馴染みが無いかも知れない。

例えば、Firefox をインストールしようと思った時、従来の Windows ユーザであれば、

1. ブラウザを開く ( 当然Firefoxではない )
1. Google で Firefox と検索
1. 公式サイトを表示
1. ダウンロードリンクを探す
1. ダウンロード
1. ダウンロードフォルダを開く
1. インストーラを実行
1. インストーラーの指示に従い、進める
2. 完了

という手順を踏んでいると思う。
また、アンインストールをする場合には、

1. **コントロールパネル** or **設定画面** を開く
2. **プログラムと機能** or **アプリと機能** を開く
3. アプリを選択し、アプリをアンインストール

という手順が必要となる。

これが、パッケージマネージャを利用すると、
ターミナルを起動し、

```bash
# インストール
brew cask install firefox

# アンインストール
brew cask uninstall firefox
```

でサクッとできてしまう。これだけで、もう魅力的なのは分かる。

それ以外にも、

- バージョンアップもコマンドから簡単
- 依存関係も上手いこと解決してくれる
    - **A** 動かすには **B** が必要で、**B** 動かすには **C** が必要で... だから全部入れちゃえ！
    - え、**C** 消すの？ **A** が使ってる **B** が使ってる **C** を、ホントに消しちゃうの？
- コマンドで全てが完了できる
    - すなわち、**手順の共有** が容易 
    - すなわち、**自動化** も容易

といった利点がある。

# Windows で使えるパッケージマネージャ

これまでも、Windows で使えるパッケージマネージャは存在していた。

- [NuGet](https://www.nuget.org/) 
- [Chocolatey](https://chocolatey.org/)
- [Microsoft Web Platform Installer] (https://www.microsoft.com/web/downloads/platform.aspx)

特に、Chocolatey は登録パッケージも豊富で、私の周りでは一番利用者が多かった。

## PackageManagement (OneGet)

**PackageManagement** は、Windows 8 の頃に生まれた **OneGet** というパッケージマネージャの後継で、Windows 10 で正式に採用された比較的新しいパッケージマネージャである。

[Github repo](https://github.com/oneget/oneget)
[Microsoft Docs](https://docs.microsoft.com/ja-jp/powershell/wmf/5.0/oneget_cmdlets)

これ、「また新しいエコシステム作りやがって ... 」 という話ではなく、各パッケージマネージャの操作を抽象化し、共通のコマンドで扱えるようにしただけのもので、裏で動いてるのは NuGet や Chocolatey リポジトリから取得したスクリプト。

### 特徴

- 何と言っても、Microsoft 公式
    - Windows10ではデフォルトで使える
- Chocolatey が後ろにいる事もあり、最初からパッケージが充実している

### 導入

以下記事でコンパクトにまとまっている。

- [Windows 10 のパッケージ管理エコシステムについて整理する。](https://qiita.com/japboy/items/451cf30fd0d763d98ae6)
- [Windows10 PackageManagementの導入](https://qiita.com/icbmuma/items/ceb55a497efd934bab38)

### 課題点

しかし、PackageManagement にはいくつか **使いづらいなぁ** と思う点がある

- コマンド体系が分かりづらい
    - サブコマンドではないので、何ができるのか、どうすればできるのかがぱっと分からない
        - 慣れの問題ではあるが …
- Chocolatey の問題点を継承している
    - アンインストールを結構な確率で失敗する
    - パッケージが古いままのものも多い
        - これからエコシステムが発展していけば、あるいは
    - UAC がインストールの邪魔をする
        - 解決しているとの情報もあるが、未検証

今の所、PackageManagement はほぼ Chocolatey のラッパーとしてしか使っておらず、PowerShell Gallery や Web PI も対象に入れれば、また印象が変わるかも。

## Scoop

**Scoop** は、主に CLI コマンドに特化したパッケージマネージャである。
Linuxに慣れ親しんだユーザが、Windows でもその使い心地を再現したいといったニーズに答えている。

[Github repo](https://github.com/lukesampson/scoop)

### 特徴

- 公式リポジトリ ( Scoop では、Bucket という ) で、使いそうな CLI コマンドをほとんど網羅している
    - sudo curl grep sed less coreutils wget which jq git hub win32-openssh rsync netcat gcc make...
- 無いものは、独自 Bucket を Github に置けば扱える
- インストール先が `~/scoop` 以下に集められて環境を汚さない (一部例外あり)
- インストール / アンインストール / アップデートに失敗しない
- コマンド体系も分かりやすい
- インストール定義が JSON でできているが、これ自体がとても分かりやすい

[Github の wiki](https://github.com/lukesampson/scoop/wiki) が良くまとめられているので、そちらも参照するとよい


### 導入

以下記事では、導入の他、Chocolatey との比較もあって参考になる

[ScoopでWindowsにおける開発環境構築を最適化しよう](https://blog.satotaichi.info/scoop/)

### 課題点

Scoop にも、使っていてあれ？と思う点がある

---

**2018/2/23 追記**
下記の PATH が壊される件、経過観察してみたところ、どうやら [Dashlane](https://www.dashlane.com/ja) によるものだった可能性が濃厚。
Scoop 、犯人扱いしてごめんよ。

---

- ~~**[未確定]** PATH が壊される~~
    - ~~現在、導入した数名に発生している。再現性もない。調査中。~~
    - ~~念のため、PATH をバックアップして使用中。~~
    - ~~最近は起きていないので直ったかな？~~
- 常に最新版
    - 開発環境等で使う場合は慎重に


# どう使い分けるのか

現在、これら2つを使い分けながら Windows 環境を作っていて、それなりに満足している。
以下、思考フロー。

#### ( Linux 環境で何かしたいなぁ ) ○。

⇒ Windows Subsystem for Linux (WSL) を使う。

#### ( Linux のあのコマンド使いたいなぁ )○。

⇒ Scoop で探す or なければ独自 Bucket に追加。

#### ( GUI のあるアプリ使いたいなぁ )○。

⇒ PackageManagement で探す。

#### ( あのプログラム言語の環境つくりたいなぁ )○。

- Case 1. ローカルで便利スクリプトを実行する、またはツールの実行環境・ビルド環境として必要
⇒ まずは Scoop で探す。Python, Go, Nodejs, Ruby 等はサクッと入れられる。

- Case 2. チームで開発環境・バージョンを合わせる
⇒ Docker を利用。

- Case 3. エディタの開発支援機能を使うための環境を用意する
⇒ 悩み中。いまは Scoop で雑に入れた環境を利用することで凌ぐ。

# 未解決問題

## GUI アプリケーションのバージョンアップ問題

最近の GUI アプリケーションは、独自にアップデート機能を有している物が多く、パッケージマネージャはほぼインストール / アンインストールを自動化しているだけな感じ。まぁ、それでも十分だけど。

## インストーラの設定デフォルト問題

Chocolatey の場合、内部でインストーラを Silent モードで起動しているだけのものも多いが、設定値がデフォルトになるのが、時々イラッとする。

## 管理対象多い問題

マシン移行を考えた時、**PackageManagement, Scoop, WSL 側の apt** と移行しないといけない環境が多い。
家 Mac では **Ansible + ちょっと手作業** で移行できるようできているので、それと比べると煩雑。

# Tips

## Alias の上書き

Powershell には、デフォルトでいくつかの Alias が存在している。

```ps1
PS> Get-Alias ls

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           ls -> Get-ChildItem
```
多分、PowerShell の使い勝手を Linux ユーザにも馴染むようにとの心遣いであると思うのだが、オプションも違ったりとイマイチ心遣いが足りず、Scoop で入れた方のコマンドが使いたいという時がある。


```ps1
PS> ls -la
Get-ChildItem : パラメーター名 'la' に一致するパラメーターが見つかりません。
発生場所 行:1 文字:4
+ ls -la
+    ~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-ChildItem]、ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
```

そんな時は、PowerShell の **Profile** で Alias を無効化すると良い。

```ps1
PS> notepad $profile
```

上記コマンドでメモ帳が立ち上がるので、その中に

```ps1:profile.ps1
...

del alias:ls -Force
```

と無効にしたい Alias を記述する。
これで、Alias にヒットせずに、Scoop で入れたコマンドの方が呼ばれる。

```ps1
PS> ls -la

drwxr-xr-x  9 Hoge Administrators 4096 Apr 25  2017 .
drwxr-xr-x 11 Hoge Administrators 4096 Nov 13 15:31 ..
-rw-r--r--  1 Hoge Administrators    7 Sep 23  2016 .gitignore
-rw-r--r--  1 Hoge Administrators  686 Oct  4  2016 README.md
```

Powershell 版が呼びたければ、直接 Alias の参照先コマンドを打てば良い

```ps1
PS> Get-ChildItem
```

## 環境変数の設定

Powershell からシステム環境変数を変更するのは、結構手間がかかったりする。
そこで、**Profile** に関数を設定しておくと良い。

最新版は、[gist に置いてある](https://gist.github.com/kentork/bf783d2a5378f32dbacb40d8897e7942)

```ps1:profile.ps1
...
function setenv($key, $value, $target) {
  if (! $target) {
    $target = "User"
  }
  if (($target -eq "Process") -Or ($target -eq "User") -Or ($target -eq "Machine")) {
    $now = [environment]::getEnvironmentVariable($key, $target)
    if ($now) {
      $tChoiceDescription = "System.Management.Automation.Host.ChoiceDescription"
      $result = $host.ui.PromptForChoice("", "Already Exists. Overwrite ?", @(
        New-Object $tChoiceDescription ("&Yes")
        New-Object $tChoiceDescription ("&No")
      ), 1)
      switch ($result) {
        0 {break}
        1 {
          Write-Host "`r`nAborted." -ForegroundColor DarkRed
          return
        }
      }
    }
    [environment]::setEnvironmentVariable($key, $value, $target)
    [environment]::setEnvironmentVariable($key, $value, "Process")
  } else {
    Write-Host "Failure ! - Invalid Target" -ForegroundColor DarkYellow
  }
}
function setpath($value, $target) {
  if (! $target) {
    $target = "User"
  }
  if (($target -eq "Process") -Or ($target -eq "User") -Or ($target -eq "Machine")) {
    $item = Convert-Path $value

    $path = [environment]::getEnvironmentVariable("PATH", $target)
    $list = $path -split ";"

    if (! $list.Contains($item)) {
      $_path = $path + ";" + $item + ";"
      $newpath = $_path -replace ";;", ";"
      [environment]::setEnvironmentVariable("PATH", $newpath, $target)

      $_path = [environment]::getEnvironmentVariable("PATH", "Process") + ";" + $item + ";"
      $newpath = $_path -replace ";;", ";"
      [environment]::setEnvironmentVariable("PATH", $newpath, "Process")
    } else {
      Write-Host "Already Exists." -ForegroundColor DarkYellow
    }
  } else {
    Write-Host "Failure ! - Invalid Target" -ForegroundColor DarkRed
  }
}
```

```ps1
PS1> setenv foo bar         # Add foo=bar as User Environment Variables
PS1> setenv foo bar Machine # Add foo=bar as Machine Environment Variables

PS1> setpath C:\hoge\huga   # Add 'C:\hoge\huga' to path as User Environment Variables
PS1> setpath C:/hoge/piyo   # '/' is convertible to '\'
```


## ConEmu 等から `$profile` が読み込めない

ConEmu や、 Cmder は、独自の Profile を持っているため、`$profile` を読み込んでくれない。そんな時は、独自 Profile の方で `$profile` を読み込むように指示すると良い。

```ps1:ConEmuのprofile
...

.$profile  # ← この一行を追加する
```