tech.guitarrapc.cóm

Technical updates

PowerShell で 関数実行結果の型をパイプラインの先に伝搬する

PowerShellといえば、型です。

文字列じゃなくて型。ということは、インテリセンスが何を使えるか教えてくれるわけです。インテリセンス使えないと辛いですよね。自作の関数を作ったことがある人は多いでしょう。でも、作った関数の出力結果がパイプラインの先でインテリセンス効かないって経験ありませんか?

今回はどうしてなのか、どうやって解決するのかを説明します。

問題はどういうこと

例えば、[System.String]を返すだけの単純な関数を考えてみます。

https://gist.github.com/guitarrapc/e21247da847f7292a690

ではこの関数を実行した結果を、パイプラインの先に伝搬してみましょう。

https://gist.github.com/guitarrapc/ec58edd62a343cb4efc0

結果は? 自動変数$_$_ピリオドを入力してもインテリセンスがききません。しょぼーん。

image

つまり、さくっと関数を書いても、パイプラインの先にオブジェクトが渡るまで型をインテリセンスでサポートしてくれないわけです。

出力型を明示する

これはC# でCmdletを書いていると気づきません。C# なら返戻値の型を明示するからです。

しかし、PowerShellスクリプトで関数を書いていると逆に返戻値の型を明示することを失念しがちです。そう、返戻値の型を明示してインテリセンスを助けてないから、パイプラインの先で型がわからないのです。

対応方法

高度な関数(でしたっけ? Advanced Functionとか言われたりする奴ですね) の中に答えがあります。

返戻値の型を明示するには、[OutputType()]属性を付ければokです。*1*2

コードで示しましょう。

https://gist.github.com/guitarrapc/c0b85baadce694b10f9c

これで先ほどと同様にパイプラインの先に型が伝搬したかみてみると?

https://gist.github.com/guitarrapc/c2136dc0cb6a15f721bc

自動変数$_の後にピリオド$_を入力した瞬間、うまく型がインテリセンスに表示されましたね。

image

[OutputType()]属性のいけてなさ

意味があったのに余り使われていないのには、知られてないノか欠点が目立ってるからなのか。

いけてないところを見てみます。

返戻値の型がずれても何も警告もエラーも出ない

C# なら、返戻値の型と実際の型がずれることはVSで警告を出して起こることがないですね。

しかし、PowerShell ISEというか、PowerShellのインテリセンスは賢くないのでそんなことしてくれません。当然キャストもしません。

ということは、返戻値の型と[OutputType()]で示した他型がずれることもありえます。

例えばこう。

https://gist.github.com/guitarrapc/670ff96a2a3966613a0d

[OutputType()][OutputType()]を示したにも関わらず、実際の返戻値は[OutputType()]型です。

パイプラインで渡してインテリセンスを見てみると?System.Int32の型がインテリセンスで表示されました。実際の型とずれていますね。

image

nyao | ForEach-Object {$_ | Get-Member}


   TypeName: System.Uri

