効率化のためにPowerShellを使ってみた話

この記事は[NTTコムウェア Advent Calendar 2021](https://qiita.com/advent-calendar/2021/nttcomware)の18日の記事です。

# はじめに
社内端末に好き勝手にソフトをインストールできない場面があります。そういった時でも、今あるもので作業の効率化ができないものかと考えることがあり、今後のことを踏まえ何かいい方法がないかなと模索した1つを紹介します。今回はWindows10にあるPowerShellを利用して、シチュエーションとともにコマンド例を紹介したいと思います。何かの参考になれば幸いです。

# PowerShellの起動

以下のどちらかの方法で起動できます。

1. [スタート] をクリックし、「PowerShell」と入力して [Windows PowerShell] をクリックします。
1. エクスプローラーのフォルダパスが記載されている、アドレスバーにフォルダパスを削除して、「powershell」のみを入力し、Enterキーを押すと起動します。

# やってみよう
以下に状況1と状況2を用意しました。それぞれの状況において、PowerShellを利用して解決していきたいと思います。

## 状況1（ファイル名一覧の作成）

あなたは複数の会社が参加する会議の準備担当者として、議題となるファイル名一覧を作成することになりました。各社の資料も合わせて一覧を作成するのですが、各社は直前まで資料作成を行っているようでギリギリになって資料が届きます。以下のフォルダ構成の時、なるべくすぐにファイル名一覧を作成するというシチュエーションとなります。

### フォルダ構成の例

```
PS \\<IPアドレス>\Users\name\Desktop\qiita> tree /f
\\<IPアドレス>.
└─2xyz0101_定例会議
    ├─A社
    │  │  【A】_aaa.pptx
    │  │  【A】_bbb.xlsx
    │  │  【A】_ccc.txt
    │  │
    │  ├─old
    │  │      ccc_old.txt
    │  │      ddd.txt
    │  │      ddd_old.txt
    │  │
    │  └─work
    │          【A】_eee.pptx
    │
    ├─B社
    │      【B】_aaa.pptx
    │      【B】_bbb.xlsx
    │      【B】_ccc.txt
    │
    └─C社
            【C】_aaa.pptx
            【C】_bbb.xlsx
            【C】_ccc.txt
```

### コマンドと結果

PowerShellコマンドと出力結果です。
1行目のディレクトリを、2行目はテキストファイル名に使用する日付をそれぞれ設定します。3行目がPowerShellのコマンドとなり、パイプ（|）の前半がファイル名を取得、後半が取得したファイル名をテキストファイルに出力するということを行ってます。Get-childitemの`-Recurse`オプションを使用される方は多いですが、今回の例ではoldやworkフォルダがあり、それを出力しないようにするには`-Depth`オプションをうまく使用するのが良いです。

```PowerShell.コマンド
PS \\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議>  
$directory = "\\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議\"
$date = Get-Date -Format "yyyyMMdd"
Get-childitem $directory -File -Depth 1 | where{ Write-output $_.Name >> filelist_$date.txt}
```

```filelist_yyyymmdd.txt
【A】_aaa.pptx
【A】_bbb.xlsx
【A】_ccc.txt
【B】_aaa.pptx
【B】_bbb.xlsx
【B】_ccc.txt
【C】_aaa.pptx
【C】_bbb.xlsx
【C】_ccc.txt
```

## シチュエーション2（ファイルサーバの資料検索)

大量にファイルが置かれているファイルサーバ（約20年間分）があります。あなたは今回初めてそのファイルサーバにアクセスできるようになり、とあるファイルを探す仕事をもらいます。構造が複雑でどこに何があるのかさっぱりわかりません。やっとの思いで、見つけると「よく見つけたね、次も頼むよ」と言われました。今後何回も苦労することを避ける時、どうしたらよいでしょうか。

### フォルダ構成の例
シミュレーション1と同じのを利用します。~~（本当はもっと大量にあったほうがいいですが...）~~

```
PS \\<IPアドレス>\Users\name\Desktop\qiita> tree /f
\\<IPアドレス>.
└─2xyz0101_定例会議
    ├─A社
    │  │  【A】_aaa.pptx
    │  │  【A】_bbb.xlsx
    │  │  【A】_ccc.txt
    │  │
    │  ├─old
    │  │      ccc_old.txt
    │  │      ddd.txt
    │  │      ddd_old.txt
    │  │
    │  └─work
    │          【A】_eee.pptx
    │
    ├─B社
    │      【B】_aaa.pptx
    │      【B】_bbb.xlsx
    │      【B】_ccc.txt
    │
    └─C社
            【C】_aaa.pptx
            【C】_bbb.xlsx
            【C】_ccc.txt
```

### コマンドと結果

PowerShellコマンドと出力結果です。
指定したフォルダとそのサブフォルダにあるファイル名をテキストファイルに出力します。テキストファイルにする理由は、大量のフォルダとファイルがあるファイルサーバのため、何回も検索には時間がかかります。そのため、一度テキストファイルに出力することで、検索をより早くできるようにしてます。また、今後ファイルが増減し、テキストファイルを更新するために、どのぐらい時間がかかるのかを確認する仕組みも入れています。

:::note info
フォルダパスを指定する際、1つのテキストファイルが大きくなりすぎないように、複数のフォルダパスに分けると良いと思います。
:::

```PowerShell.コマンド
PS \\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議>  
$date = Get-Date -Format "yyyyMMdd"

#フォルダパスを指定
$ary_names = @(
"\\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議\A社",
"\\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議\B社",
"\\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議\C社"
)

foreach($str_name in $ary_names){
  $StartTime = Get-Date
  
  $directory_name = $str_name.Replace('\', '_')
  $filename  = "\\<IPアドレス>\Users\name\Desktop\qiita\search_raw\" + $directory_name + '_' + $date + '.txt'
  Get-ChildItem $str_name -Name -File -Recurse | Sort-Object | Out-File  $filename -Encoding default

  $StopTime = Get-Date
  Write-Host $str_name "の計測時間:"  ($StopTime - $StartTime).TotalSeconds"秒"
  $TotalTime += ($StopTime - $StartTime).TotalSeconds
}

echo "全処理時間は:"$TotalTime"秒"
```

コンソール出力
どのぐらい時間がかかったのかは後ほど、テキストを更新する際の参考とします。

```
\\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議\A社 の計測時間: 0.0560023 秒
\\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議\B社 の計測時間: 0.0219958 秒
\\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議\C社 の計測時間: 0.0311039 秒
全処理時間は:
0.7781133秒
```

出力されたテキストを見てるとこのように出力されています。（例：A社）

```__<IPアドレス>_Users_name_Desktop_qiita_2xyz0101_定例会議_A社_yyyymmdd.txt
【A】_aaa.pptx
【A】_bbb.xlsx
【A】_ccc.txt
old\ccc_old.txt
old\ddd.txt
old\ddd_old.txt
work\eee.pptx
```

### 検索
あとは、ファイル名を検索するPowerShellコマンド`Select-String`を利用して、検索した結果を出力します。
出力した結果より、ファイルの場所がわかります。

```PowerShell.コマンド
PS \\<IPアドレス>\Users\name\Desktop\qiita\2xyz0101_定例会議>  
Select-String -Encoding default "検索したいワード" \\<IPアドレス>\Users\name\Desktop\qiita\search_raw\*.txt > \\<IPアドレス>\Users\name\Desktop\qiita\search_result\ファイル名.txt
```

検索したいワードを`aaa`にした時の出力結果は以下になります。
このファイルをもとにあなたが探したかったファイルを見つけます。

```ファイル名.txt
\\<IPアドレス>\Users\name\Desktop\qiita\search_raw\__<IPアドレス>_Users_name_Desktop_qiita_2xyz0101_定例会議_A社_yyyymmdd.txt:1:【A】_aaa.pptx
\\<IPアドレス>\Users\name\Desktop\qiita\search_raw\__<IPアドレス>_Users_name_Desktop_qiita_2xyz0101_定例会議_B社_yyyymmdd.txt:1:【B】_aaa.pptx
\\<IPアドレス>\Users\name\Desktop\qiita\search_raw\__<IPアドレス>_Users_name_Desktop_qiita_2xyz0101_定例会議_C社_yyyymmdd.txt:1:【C】_aaa.pptx
```

# おわりに
今回それぞれの状況を元に解決するPowerShellコマンドを紹介しました。こういった状況を考えるのは難しくもあり、楽しいですね。
皆さんも目の前の状況をどのようにPowerShellや他のツールを利用して、よりよく解決出来るのか考えてみてはいかがでしょうか。
また、どこかでお会いしましょう。

# 参考
1. [PowerShell とは](https://docs.microsoft.com/ja-jp/powershell/scripting/overview)
1. [Windows PowerShell の開始](https://docs.microsoft.com/ja-jp/powershell/scripting/windows-powershell/starting-windows-powershell?view=powershell-7.2)
1. [Microsoft.PowerShell.Management](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.management/)