元Linux系開発者がPowerShellはじめて面白かったこと

#はじめに
この記事は[Fujitsu Advent Calendar 2021](https://qiita.com/advent-calendar/2021/fujitsu) 19日目の記事です。

この記事では、元Linux系開発者で、Bashのシェルスクリプトでツール開発をしていた私が、Windowsで同様なことができるPowerShellを勉強して、面白いと感じたことを書きます。

#PowerShellの概要
- マイクロソフトが開発したシェル、スクリプト言語
- 2006年に公開
- 2016年にオープンソース化
- 2018年公開のPowerShell Core 6.0からマルチプラットフォームに対応(Windows、Linux、macOSに対応)
- .NET Framework(Windows PowerShell5系以前)、または、.NET Core(PowerShell Core6系以降)で動作する
- コマンドレット(Linuxのコマンド相当)が使用可能
- パイプライン(Linuxのパイプ相当)やリダイレクトが使用可能
- スクリプトはBashのように、if文やwhile文などの制御構文が使用可能

※詳細は[Wikipedia](https://ja.wikipedia.org/wiki/PowerShell)参照。

#BashとPowerShellの違い
- Bash
  - 扱うのはテキスト
  - 変数にはテキストが格納される
  - 変数には型がない
  - パイプは標準出力で渡されたテキストを標準入力で受け取る
- PowerShell
  - 扱うのは.NETランタイムのオブジェクト
  - オブジェクトのプロパティやメソッドにアクセス可能
  - 変数にはオブジェクトが格納される
  - オブジェクトや変数には型がある
  - パイプラインはオブジェクトを送る

#オブジェクトの例
PowerShellが扱うのは.NETランタイムのオブジェクトなので、文字列や数値のプロパティやメソッドにアクセスできます。

```
# 文字列オブジェクトのLengthプロパティで文字数を取得
PS > "abc".Length
3

# ToUpperメソッドで大文字に変換
PS > "abc".ToUpper()
ABC

# 数値リテラルを文字列に変換して文字数を取得、数値リテラルのメソッドにアクセスするには括弧で囲む必要あり
PS > (123).ToString().Length
3
```

Get-Memberコマンドレットで、オブジェクトや変数の型名や、プロパティ、メソッドを確認できます。

```
# 文字列オブジェクトの情報取得
PS > Get-Member -InputObject "abc"


   TypeName: System.String

Name             MemberType            Definition
----             ----------            ----------
Clone            Method                System.Object Clone(), System.Object ICloneable.Clone()
CompareTo        Method                int CompareTo(System.Object value), int CompareTo(string strB), int IComparab...
Contains         Method                bool Contains(string value)
CopyTo           Method                void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int co...
EndsWith         Method                bool EndsWith(string value), bool EndsWith(string value, System.StringCompari...
Equals           Method                bool Equals(System.Object obj), bool Equals(string value), bool Equals(string...
GetEnumerator    Method                System.CharEnumerator GetEnumerator(), System.Collections.IEnumerator IEnumer...
GetHashCode      Method                int GetHashCode()
GetType          Method                type GetType()
GetTypeCode      Method                System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode()
IndexOf          Method                int IndexOf(char value), int IndexOf(char value, int startIndex), int IndexOf...
IndexOfAny       Method                int IndexOfAny(char[] anyOf), int IndexOfAny(char[] anyOf, int startIndex), i...
Insert           Method                string Insert(int startIndex, string value)
IsNormalized     Method                bool IsNormalized(), bool IsNormalized(System.Text.NormalizationForm normaliz...
LastIndexOf      Method                int LastIndexOf(char value), int LastIndexOf(char value, int startIndex), int...
LastIndexOfAny   Method                int LastIndexOfAny(char[] anyOf), int LastIndexOfAny(char[] anyOf, int startI...
Normalize        Method                string Normalize(), string Normalize(System.Text.NormalizationForm normalizat...
PadLeft          Method                string PadLeft(int totalWidth), string PadLeft(int totalWidth, char paddingChar)
PadRight         Method                string PadRight(int totalWidth), string PadRight(int totalWidth, char padding...
Remove           Method                string Remove(int startIndex, int count), string Remove(int startIndex)
Replace          Method                string Replace(char oldChar, char newChar), string Replace(string oldValue, s...
Split            Method                string[] Split(Params char[] separator), string[] Split(char[] separator, int...
StartsWith       Method                bool StartsWith(string value), bool StartsWith(string value, System.StringCom...
Substring        Method                string Substring(int startIndex), string Substring(int startIndex, int length)
ToBoolean        Method                bool IConvertible.ToBoolean(System.IFormatProvider provider)
ToByte           Method                byte IConvertible.ToByte(System.IFormatProvider provider)
ToChar           Method                char IConvertible.ToChar(System.IFormatProvider provider)
ToCharArray      Method                char[] ToCharArray(), char[] ToCharArray(int startIndex, int length)
ToDateTime       Method                datetime IConvertible.ToDateTime(System.IFormatProvider provider)
ToDecimal        Method                decimal IConvertible.ToDecimal(System.IFormatProvider provider)
ToDouble         Method                double IConvertible.ToDouble(System.IFormatProvider provider)
ToInt16          Method                int16 IConvertible.ToInt16(System.IFormatProvider provider)
ToInt32          Method                int IConvertible.ToInt32(System.IFormatProvider provider)
ToInt64          Method                long IConvertible.ToInt64(System.IFormatProvider provider)
ToLower          Method                string ToLower(), string ToLower(cultureinfo culture)
ToLowerInvariant Method                string ToLowerInvariant()
ToSByte          Method                sbyte IConvertible.ToSByte(System.IFormatProvider provider)
ToSingle         Method                float IConvertible.ToSingle(System.IFormatProvider provider)
ToString         Method                string ToString(), string ToString(System.IFormatProvider provider), string I...
ToType           Method                System.Object IConvertible.ToType(type conversionType, System.IFormatProvider...
ToUInt16         Method                uint16 IConvertible.ToUInt16(System.IFormatProvider provider)
ToUInt32         Method                uint32 IConvertible.ToUInt32(System.IFormatProvider provider)
ToUInt64         Method                uint64 IConvertible.ToUInt64(System.IFormatProvider provider)
ToUpper          Method                string ToUpper(), string ToUpper(cultureinfo culture)
ToUpperInvariant Method                string ToUpperInvariant()
Trim             Method                string Trim(Params char[] trimChars), string Trim()
TrimEnd          Method                string TrimEnd(Params char[] trimChars)
TrimStart        Method                string TrimStart(Params char[] trimChars)
Chars            ParameterizedProperty char Chars(int index) {get;}
Length           Property              int Length {get;}


# 数値オブジェクトの情報取得
PS > Get-Member -InputObject 123


   TypeName: System.Int32

Name        MemberType Definition
----        ---------- ----------
CompareTo   Method     int CompareTo(System.Object value), int CompareTo(int value), int IComparable.CompareTo(Syste...
Equals      Method     bool Equals(System.Object obj), bool Equals(int obj), bool IEquatable[int].Equals(int other)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
GetTypeCode Method     System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode()
ToBoolean   Method     bool IConvertible.ToBoolean(System.IFormatProvider provider)
ToByte      Method     byte IConvertible.ToByte(System.IFormatProvider provider)
ToChar      Method     char IConvertible.ToChar(System.IFormatProvider provider)
ToDateTime  Method     datetime IConvertible.ToDateTime(System.IFormatProvider provider)
ToDecimal   Method     decimal IConvertible.ToDecimal(System.IFormatProvider provider)
ToDouble    Method     double IConvertible.ToDouble(System.IFormatProvider provider)
ToInt16     Method     int16 IConvertible.ToInt16(System.IFormatProvider provider)
ToInt32     Method     int IConvertible.ToInt32(System.IFormatProvider provider)
ToInt64     Method     long IConvertible.ToInt64(System.IFormatProvider provider)
ToSByte     Method     sbyte IConvertible.ToSByte(System.IFormatProvider provider)
ToSingle    Method     float IConvertible.ToSingle(System.IFormatProvider provider)
ToString    Method     string ToString(), string ToString(string format), string ToString(System.IFormatProvider pro...
ToType      Method     System.Object IConvertible.ToType(type conversionType, System.IFormatProvider provider)
ToUInt16    Method     uint16 IConvertible.ToUInt16(System.IFormatProvider provider)
ToUInt32    Method     uint32 IConvertible.ToUInt32(System.IFormatProvider provider)
ToUInt64    Method     uint64 IConvertible.ToUInt64(System.IFormatProvider provider)


# 変数を定義して、変数の型情報を調べてみる
PS > $a = 1.23
PS > Get-Member -InputObject $a


   TypeName: System.Double

Name        MemberType Definition
----        ---------- ----------
CompareTo   Method     int CompareTo(System.Object value), int CompareTo(double value), int IComparable.CompareTo(Sy...
Equals      Method     bool Equals(System.Object obj), bool Equals(double obj), bool IEquatable[double].Equals(doubl...
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
GetTypeCode Method     System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode()
ToBoolean   Method     bool IConvertible.ToBoolean(System.IFormatProvider provider)
ToByte      Method     byte IConvertible.ToByte(System.IFormatProvider provider)
ToChar      Method     char IConvertible.ToChar(System.IFormatProvider provider)
ToDateTime  Method     datetime IConvertible.ToDateTime(System.IFormatProvider provider)
ToDecimal   Method     decimal IConvertible.ToDecimal(System.IFormatProvider provider)
ToDouble    Method     double IConvertible.ToDouble(System.IFormatProvider provider)
ToInt16     Method     int16 IConvertible.ToInt16(System.IFormatProvider provider)
ToInt32     Method     int IConvertible.ToInt32(System.IFormatProvider provider)
ToInt64     Method     long IConvertible.ToInt64(System.IFormatProvider provider)
ToSByte     Method     sbyte IConvertible.ToSByte(System.IFormatProvider provider)
ToSingle    Method     float IConvertible.ToSingle(System.IFormatProvider provider)
ToString    Method     string ToString(), string ToString(string format), string ToString(System.IFormatProvider pro...
ToType      Method     System.Object IConvertible.ToType(type conversionType, System.IFormatProvider provider)
ToUInt16    Method     uint16 IConvertible.ToUInt16(System.IFormatProvider provider)
ToUInt32    Method     uint32 IConvertible.ToUInt32(System.IFormatProvider provider)
ToUInt64    Method     uint64 IConvertible.ToUInt64(System.IFormatProvider provider)
```

ハッシュテーブルなどのデータ構造もオブジェクトとして使用できます。

```
# ハッシュテーブルを初期化、変数に格納
PS > $hash_table = @{
>>   'key1' = 'value1'
>>   'key2' = 'value2'
>>   'key3' = 'value3'
>> }

# キーから値を取得
PS > $hash_table['key1']
value1

# Keysプロパティでキーの一覧を取得
PS > $hash_table.Keys
key3
key1
key2

# Valuesプロパティで値の一覧を取得
PS > $hash_table.Values
value3
value1
value2
```

#パイプラインの動作
PowerShellのパイプラインはオブジェクトを送るため、Linuxのパイプと比べると複雑なルールでコマンドレット間が接続されます。

ルールを一言で言うと、「送り元のコマンドレットの出力オブジェクトの型に応じた、送り先のコマンドレットのパラメーターが自動的に適用される」となります。
\#「コマンドレットのパラメーター」は、Linuxで言うところの「コマンドのオプション」に相当。

以下では、JSONを変換するConvertFrom-Jsonコマンドレットでのパイプラインの動作を見てみます。

```
# ConvertFrom-Jsonのhelpを確認、JSON文字列を渡せばよさそう
PS > help ConvertFrom-Json

名前
    ConvertFrom-Json

概要
    Converts a JSON-formatted string to a custom object.


構文
    ConvertFrom-Json [-InputObject] <System.String> [<CommonParameters>]

～省略～

# JSON文字列を渡してみる、別のオブジェクトに変換されて出力される
PS > $json = '{
>> "name" : "taro",
>> "age" : 18,
>> "job" : "engineer"
>> }'

PS > $json | ConvertFrom-Json

name age job
---- --- ---
taro  18 engineer

# 試しにHashtableオブジェクトを渡してみる
PS > $hash_table = @{
>> 'name' = 'taro'
>> 'age' = 18
>> 'job' = 'engineer'
>> }

PS > $hash_table | ConvertFrom-Json
ConvertFrom-Json : 無効な JSON プリミティブです: System.Collections.Hashtable。
発生場所 行:1 文字:15
+ $hash_table | ConvertFrom-Json
+               ~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [ConvertFrom-Json], ArgumentException
    + FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand

```

※パイプラインは実際にはもっと複雑なルールで動作します。詳細は[参考文献](#参考文献)を参照。

#PowerShellで作成したツールの紹介
テスト用の環境構築で、「ユーザーとグループを作成し、ユーザーをグループに追加」を手作業で行っていたのを、以下のようなスクリプトで自動化しました。

```CreateUserGroup.ps1
# 作成するユーザーの配列
$users = @('Test_User1', 'Test_User2', 'Test_User3', 'Test_User4', 'Test_User5')

# パスワードはSecureString型に変換する必要がある
$password = convertto-securestring 'Test_Password' -AsPlainText -Force

foreach($user in $users) {
    # ユーザー作成
    New-LocalUser -Name $user -Password $password
}

#作成するグループの配列
$groups = @('Test_Group1', 'Test_Group2')

foreach($group in $groups) {
    # グループ作成
    New-LocalGroup -Name $group
}

# ユーザー、グループの紐づけ情報(ハッシュテーブル)
$user_group = @{
    'Test_Group1' = @('Test_User1', 'Test_User2')
    'Test_Group2' = @('Test_User3', 'Test_User4', 'Test_User5')
}

foreach($entry in $user_group.GetEnumerator()) {
    foreach($value in $entry.Value) {
        # ユーザーをグループに追加
        Add-LocalGroupMember -Group $entry.Key -Member $value
    }
}
```

#参考文献
[PowerShell実践ガイドブック クロスプラットフォーム対応の次世代シェルを徹底解説](https://www.amazon.co.jp/PowerShell%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89%E3%83%96%E3%83%83%E3%82%AF-%E3%82%AF%E3%83%AD%E3%82%B9%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E5%AF%BE%E5%BF%9C%E3%81%AE%E6%AC%A1%E4%B8%96%E4%BB%A3%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%92%E5%BE%B9%E5%BA%95%E8%A7%A3%E8%AA%AC-%E5%90%89%E5%B4%8E-%E7%94%9F-ebook/dp/B07D73G2X7)