しばたテックブログ

気分で書いている技術ブログです。

PowerShellにおける"配列リテラル"について

PowerShell Advent Calendar 2017、9日目です。

qiita.com

久しぶりのPowerShellの基本がわかっていなかったシリーズです。

以前のオープンソースカンファレンス2017 Hokkaidoでの登壇などで、ふつうに

PowerShellには配列リテラルがあります。

と説明していたのですが思いっきり間違っていました。
誤った説明をしてきたことを謝罪するとともに、あらためてPowerShellにおける"配列リテラル"について説明したいと思います。

PowerShellの仕様としての"配列リテラル"

中の人であるBruce Payetteさんが書いたWindows PowerShell in ActionPowerShell Blogの記事によれば、


  • Windows PowerShell in Action : 2.4 Collections: arrays and sequencesより一部引用

Here’s how array literals are defined in PowerShell: They’re not. There’s no array literal notation in PowerShell.


  • PowerShell Blog : Array Literals In PowerShell より一部引用

The first thing to understand is that there are no array literals in PowerShell Arrays are built using operators or casts.


と、PowerShellに"配列リテラル"は存在しないと説明されています。

例えば、

$a = 1,2

$a = (1,2)

といった例における配列は両者ともリテラルではなくカンマ演算子によって生成されるものであるとされています。

また、PowerShellの言語仕様書(本エントリではV3を例示)においても、2.3.5.5 Array literalsにおいて、

2.3.5.5 Array literals

PowerShell allows expressions of array type (§9) to be written using the unary comma operator (§7.2.1), array-expression (§7.1.7), the binary comma operator (§7.3), and the range operator (§7.4).

との説明がなされ、仕様として"配列リテラル"は存在しないことがきちんと説明されていました。

PowerShellの構文から見た"配列リテラル"

私が間違った説明をした原因とも言えるのですが、PowerShellの構文上、ASTにおいては"配列リテラル"に類するものが存在しています。

先述の配列の例をShowPSAstモジュールを使いASTを可視化してみると以下の様になります。

Import-Module ShowPSAst
{ $a = (1,2) } | Show-Ast

f:id:stknohg:20171207181148p:plain

1,2の部分についてはASTとしてはArrayLiteralAstとなっておりあたかも配列リテラルを取り扱うかの様になっています。

そして、言語仕様書ではこれはarray-literal-expressionとして以下の様に定義されています。

f:id:stknohg:20171207181206p:plain

図の上部を見ればわかりますが、これはarray-literal-expressionといいつつカンマ演算子専用の構文です。

この仕様と実装のちぐはぐさが私の誤解の根本的な原因となりました。

ちなみに、仕様の説明では"二項"カンマ演算子と記載されていますが、単項や3項以上の場合でもこのArrayLiteralAstが使われます。

# 単項の場合
{ $a = ,1 } | Show-Ast

f:id:stknohg:20171207181228p:plain

# 3項以上の場合
{ $a = 1,2,3,4 } | Show-Ast

f:id:stknohg:20171207181245p:plain

【補足】配列部分式演算子について

補足としてもう一つややこしい話をします。

PowerShellにおいて配列を生成するにはカンマ演算子のほかに@()による配列部分式演算子を使うことができます。

ここでカンマ演算子と配列部分式演算子の違いについて触れておきます。

配列部分式演算子は構文としてはarray-expressionとして以下の様に定義されています。

f:id:stknohg:20171207181259p:plain

この演算子最大の特徴は()の中がstatement-listと複数の文を受け入れことができる点になります。

この説明だけみればカンマ演算子と配列部分式演算子が全然別物だと認識できる*1かと思いますが、ちょっとややこしい例を挙げてみます。

#
# カンマ演算子による配列生成
#
# 定義可能
$array01 = (
    1,
    2,
    3
)
# エラーになる
$error01 = (
    1
   ,2
   ,3
)
# エラーになる
$error02 = (
    1
    2
    3
)

#
# 配列部分式演算子による配列生成
#
# 定義可能
$array10 = @(
    1,
    2,
    3
)
# 定義可能
$array11 = @(
    1
   ,2
   ,3
)
# 定義可能
$array12 = @(
    1
    2
    3
)

この6つの配列定義についてそれぞれ定義可・不可となる理由がわかるでしょうか?
これがきちんと説明できればPowerShellの配列をマスターしたも同然です。

