PowerShell 第7回 他のPowerShell Scriptを実行＋関数名の重複

#はじめに
もともと他のPowerShell Scriptを呼び出したいと思い、そのやり方を調べました(参照[PowerShell 第5回 他のPowerShell Scriptを実行](https://qiita.com/Mount/items/14b29995b7d9571f3df2))。
そうしたときに呼び出し元と呼び出し先で関数名が同じだったらどうなるか？と思い、調べました(参照[PowerShell 第5回 他のPowerShell Scriptを実行](https://qiita.com/Mount/items/14b29995b7d9571f3df2))。
結局のところ、同一Script内では関数名が重複するとどんどん上書きされていくだけということがわかりました。
では、他のScriptだったらどうなるのか？というのをまとめるのが今回です。

#今回実施する内容
参照元Scriptから参照先Scriptを呼び出しつつ、参照先に存在するScript内の関数名と同一の関数名が参照元Scriptにある場合どうなるのかをまとめます。

#ソースコード(Git Hub)
[PowerShell_07_Refer_SameName](https://github.com/Build-Mount/PowerShell_07_Refer_SameName)

#環境
OS: Windows 10 JP (64bit)
PowerShell version: 5.1.19041.1

#参考
[PowerShell 第5回 他のPowerShell Scriptを実行](https://qiita.com/Mount/items/14b29995b7d9571f3df2)
他のPowerShell Scriptを呼び出して実行する方法の記事です。

[PowerShell 第6回 関数名の重複](https://qiita.com/Mount/items/4683e899afc7874cbf31)
Script内で関数名が重複したらどうなるのかについての記事です。

#用語

#.（ドット）を使用して実現
.（ドット）を使用して、参照元Scriptと参照先Scriptに同一名称の関数を記載すると、どちらが適用されるか確認します。
結論としては、**「.（ドット）を使用して参照先Scriptを呼び出した場合、関数呼び出し時に最後に定義した関数が呼ばれる」**です。

##ソースコード

```PowerShell:BaseScript7.ps1
function ShowMessage {
	[CmdletBinding()]
	param (
		[Parameter(Mandatory = $true)]
		[string]$Message
	)
    Write-Host "参照元Script：" -NoNewline
	Write-Host $Message
}

try {
    . .\referredScript7.ps1

    ShowMessage("こんにちは。")
} catch {
    Write-Host $Error[0]
}
```

```PowerShell:ReferenceScript7.ps1
function ShowMessage {
	[CmdletBinding()]
	param (
		[Parameter(Mandatory = $true)]
		[string]$Message
	)
    Write-Host "参照先Script：" -NoNewline
	Write-Host $Message
}
```

```
＜実行結果＞
参照先Script：こんにちは。
```

##ソースコードの説明
参照元のShowMessageでなく、参照先のShowMessageが呼ばれます。
今回のソースコードを参照先Scriptを読み込んだものを展開すると、

・参照元関数ShowMessage
・参照先関数ShowMessage
・ShowMessage呼び出し

となるため、参照先Scriptの関数ShowMessageが呼ばれます。
ということで、参照先Scriptの位置を変えると動作が変わります。

##ソースコード

```PowerShell:BaseScript8.ps1
. .\referredScript7.ps1

function ShowMessage {
	[CmdletBinding()]
	param (
		[Parameter(Mandatory = $true)]
		[string]$Message
	)
    Write-Host "参照元Script：" -NoNewline
	Write-Host $Message
}

try {

    ShowMessage("こんにちは。")
} catch {
    Write-Host $Error[0]
}
```

```
＜実行結果＞
参照元Script：こんにちは。
```

##ソースコードの説明
参照先のShowMessageでなく、参照元のShowMessageが呼ばれます。
今回のソースコードを参照先Scriptを読み込んだものを展開すると、

・参照先関数ShowMessage
・参照元関数ShowMessage
・ShowMessage呼び出し

となるため、参照元Scriptの関数ShowMessageが呼ばれます。


#&を使用して実現
関数名が同じ場合上書きされるということは理解したのですが、これだと万が一参照先のScriptと関数名がかぶったら、気づかずに期待動作しないことがありますので、何とかできないのかと調べました。
スコープの問題かと思いまして調べてみたところ、期待する動作は見つかりませんでしたが、許容可能なのはありました。
&を使って呼び出します。もうひとつは前回も試したInvoke-Expressionです。
まずは&からです。&はCall演算子として利用されるようです。

##Call演算子の説明
Microsoftのページの参照です。
簡単に言えば、&にあった記載の文字列を実行するものであり、その中に変数などはおけないということだと理解しました。

[about_Operators](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.1)

>##Call 演算子 &
>コマンド、スクリプト、またはスクリプトブロックを実行します。 呼び出し演算子 ("呼び出し演算子" とも呼ばれます) を使用すると、変数に格納され、文字列またはスクリプトブロックによって表されるコマンドを実行できます。 呼び出し演算子は、子スコープで実行されます。 スコープの詳細については、「 about_Scopes」を参照してください。
>
>この例では、コマンドを文字列に格納し、call 演算子を使用して実行します。
>
>```PowerShell
>PS> $c = "get-executionpolicy"
>PS> $c
>get-executionpolicy
>PS> & $c
>AllSigned
>```
>
>呼び出し演算子は、文字列を解析しません。 これは、call 演算子を使用するときに、文字列内のコマンドパラメーターを使用できないことを意味します。
>
>```PowerShell
>PS> $c = "Get-Service -Name Spooler"
>PS> $c
>Get-Service -Name Spooler
>PS> & $c
>& : The term 'Get-Service -Name Spooler' is not recognized as the name of a
>cmdlet, function, script file, or operable program. Check the spelling of
>the name, or if a path was included, verify that the path is correct and
>try again.
>```
>呼び出し演算子を使用する と、解析 エラーを発生させるコードを実行できます。
>
>```PowerShell
>PS> & "1+1"
>& : The term '1+1' is not recognized as the name of a cmdlet, function, script
>file, or operable program. Check the spelling of the name, or if a path was
>included, verify that the path is correct and try again.
>At line:1 char:2
>+ & "1+1"
>+  ~~~~~
>    + CategoryInfo          : ObjectNotFound: (1+1:String) [], CommandNotFoundException
>    + FullyQualifiedErrorId : CommandNotFoundException
>PS> Invoke-Expression "1+1"
>2
>```
>Call 演算子を使用して、ファイル名を使用してスクリプトを実行できます。 次の例では、スペースを含むスクリプトファイル名を示しています。 スクリプトを実行しようとすると、PowerShell によって、ファイル名を含む引用符で囲まれた文字列の内容が表示されます。 Call 演算子を使用すると、ファイル名を含む文字列の内容を実行できます。
>
>```PowerShell
>PS C:\Scripts> Get-ChildItem
>
>    Directory: C:\Scripts
>
>
>Mode                LastWriteTime         Length Name
>----                -------------         ------ ----
>-a----        8/28/2018   1:36 PM             58 script name with spaces.ps1
>
>PS C:\Scripts> ".\script name with spaces.ps1"
>.\script name with spaces.ps1
>PS C:\Scripts> & ".\script name with spaces.ps1"
>Hello World!
>```
>スクリプトブロックの詳細については、「 about_Script_Blocks」を参照してください。

##ソースコード

```PowerShell:BaseScript9.ps1
function ShowMessage {
	[CmdletBinding()]
	param (
		[Parameter(Mandatory = $true)]
		[string]$Message
	)
    Write-Host "参照元Script：" -NoNewline
	Write-Host $Message
}

try {
    & ".\referredScript9.ps1"

    ShowMessage("こんにちは。")
} catch {
    Write-Host $Error[0]
}
```

```PowerShell:ReferenceScript9.ps1
function ShowMessage {
	[CmdletBinding()]
	param (
		[Parameter(Mandatory = $true)]
		[string]$Message
	)
    Write-Host "参照先Script：" -NoNewline
	Write-Host $Message
}

ShowMessage("こんにちは。")
```

```
＜実行結果＞
参照先Script：こんにちは。
参照元Script：こんにちは。
```

##ソースコードの説明
BaseScript9.ps1の実行文で、Call演算子&を使用して、ReferredScript9.ps1を呼び出して、そのあとにShowMessageを実行しました。
ReferredScript9.ps1内にも実行文でShowMessageを呼び出しました。

.（ドット）を使用した場合は、[.（ドット）を使用して実現](https://qiita.com/drafts/d274c7cad7037b0fee6c/edit#%E3%83%89%E3%83%83%E3%83%88%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E5%AE%9F%E7%8F%BE)で示した通り、同一関数は上書きされるため、
「参照先Script:こんにちは。」
となりましたが、Call演算子&を使用すると、
「参照先Script：こんにちは。
　参照元Script：こんにちは。」
のように、ReferredScript9.ps1内のShowMessageの実行はそのScriptの関数が実行され、BasicScript9.ps1内のShowMessageの実行は、BasicScript9.ps1の関数が実行されました。
これが、Microsoftのページに記載のあった`呼び出し演算子は、子スコープで実行されます。`ということだと理解しました。

#おわりに
今回は、他のPowerShell Scriptを呼び出したときに、関数名がScript間で重複したらどうなるかについてまとめました。

|方法|関数の動作|
|:--|:--|
|・（ドット）を使用する場合|関数は上書きされる|
|&を使用する場合|関数はそれぞれのスコープで動作する|

.（ドット）を使用するか&を使用するかの判断は難しいなと思いました。
そもそも関数名が重複するってことが考えづらく、あるとしたらどちらのScriptも自分で作成しているようなケースだと思います。
そういった場合は、関数をモジュール化したり、重複しないように命名を工夫したりするだろうし、そう考えるとあまり気にすることではないような気もしました。