Name                       MemberType Definition
----                       ---------- ----------
Equals                     Method     bool Equals(System.Object comparand)
GetComponents              Method     string GetComponents(System.UriComponents components, System.UriFormat f...
GetHashCode                Method     int GetHashCode()
GetLeftPart                Method     string GetLeftPart(System.UriPartial part)
GetObjectData              Method     void ISerializable.GetObjectData(System.Runtime.Serialization.Serializat...
GetType                    Method     type GetType()
IsBaseOf                   Method     bool IsBaseOf(uri uri)
IsWellFormedOriginalString Method     bool IsWellFormedOriginalString()
MakeRelative               Method     string MakeRelative(uri toUri)
MakeRelativeUri            Method     uri MakeRelativeUri(uri uri)
ToString                   Method     string ToString()
AbsolutePath               Property   string AbsolutePath {get;}
AbsoluteUri                Property   string AbsoluteUri {get;}
Authority                  Property   string Authority {get;}
DnsSafeHost                Property   string DnsSafeHost {get;}
Fragment                   Property   string Fragment {get;}
Host                       Property   string Host {get;}
HostNameType               Property   System.UriHostNameType HostNameType {get;}
IsAbsoluteUri              Property   bool IsAbsoluteUri {get;}
IsDefaultPort              Property   bool IsDefaultPort {get;}
IsFile                     Property   bool IsFile {get;}
IsLoopback                 Property   bool IsLoopback {get;}
IsUnc                      Property   bool IsUnc {get;}
LocalPath                  Property   string LocalPath {get;}
OriginalString             Property   string OriginalString {get;}
PathAndQuery               Property   string PathAndQuery {get;}
Port                       Property   int Port {get;}
Query                      Property   string Query {get;}
Scheme                     Property   string Scheme {get;}
Segments                   Property   string[] Segments {get;}
UserEscaped                Property   bool UserEscaped {get;}
UserInfo                   Property   string UserInfo {get;}

なんだこれ。厄介としか言いようがありません。警告してくだされ~。

PSCustomObjectの厄介さ

PowerShellを使っていてPSCustomObjectを触ったことがない人は、v2の人ですね? お疲れ様です。

PSCustomObjectは、いろんな意味でPowerShellらしいともいえる、便利だけど特殊な型です。PSCustomObjectを使うと任意のプロパティを含んだオブジェクトがさくっと、低コスト、便利に作れるので多用することが多いでしょう。

しかし、任意のプロパティを自在に含められるため、インテリセンスと相性はさいてーです。サイテー。

たとえば、次のような関数をパイプラインの先で補完してみても?

https://gist.github.com/guitarrapc/cb26670d4c92ed9668bb

当然ですが、piyoプロパティはインテリセンスに出ませんね。

image

まとめ

[OutputType()]大事ですよ? 使ってなかった人はぜひ使ってください。

型大事です。実際。もぅ、yield return用意したりして、yield return以外はyield return必須にしたり、yield returnと検査すればいいのにって思っちゃいますね。

*1:必ずparam()ブロックを使って引数を受けないといけません。paramブロックは空でもいいです。

*2:[CmdletBinding]属性は[OutputType]属性とセットにする必要はありません

PowerShell も Windows Store Apps 同様に Windows.Security.Credentials namespace を使って認証情報を管理できるようにしてみる

以前、P/InvokeしてWindows Credential ManagerでPowerShellの認証情報を格納したり取得する簡単なモジュールを紹介しました。

https://tech.guitarrapc.com/entry/2014/03/13/062713
https://github.com/guitarrapc/WinCredManager

実際これは、valentiaを初めとして、認証を必要とするモジュールで使っています。

もっといい方法ないかなぁと、Windows Store Appsはどうしてるか調べていたところ、Windows.Security.Credentials namespaceを使った方法がスタンダードなようですね。

今回は、PowerShellでWindows.Security.Credentialsを使って認証情報を格納する方法を見てみましょう。

どこに保持されるの?

資格情報マネージャー(Windows Credential Manager) にある、Web資格情報(Web Credential) です。

以前作成したものが対象にしていたのはWindows Credentialsだったので、ここは違いますね。

image

さようなら P/Invoke

けた違いに書きやすくなりました。

例えば、現在設定されている一覧を取得するなら2行で済みます。

https://gist.github.com/guitarrapc/336bfc9301bd4f368cbd

もちろん、追加/削除も簡単です。便利ですね。

モジュールにして使いやすくする

PowerShellの認証型はPSCredentialです。

一方で、Windows.Security.Credentials名前空間ではWindows.Security.Credentialsを利用します。型が違うのでコンバーターとかさくっと作ればokです。

ということで、モジュールです。

https://github.com/guitarrapc/WindowsCredentialVault

image

関数一覧

Configを一応用意していますが、使ってないので除いておきます。

CommandType Name                                Version Source
----------- ----                                ------- ------
Function    ConvertFrom-PasswordCredential      1.0.0   WindowsCredentialVault
Function    ConvertTo-PasswordCredential        1.0.0   WindowsCredentialVault
Function    Get-WindowsCredential               1.0.0   WindowsCredentialVault
Function    Remove-WindowsCredential            1.0.0   WindowsCredentialVault
Function    Set-WindowsCredential               1.0.0   WindowsCredentialVault
Function    Test-WindowsCredential              1.0.0   WindowsCredentialVault

Get/Remove/Set/Testがそろっているので、大体やりたいことはできます。READMEに使い方の説明を書いておいたのでどうぞ。

まとめ

既存の差し替え検討してもいいぐらいには使いやすかったのでいいですね。

NewRelicで特定のアプリケーションプールをプロファイルしないようにする

NewRelicは、PHPやRuby、Javaだけでなく .NETのプロファイリングも優れています。

今回は、ASP.NETのアプリケーションの前段にARRを使ったリバースプロキシを置いた時に、アプリケーション本体のみをプロファイルさせるための方法について説明します。

事前に読んでおくといいもの

  • そもそもNewRelic Agentのコンフィグをどこでやるか把握しましょう

https://docs.newrelic.com/docs/agents/net-agent/installation-configuration/net-agent-configuration

  • .NETでできないことも把握しましょう

https://docs.newrelic.com/docs/agents/net-agent/installation-configuration/known-issues-net

やりたいこと

IIS上に、ASP.NET MVCなアプリをホスティングしているとしましょう。

ここで、前段にARR(Application Request Routing) を利用したリバースプロキシを置いているとします。よくあるnginxを前段において、アプリをバックエンドをIISで組んでいるケースと思ってください。

つまり以下のような構成です。

image

さて、NewRelicにやらせたいことの1つはアプリケーションのモニタリング・監視ですね。この辺がピンとこない方は、@ITでid:kkamegawaさんが簡潔にきっちりまとめられている記事が参考になるでしょう。

https://www.atmarkit.co.jp/ait/articles/1501/19/news114.html

このNewRelicによる監視をもう少し実用面で考えます。

.NET Agent の副作用は凄まじい

さて、このまま .NET Agentを入れて問題ないでしょうか?

NewRelic .NET Agentは、RubyやPHPのAgentと同様にプロファイルします。さて、どこでというと、このケースではIISにdllを突っ込んでです。

結果どうなるかというと、アプリケーションのパフォーマンスが低下します。アプリの構成にもよりますが、30%~50%弱影響を受けるケースもざらにあります。

監視はしたいけど、パフォーマンスをむやみに下げたくないですよね? 特にIIS上で、複数のWebサイトを同居させていている場合は顕著です。

アプリケーション と リバースプロキシではプロファイル制御したい

さらに今回の構成のようなリバースプロキシのサイトでもプロファイルが走ると、アプリケーションのThroughputだけ見たくてもリバースプロキシサイト分が追加されて2倍になります。これは良くない。大変良くない。

リバースプロキシは単純にリダイレクト書けるだけなので、NewRelic .NET Agentを走らせる意味が(ほぼ)皆無です。無駄といえます。

とすると、同じIIS上でプロファイルを走らせるサイトと走らせないサイトが出てきますね? これをどうやるかが本題です。

つまりこれを目指します。

image

NewRelic のプロファイラを制御する

IIS 上の設定

Webサイトごとにアプリケーションプールは分離させよう

IISのWebSiteは、WorkerProcess(w3wp.exe) をアプリケーションプール単位で動作させています。

NewRelic .NET Agentは、WebSite単位でプロファイルをしているのですが、その対象はアプリケーションプールです。Webサイトごとにアプリケーションプールを分離させて置くことは大事です。

アプリケーションプールの実行ユーザーが問題となる

WorkerProcessを何で動かしているかはNewRelic .NET Agentの設定に関して影響します。

ふつーはApplicationPoolIdentityでしょう。しかし、アクセス制御をしたいなど多くのシーンではSpecific Userにしていることが多いでしょう。このSpecific Userの時に困ります。

いずれのパターンでも対応可能ですし後述しますが、自分がなぜそのアプリケーション実行ユーザーにしているかは把握しておくことを推奨します。

newrelic.config は罠

さて、NewRelic Agentは、newrelic.configで制御可能とNewRelic docには書いてあります。セクションとしてはinstrumentation SectionのApplications element (instrumentation)がいかにもそれっぽいでしょう。

https://docs.newrelic.com/docs/agents/net-agent/installation-configuration/net-agent-configuration

image

結論からいうと、これではアプリケーションプール単位での制御ができないケースもあります。

どういうことかというと、ApplicationPoolをApplicationPoolIdentityではなく、ApplicationPoolIdentityで動かしているとNewRelic Agentがアプリケーションプール名を取得できません。なんてこった!

NewRelic agentは、IISRESETでプロファイルが再開された時にログを吐くのですが、そこでApplicationPool名をとれているか確認できます。

This application pool () is not explicitly configured to be

アプリケーションプールが誰で実行されていてもプロファイルを制御する

newrelic.configでアプリケーションプール単位の制御ができないとわかりました。

ではどうするか。そのヒントはここにあります。(何と答えがNewRelic doc上にない)

https://docs.newrelic.com/docs/agents/net-agent/instrumentation/instrumenting-non-iis-apps

image

これがポイントです。

<add key="NewRelic.AgentEnabled" value="true" />

では、IISのWebサイト単位でどうやって制御するかといえば web.config です。(user.config で転換している場合はそっちでok)

https://stackoverflow.com/questions/17800261/configure-new-relic-in-iis-to-show-one-application-per-domain

プロファイルを取りたくないサイトに対して、アプリケーション名を明示して、プロファイルの実行をfalseとすればokです。(プロファイルを取りたい場合は、trueにしましょう。)

<appSettings>
  <add key="NewRelic.AppName" value="my_web_app" />
  <add key="NewRelic.AgentEnabled" value="false" />
</appSettings>

あとは、ASP.NETアプリをデプロイした際にNewRelicのプロファイルはこの web.configに従ってプロファイルを停止します。

まとめ

NewRelic Profilerの制御は大事です。現在NewRelic.configは使わない方向にNewRelicは移行しており、Onlineで大体設定可能になっています。つまりNewRelic.configオワコン。

ASP.NETに限らず、プロファイルの制御はアプリサイドのコンフィグで可能なので web.config.exe.config で制御してください。

特にリバースプロキシを持っているIISの場合は必須ですよ。お気を付けて。

ずっと書こうと思っていながら1か月以上放置していました*1

*1:いつも通り