カンマ演算子による配列生成の解説

カンマ演算子による配列生成で$array01以外がエラーとなるのはarray-literal-expressionの定義にある通り、カンマ演算子を使い複数行にわたる配列定義を行う際のカンマは後置のみ許可されているためです。

こちらは割と直感的に理解できるのではないかと思います。

配列部分式演算子による配列生成の解説

そして、ちょっと厄介なのが$array10$array12で、これらはすべて定義可能であり同じ結果を返します。

まず、$array10

$array01 = (
    1,
    2,
    3
)

$array10 = @($array01)

と同等のイメージになります。これはわかりやすいかと思います。

つぎに$array11$array12については以下の様に;を入れるとわかりやすくなります。
本項の最初に説明した()の中に複数の文が来るケースに該当します。

# 定義可能
$array11 = @(
    1;
   ,2;
   ,3;
)
# 定義可能
$array12 = @(
    1;
    2;
    3;
)

それぞれに注釈を入れると

# 定義可能
$array11 = @(
    1;    # 文1 : 数値リテラル -> パイプライン
   ,2;    # 文2 : 単項カンマ演算子 + 数値リテラル -> パイプライン
   ,3;    # 文3 : 単項カンマ演算子 + 数値リテラル -> パイプライン
)
# 定義可能
$array12 = @(
    1;    # 文1 : 数値リテラル -> パイプライン
    2;    # 文2 : 数値リテラル -> パイプライン
    3;    # 文3 : 数値リテラル -> パイプライン
)

と、$array11は単項カンマ演算子を使った3つの文、$array12は数値リテラルを使った3つの文から構成されており、3つの文を評価した結果が配列化されることになります。

本エントリではこれ以上触れませんがそれぞれのケースに対してASTを見てみるとより分かりやすいでしょう。

最後に

とりあえずこんな感じです。

個人的にPowerShellの配列は自由度の高さとその代償としての厄介さを抱えているのかなと思っています。

追記

ぎたぱそ先生の配列生成のパフォーマンスについての記事で本エントリを取り上げてもらいました。

tech.guitarrapc.com

とても有用な内容ですので是非こちらもご覧ください。

*1:実際別物です

Application Request Routing(ARR)を使ったRedmineの設定について

blog.shibata.tech

以前に構築したWindows Server上のRedmineですが「IISと一緒に80番ポートでアクセスできる様にしたいなぁ。」と思ったのでリバースプロキシとしてApplication Request Routing(以降ARR)を導入してみました。

ARRを使ったRedmineの設定についてはググるといろいろなサイトでその手順が紹介されているのですが、割と古めの情報が多かったので、本エントリでは最新のARR 3.0を使いできるだけGUIに頼らない手順を紹介しようと思います。

参考サイト

ARRとは何ぞやといった話や、基本的な設定手順についてはMVPの田中さんの以下の記事が詳しく参考になります。

www.buildinsider.net

本エントリも基本的にはこの記事の内容に従っており、記事中ではC#で書かれている個所をPowerShellで書き直している感じになります。

0. はじめに

本エントリの前提として、Windows Serverに最小構成でRedmineをインストールするでRedmineをインストールした直後の状態からはじめます。

この環境にIISとARRをインストールし、RedmineにアクセスするためのURLの書き換えを行っていきます。

1. IISとARRのインストール

はじめにIISとARRをインストールします。
IISはWindowsの機能の追加、またはInstall-WindowsFeatureコマンドレットからインストール可能ですが、ARRと一緒にインストールするのであればWebPIからインストールするのが手っ取り早く確実です。

以下の手順でWebPIをサイレントインストールします。

# Web PI
# ダウンロード
Invoke-WebRequest -Uri "https://download.microsoft.com/download/C/F/F/CFF3A0B8-99D4-41A2-AE1A-496C08BEB904/WebPlatformInstaller_amd64_en-US.msi" -OutFile "$(Get-Location -PSProvider Filesystem)\WebPlatformInstaller_amd64_en-US.msi"
# インストール
Start-Process -FilePath "msiexec.exe" -ArgumentList @('/i', "$(Get-Location -PSProvider Filesystem)\WebPlatformInstaller_amd64_en-US.msi", '/passive') -Wait -PassThru
# PATH追加
$env:PATH = "C:\Program Files\Microsoft\Web Platform Installer;" + $env:PATH

WebPIをインストールしたらWebpiCmdコマンドが使える様になりますので、以下の様にWebpiCmd /InstallコマンドでIISとARRをインストールします。

# IIS + ARR 3.0のインストール
#   IIS-WebServer        IIS-WebServer
#   ARRv3_0              Application Request Routing 3.0
#   ManagementService    IIS: Management Service
WebpiCmd.exe /Install /Products:"IIS-WebServer,ARRv3_0,ManagementService" /AcceptEULA

f:id:stknohg:20171105210848p:plain

f:id:stknohg:20171105210903p:plain

補足としてWebpiCmdでインストール可能なプロダクトは以下のコマンドで確認することができます。
環境に応じてインストールするプロダクトを変えてやれば良いでしょう。

WebpiCmd.exe /List /ListOption:All

2. ARRの構成

ここからARRの構成を行います。
はじめにWebAdministrationをインポートしておきます。

Import-Module WebAdministration

リバースプロキシの有効化

Set-WebConfigurationPropertyコマンドでリバースプロキシを有効にします。

# リバースプロキシ有効化
Set-WebConfigurationProperty -Filter system.webServer/proxy -Name Enabled -Value $true

GUIだと以下の設定と等価になります。

f:id:stknohg:20171105211131p:plain


f:id:stknohg:20171105211148p:plain


f:id:stknohg:20171105211204p:plain

URL書き換え規則の設定 - その1

ここからURL書き換え規則を追加していきます。
書き換え規則はDefault Web Siteに対して行うものとし、

  • /http://localhost:3000/

に書き換えるルールを設定してみます。

ざっくり図にするとこんな感じです。

f:id:stknohg:20171105212112p:plain:w400

書き換え設定はGUI、CUIのどちらから行おうとも最終的にはWeb.configの設定に行きつきます。

なのでPowerShellで設定する場合でもAdd-WebConfigurationPropertySet-WebConfigurationProperty等を使ってひたすら該当する設定をしてやるしかありません。

以下の例ではReverseProxyInboundRule1という規則*1を作っています。

# Rewrite Ruleの設定
$REWRITE_SITE_PATH = "IIS:\Sites\Default Web Site"
$REWRITE_RULE_NAME = "ReverseProxyInboundRule1" # GUIで設定した時と同じ名前にしておく
$REVERSE_PROXY_HOST = "localhost:3000"
$rule = @{
    Name = $REWRITE_RULE_NAME; 
    PatternSyntax = 'ECMAScript';
    stopProcessing = 'True'
}
Add-WebConfigurationProperty -PSPath $REWRITE_SITE_PATH -Filter "system.webServer/rewrite/rules" -Name "." -Value $rule
Set-WebConfigurationProperty -PSPath $REWRITE_SITE_PATH -Filter "system.webServer/rewrite/rules/rule[@name='$($REWRITE_RULE_NAME)']/action" -Name "url" -Value "http://$REVERSE_PROXY_HOST/{R:1}"
Set-WebConfigurationProperty -PSPath $REWRITE_SITE_PATH -Filter "system.webServer/rewrite/rules/rule[@name='$($REWRITE_RULE_NAME)']/action" -Name "type" -Value "Rewrite"
Set-WebConfigurationProperty -PSPath $REWRITE_SITE_PATH -Filter "system.webServer/rewrite/rules/rule[@name='$($REWRITE_RULE_NAME)']/match" -Name "url" -Value "(.*)"

GUIだと以下の設定と等価になります。

f:id:stknohg:20171105211319p:plain

f:id:stknohg:20171105211333p:plain

f:id:stknohg:20171105211348p:plain

f:id:stknohg:20171105211404p:plain

URL書き換え規則の設定 - その2

先ほどの設定で基本的な内容は理解できたかと思いますが、これではあまり使い勝手がよくありません。

そこで追加例として、

  • /redminehttp://localhost:3000/redmine

に書き換えるルールを設定してみます。

このルールであれば/redmine/*の場合だけRedmineを、その他URLの場合はIISを利用することができます。
(/redminehttp://localhost:3000/としていないのは、このルールだとRedmine側でCSS等のパスが合わなくなるためです)

f:id:stknohg:20171105212217p:plain:w400

Redmineの設定変更

このルールに合わせるため、URL書き換えの前にRedmine側でURLにプリフィックス(/redmine)を付ける設定変更をしておきます。
Redmineでプリフィックスを付けるにはいくつか方法がある様ですが、今回は/config/environment.rbを書き換える方法を採ります。

以下の手順で/config/environment.rb

Redmine::Utils::relative_url_root = "/redmine"

の1行を追加します。

# CSS等のパスを変えるために ./config/environment.rb にPrefix設定を追加。
# http://www.redmine.org/projects/redmine/wiki/HowTo_Install_Redmine_in_a_sub-URI
$REDMINE_VER = '3.4.2'
$REDMINE_INSTALL_ROOT = 'C:\'
$REDMINE_INSTALL_PATH = Join-Path $REDMINE_INSTALL_ROOT "redmine-$REDMINE_VER"
@"
#
Redmine::Utils::relative_url_root = "/redmine"
"@ -replace "`n","`r`n" | Add-Content (Join-Path $REDMINE_INSTALL_PATH "config\environment.rb") -Encoding Default

/config/environment.rbの設定を変更した後は、Thinの起動引数に--prefixを付ける変更をしてRedmineを起動しなおします。

# サービス設定を変更
$REDMINE_SERVICE_NAME = "Redmine"
$REDMINE_INSTALL_PORT = 3000
$REDMINE_URL_PREFIX = "/redmine"
Stop-Service $REDMINE_SERVICE_NAME
thin_service remove -N $REDMINE_SERVICE_NAME
thin_service install -N $REDMINE_SERVICE_NAME -c "$REDMINE_INSTALL_PATH" -p $REDMINE_INSTALL_PORT -e production --prefix $REDMINE_URL_PREFIX
Start-Service $REDMINE_SERVICE_NAME

これでプリフィックスを付けてRedmineを起動することができ、

でアクセス可能になります。

URL書き換えルールの設定

最後にURL書き換えルールを設定します。
その1で追加したルールを以下のコマンドで書き換えます。

Set-WebConfigurationProperty -PSPath $REWRITE_SITE_PATH -Filter "system.webServer/rewrite/rules/rule[@name='$($REWRITE_RULE_NAME)']/action" -Name "url" -Value "http://$REVERSE_PROXY_HOST/{R:0}"
Set-WebConfigurationProperty -PSPath $REWRITE_SITE_PATH -Filter "system.webServer/rewrite/rules/rule[@name='$($REWRITE_RULE_NAME)']/match" -Name "url" -Value "redmine(.*)"

GUIだとこう書き換えています。

f:id:stknohg:20171105211509p:plain


f:id:stknohg:20171105211530p:plain

結果

最終的に/redmineでアクセスした場合はRedmineに、

f:id:stknohg:20171105212958p:plain

それ以外のURLでアクセスした場合はIIS(ここではデフォルトサイト)にアクセス可能になり

f:id:stknohg:20171105213110p:plain

RedmineとIISのサイトを併用可能にすることができました。

*1:GUIで設定した場合につく名前と同じ

Windows 10のPowerShellモジュール変更点まとめ

まとめ、というか作成予定地です。

Windows 10の各バージョンごとでのPowerShellモジュールの変更点についてGistに簡単なメモをまとめたのでリンクを張っておきます。
時間があればちゃんとした記事としてまとめ直したいと思います。

変更点の調査方法

各バージョン毎に新規インストールした環境、または手元の開発環境に対して簡単なスクリプトを流して、実行環境のモジュールの一覧・コマンドレットや関数の一覧・パラメーターの一覧をまとめてJSONファイルに保存し、Diffをとって変更点を調べています。

割とざっくり調べているので抜け・漏れは普通にあると思いますので結果については軽く見てください。

スクリプトはちょいちょい修正しているのでもう少し内容が固まったら公開しても良いかなといった感じです...

Windows 10 May 2021 Update (21H1)での変更点

Windows 10 October 2020 Update(20H2)での変更点

Windows 10 May 2020 Update (20H1, 2004)での変更点

Windows 10 November 2019 Update (19H2, 1909)での変更点

Windows 10 May 2019 Update (19H1, 1903)での変更点

Windows 10 October 2018 Update (RS5, 1809)での変更点

Windows 10 Spring Creators Update (RS4, 1803)での変更点

Windows 10 Fall Creators Update (RS3, 1709)での変更点

Windows 10 Creators Update (RS2, 1703)での変更点

Windows 10 Anniversary Update (RS1, 1607)での変更点

Windows 10 November Update (TH2, 1511)での変更点