tech.guitarrapc.cóm

Technical updates

『PowerShell実践ガイドブック』の先行レビューアーを募集します!

刊行される「PowerShell実践ガイドブック」(通称:貝殻本)ですが、編集さんにかけあったところ先行レビューという形で書籍をプレゼントできることになりました!

tech.guitarrapc.com

マイナビBOOKSのPC書籍編集部ブログで、応募フォームが用意されています!

book.mynavi.jp

レビュワー募集終了のお知らせ

締め切りを迎えて、抽選により5名様にレビュワーの連絡と書籍が発送されました。 レビューされるのを心待ちにしています!

お願い

当選された方は、アマゾンやブログ(はてな、note、medium、など場所は問いません)でのレビュー+TwitterなどのSNSでの告知をお願いします。 書籍到着から2週間以内にレビューを公開しご連絡ください、拡散します!

「意気込み」とは、ですが、読みたいんだーと思っていただけるの本当にうれしいです。 その読みたい理由とか教えていただけると嬉しいなぁと思います。

以下、応募サイトの抜粋です!

募集内容

発売に先立って『PowerShell実践ガイドブック』をプレゼントします。当選された方は、レビューをお願いします。Amazon.co.jpのレビューやブログ(はてな、note、mediumなどなど、場所は問いません)でのレビュー+TwitterなどのSNSでの告知をお願いします。 書籍到着から2週間以内にレビューを公開し、ご連絡ください。マイナビブックスや著者などが拡散します。

募集人数

5名

募集締切

2018年5月21日 23:59

応募方法

こちらの申し込みフォームから ・名前(ハンドル) ・メールアドレス ・意気込み ・レビュー場所(の予定) を記入して、ご応募ください。 「意気込み」欄には、PowerShellをどのように使っているか、あるいは、これから学びたいと思っている理由など、本書を読みたい!という(熱い)気持ちを自由にお書きください。

当選発表

当選者にはマイナビ出版からメールをお送りします。送付先をお知らせいただければ、書籍を発送いたします。

お問い合わせ

本件についてのお問い合わせは、books-hen3[at]mynavi.jp宛てにお送りください。 ※上記の[at]のところは@に変更してお送りください。

ご応募をお待ちしています!

「PowerShell実践ガイドブック」という本を書きました!

このたび、PowerShell実践ガイドブックという本を執筆・刊行する運びとなりました。

本日から予約開始、2018/5/30日に発売です。

※ Amazonの紹介文がおかしいのは修正予定です。 修正されました。

Amazonなどのオンライン書店で予約が始まってます。 Kindle版もありますが、まだAmazonでは発売情報が出ていませんのでお待ちください。 5/22 Kindle版も予約開始しています。

また、マイナビ出版でもPDFを販売しています。

book.mynavi.jp

PowerShell Coreを扱った本は、世界では2017年に発売された6.0リリース前の本のみで、国内でも初めてになります。

こんな人におすすめです

  • ふだんのちょっとしたPC作業を自動化をしてみたい方
  • システム管理者としてPowerShell使っていく、使いこなしたい方
  • 開発者からみてPowerShellをどう使うか気になる方

テーマは、PowerShell Core (PowerShell 6.0) をメインに据えた、PowerShellを実際に使っていくときのガイドとなる本です。 この本は過去の「PowerShellを触り始めた直後の自分」がほしかった本でもあり、今の自分が幅広い利用者を想定して書き出せる限界ギリギリを絞りました。 コードパズルに近いマニアックな内容にならず、プログラマーでないとわからないということがないように気を使っています。

Twitterアンケートでも本当に幅広い方がPowerShellを使っていて嬉しい反面、最新の情報や実践を見据え体系的にまとまった情報を得にくいと感じていました。

私自身がPowerShellを学び始めたときに困ったのが「情報がない」です。 PowerShell 6.0になって、Windowsだけでなく、macOSやLinuxでも動かせるようになりました。 とはいえ、PowerShell 6.0をどうやって導入するのか、何に使えるのか、どうやって動かすのか、スクリプトってなに、関数? 高度な関数?実際に使ってみてハマることは?という、基本的なことすらも分かりにくいと感じていました。 PowerShellに関する記事を数多く書いて来ましたが、「本によるまとまった情報の取り込み力」が必要と考えての一冊です。

本書が、そんな悩みを解決できる一助になれば、幸いです。

Windows PowerShell In Action や Windows PowerShell Cookbook との違い

当初案のタイトルは、PowerShell In Action でした。 まさにテーマが In Action「実際に使う」ということだったので被ってることにしょんぼりすることになるとは思いませんでした。

Windows PowerShell イン アクション も Windows PowerShell クックブックも何度か推薦するぐらい素晴らしい良著で私の大好きな本です。

PowerShell実践ガイドブックは、この2冊と明確に立ち位置が違います。

  • PowerShell Core (現時点の最新である PowerShell 6.0) をメインとした内容になっている
  • 体系的な基本機能も網羅しているが、常に実践を意識した内容になっている
  • 実際にPowerShellを使うにあたっての操作や直面する課題を対象にしている

これまで学んできたことを含めて全ページ書き下ろしているので、お気に入りの一冊に加えていただけると嬉しいです。

章構成と立ち読み

大まかな構成は、まずは触ってみる(1章)→コンソール操作と基本機能(2章)→管理操作(3章)→スクリプティングと自動化(4章)→実践とTips(5章)となっています。

本書の前書き「はじめに」のセクションをテキストで出すことを編集者さんに許可もらったので、ブログで一部を公開します。 「はじめに」の全文はマイナビ出版様より立ち読みとして公開予定もあるそうです。

本書の想定する読者

本書は、これからPowerShellを触ってみたい方、システム管理者、開発者の三者を想定しています。 これから触る方には、日常の中で繰り返し手作業で行っていることを自動化する足がかりになるでしょう。 特に、筆者自身が今までコマンドプロンプトやPowerShellをちょっとだけ起動したことはあったけれども「さらに習得したい」と考えたときに手に取りたかったものとして本書を執筆しました。

システム管理に求められる問題領域は、コンピューターシステムの構成や運用に始まり、ネットワーク、コンピューターセキュリティなど多くのリソースに及びます。さらに、クラウド管理、データベース管理、Webシステム管理などが求められることも少なくありません。開発者としても、多くのビルド、デプロイ、Application Programming Interface(API)を通した各種サービス操作を日常的に行います。

両者に共通するのは、多岐にわたる知識と解決です。限られた時間の中で解決するために、多くの操作を自動化する必要に迫られます。操作自体はシンプルな処理の連続であることが多く、コマンド1つ、あるいはパイプラインでつないで、はたまたスクリプトで自動化されます。

PowerShellは自動化に際して大きな力を発揮します。特に構造化データの取り扱いの容易さが大きな力になるでしょう。経験の有無にかかわらず、繰り返し作業やうんざりする作業をPowerShellを使って自動化したい方にとって本書が力になると考えています。

本書で得られること

本書を通して、PowerShellを使うことに対して「知らない」に起因する心理的なハードルの軽減すること、そして、PowerShellの網羅的な内容を知ることができるように気を配りました。本書は詳細機能の説明を無理に詰め込むことはせず、本書を読み進めることによってPowerShell 6.0の概要の把握から、C#コマンドレットの作成の入り口までの導入を目指しています。

本書は5章に分かれています。各章はテーマを持ち、実践的なTipsを交えつつ紹介しています。

第1章では、起動するのも初めて、これからまさに触ろうと思ったときに知りたかった「何ができるか」を平易に伝えることを念頭に置きました。Visual Studio Codeを使ったPowerShellのデバッグにも触れています。他言語の経験者の方やすでに慣れ親しんでいる方からすると、すでに知っていること、普段から行っていることの繰り返しに近いため、第2章から読み進めてもいいでしょう。

第2章では、PowerShellのコンソール操作に触れつつ、しっかりと基礎を学ぶことを目標に置いています。 PowerShellは、操作することを自動化に結び付けやすいため、多くの方にとってはこのコンソール操作をどうするかが知りたいことだと思います。

第3章では、クロスプラットフォームなシステム管理操作がテーマです。操作ログやリモート操作を始めとして、PowerShellでどのようにシステム管理を行うかを主題にしています。

第4章では、スクリプティングを網羅しつつ、理解できるようにしました。スクリプト、関数、クラス構文を始めとした機能の利用から、実際にモジュールを作って公開することまで、手を動かしながら学べます。

第5章では、さらに一歩進んだPowerShellの利用に触れています。実際にPowerShellを使って気付く悩みがちなこと、より実践的なTipsに踏み込んでいます。

PowerShellは画面がテキストだけということもあり、コマンド操作経験がない方がいきなり触るには難しいと考えています。そこで、第1章は、平易な言葉と例を用いて、可能な限りわかりやすい表現を心がけました。第2章以降は、オブジェクトなどの概念を順に追いかけつつ、手を動かしながら理解していく内容になっています。初めてPowerShellを触る場合でも、C#や.NETランタイムの知識や経験は必要ないように配慮して書きましたが、第2章以降ではそれを知っていることが理解を助けるでしょう。

本書で得られないこと

PowerShellは、管理や自動化といったタスクを達成しようと思ったときに使うであろうツールです。本書が目指すのは、ツールの扱い方であり使い方ではありません。したがって、標準だけでも500を越えるコマンドの1つひとつの説明はしないので、どんなときにどのような管理をするのかというユースケース、Acrive Directoryのアカウントの操作方法などは本書で説明しません。

また、本書をもってプログラマーになることは目的としていません。スクリプト環境としてVisualStudio Code(以降、VS Code)を使って説明しますが、PowerShell ISEやVS Codeの使い方やスニペット、GUIアプリケーションの作成などは触れません。

線引き

「PowerShellは何でもできるわけではない」といってしまうのは簡単です。やりたいことを探していて、要件をまったく満たしておらず使えないということもあります。コマンド1つで実行できて極めて便利なこともあるでしょう。しかし、可能ではあるものの、PowerShellで行うには複雑になってしまって難しいこともあります。

本書では、こういったPowerShellだけでは扱いにくいことをどのように扱うか、あなた自身が学んでいけるようにガイドを示します。

Windows PowerShell と PowerShell Core

2018年現在、PowerShellには、2006年に発表されたWindows PowerShellと2018年に正式版を迎えたPowerShell Coreが存在しています。

PowerShell 6.0は、PowerShell Coreの最新リリースバージョンです。Windows PowerShellのコードを元に.NET Coreをベースとしてさまざまな改善が図られており、Windows PowerShellを置き換えることを視野に開発されています。パフォーマンスや使い勝手の大幅な改善を目的に後方互換性をなくした機能もあり、.NET Frameworkには存在するものの.NET Coreにない機能は削られてもいます。

本書はPowerShell Coreの最新リリースである「PowerShell 6.0」を前提として紹介していますが、Windows PowerShellからみた変更点にも極力触れています。Windows PowerShellについて知識をお持ちの方は、PowerShell Coreでどのように変化したのかにも注目してください。

AzureAD ログインと Docker for Windows Shared Drive によるボリュームマウント

Docker は普段 macOS で使っているのですが、そういえば Docker for Windows がGAされて久しいです。ふつうに Linux コンテナが動かせるのは WSL とは違うコンテナとしての良さに満ちてていいものです。

docker run -it centos /bin/bash

とはいえ Docker for Windows は、触るたびにHyper-V をはじめとしていくつかの特徴的な挙動で難しく感じます。

さて、今回は AzureAD でログインしている Windows において、Docker の Shared Drive が上手く使えない症状に遭遇したので、解消方法をメモしておきます。

見つけるまで結構悩んでしまったので、もしほかの方が同様の症状にあった時にうまくいくヒントとなることを祈ります。

更新

2020年6月 時点で、最新 Docker Desktop for Windows にて修正されています。やったね!

github.com

目次

概要

Docker はホストのドライブをコンテナで共有する機能があります。Docker for Windows の場合は、SMB (TCP445) を使います。(SMB/CIFS)

これにより、ホストのファイル更新をコンテナと共有が可能だったり、ログ処理したりが可能になります

環境

Docker for Windows 17.09.1-ce-win42

Share Drive 方法

Docker for Windows > Settings > Shared Drives に行きます。ここで共有したいドライブを有効にします。

  • シェアしていない状態

  • シェアした状態

うまくいきましたか? やりましたね!完璧です。そのまま docker run -v を楽しみましょう。

docker run --rm -v c:/Users:/data alpine ls /data

単純にこれで済む場合は、local User でログインしている Administrators 権限を持っているケースか、AD でログインしていて適切に権限が付与されているケースが経験あります。

注意点

Network Connection Profile

もし自ネットワークプロファイルが private でない場合は、Private にしておきます。ただ、Hyper-V のプロファイルはパブリックネットワークで構いません。

自宅や職場で guest or public netrowk は、相応の理由がない場合はちょっと困ったことを引き起こしやすいです。(Firewall ルールからしてセキュアに守られる)

Hyper-V ではない、自分のイーサネットやWifi のネットワークコネクションのプロファイルを プライベートネットワークに変更する場合は、PowerShell 上で以下でさくっとできます。*1

# 対象のId を確認しましょう。
Get-NetConnectionProfile
$id = "対象のConnectionIdをどうぞ"
Set-NetConnectionProfile -NetworkCategory Private -InterfaceIndex $id

うまくホストのネットワークが プライベートになりましたか?

Firewall

Firewall の警告が出た場合はこのケースがあり得ます。

Firewall で弾かれる場合は TCP 445 のコンテナip 10.0.75.1からのアクセスになっている DockerSmbMount のエントリが許可になっていますか?

このコンテナIP 10.0.75.1 は、Docker for Windows > Settings > Network にあるデフォルトの値です。自分でIPを設定されている場合はよきように解釈してください

もしアンチマルウェアを使っていらしゃる場合は、そのソフトのブロックを解除してあげるか White list に追加するといいでしょう。

これで共有ができましたか?

AzureAD の場合

さて、本題です。

AzureAD でログインしたUserにおいては、そのままでは Shared Drives が失敗します。*2Driveのシェアを実行 > 認証を入力 > 数秒で Share がはずれる。という状態です。

ログを見ると原因がわかります。

[11:19:49.606][NamedPipeClient][Info   ] Sending Version()...
[11:19:49.607][NamedPipeClient][Info   ] Received response for Version
[11:19:49.607][NamedPipeServer][Info   ] Version()
[11:19:49.608][NamedPipeClient][Info   ] Sending Mount(C, AzureAD\UserName:**********, Docker.Core.Settings)...
[11:19:49.607][NamedPipeServer][Info   ] Version done in 00:00:00.
[11:19:49.609][NamedPipeServer][Info   ] Mount(C, AzureAD\UserName:**********, Docker.Core.Settings)
[11:19:49.629][SambaShare     ][Info   ] Mount C
[11:19:49.679][Cmd            ][Info   ] この共有リソースは存在しません。
[11:19:49.679][Cmd            ][Info   ] NET HELPMSG 2310 と入力すると、より詳しい説明が得られます。
[11:19:49.681][SambaShare     ][Info   ] "C" is not shared
[11:19:49.681][SambaShare     ][Info   ] Creating share "C:\" as "C" with Full Control to "AzureAD\UserName"
[11:19:49.740][Cmd            ][Info   ] C が共有されました。
[11:19:49.777][Cmd            ][Info   ] 共有名             C
[11:19:49.777][Cmd            ][Info   ] パス               C:\
[11:19:49.777][Cmd            ][Info   ] 注釈
[11:19:49.777][Cmd            ][Info   ] 最大ユーザー数     制限なし
[11:19:49.777][Cmd            ][Info   ] ユーザー
[11:19:49.777][Cmd            ][Info   ] キャッシュ         キャッシュは無効
[11:19:49.778][Cmd            ][Info   ] アクセス許可       AzureAD\UserName, FULL
[11:19:49.778][Cmd            ][Info   ] コマンドは正常に終了しました。
[11:19:49.780][SambaShare     ][Info   ] "C" is shared
[11:19:50.700][SambaShare     ][Info   ] Username: UserName
[11:19:50.700][SambaShare     ][Info   ] Host IP: 10.0.75.1
[11:19:50.700][SambaShare     ][Info   ] Cifs options: noperm,iocharset=utf8,nobrl,mfsymlinks,vers=3.02,domain=AzureAD
---- 中略 ----
[11:19:52.190][SambaShare     ][Error  ] Unable to mount C drive: 10.0.75.1 (10.0.75.1:445) open
umount: can't unmount /c: No such file or directory
umount: can't unmount /C: No such file or directory
rmdir: '/c': No such file or directory
rmdir: '/C': No such file or directory
mount error(13): Permission denied
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)
mount: mounting //10.0.75.1/C on /c failed: Invalid argument

[11:19:52.192][SambaShare     ][Info   ] Removing share C
[11:19:52.244][SambaShare     ][Info   ] Mount C
[11:19:52.273][Cmd            ][Info   ] この共有リソースは存在しません。
[11:19:52.273][Cmd            ][Info   ] NET HELPMSG 2310 と入力すると、より詳しい説明が得られます。
[11:19:52.275][SambaShare     ][Info   ] "C" is not shared
[11:19:52.275][SambaShare     ][Info   ] Creating share "C:\" as "C" with Full Control to "UserName"
[11:19:52.300][ApiProxy       ][Info   ] proxy >> GET /images/sha256:9e4f13a0901e7cdc0c16babf4ebec822828ecae42947c79b69c51e2e22e0470e/json
[11:19:52.300][ApiProxy       ][Info   ] Dial Hyper-V socket 45ef270d-7ba5-4a0c-9633-f0d79d3b0f30:23a432c2-537a-4291-bcb5-d62504644739
[11:19:52.300][ApiProxy       ][Info   ] Successfully dialed Hyper-V socket 45ef270d-7ba5-4a0c-9633-f0d79d3b0f30:23a432c2-537a-4291-bcb5-d62504644739
[11:19:52.302][ApiProxy       ][Info   ] proxy << GET /images/sha256:9e4f13a0901e7cdc0c16babf4ebec822828ecae42947c79b69c51e2e22e0470e/json
[11:19:52.309][Cmd            ][Info   ] システム エラー 1332 が発生しました。
[11:19:52.309][Cmd            ][Info   ] アカウント名とセキュリティ ID の間のマッピングは実行されませんでした。
[11:19:52.311][SambaShare     ][Error  ] Failed to create share "C:\" as "C" with Full Control to "UserName" with code: 2
[11:19:52.341][Cmd            ][Info   ] この共有リソースは存在しません。
[11:19:52.343][NamedPipeClient][Info   ] Received response for Mount
[11:19:52.341][Cmd            ][Info   ] NET HELPMSG 2310 と入力すると、より詳しい説明が得られます。
[11:19:52.343][SambaShare     ][Info   ] "C" is not shared
[11:19:52.343][NamedPipeServer][Info   ] Mount done in 00:00:02.7347295.
[11:19:52.449][ApiProxy       ][Info   ] proxy >> GET /images/sha256:9e4f13a0901e7cdc0c16babf4ebec822828ecae42947c79b69c51e2e22e0470e/json
[11:19:52.449][ApiProxy       ][Info   ] Dial Hyper-V socket 45ef270d-7ba5-4a0c-9633-f0d79d3b0f30:23a432c2-537a-4291-bcb5-d62504644739
[11:19:52.449][ApiProxy       ][Info   ] Successfully dialed Hyper-V socket 45ef270d-7ba5-4a0c-9633-f0d79d3b0f30:23a432c2-537a-4291-bcb5-d62504644739
[11:19:52.450][ApiProxy       ][Info   ] proxy << GET /images/sha256:9e4f13a0901e7cdc0c16babf4ebec822828ecae42947c79b69c51e2e22e0470e/json

原因は アカウント名とセキュリティ ID の間のマッピングは実行されませんでした。 がまさにそれです。要は no mapping between security id ですが、Windows においてこれは Security上の アクセスIdが一致していないことを示します。一度 C: とドライブ共有できているにもかかわらず、なのがなかなか厄介です。 何がセキュリティIdと違うのかというと、 認証時に AzureAD ログインしているときに入れないといけない AzureAD\ が原因です。では、ということで AzureAD\ を除いてユーザー名のみにすると、そんなIdはないので共有自体が失敗します。セキュリティIdが一致していない、厄介。AzureAD で Windows ログインすることはかなり快適なのですが、こういう問題はたびたびあります。

問題がわかれば対処方法が思いつきます。

対処方法は、AzureAD\UserName の UserName だけの ローカルユーザーを作りましょう。ユーザー作成後、Administrators を与えます。これで、AzureAD\UserName で共有後に、UserName も見つかるので正常にドライブ共有が可能になります。

PowerShell ならこうです。

# ここでポップアップがでるので、追加したいユーザーのUsername と Password をいれます。
$credential = Get-Credential
New-LocalUser -AccountNeverExpires -PasswordNeverExpires -UserMayNotChangePassword -Name $credential.UserName -Password $credential.Password
Add-LocalGroupMember -Group Administrators -Member $credential.UserName

もしGUI でやりたい場合、UserName をご自身のユーザー名に置き換えてください

参考

*1:管理者権限がいります。ADならDomainAuthenticated なので問題ないはずですが Firewall ルールが問題になる可能性はあります

*2:私は5度やって5度失敗したのでそういうものでしょう

PowerShell の配列表現と生成処理時間

面白い記事があります。

blog.shibata.tech

PowerShell において配列生成は 言語仕様上にある通りカンマ演算子 , によって表現されるものであり、ASTでも満たされている。しかし、そこに言及がなく ()$()@() で生成しているような表現を見かけるけど実は違うんだよ。ということが説明されています。とはいえ、@() で囲むことに意味はあるので注意なのですが。

tech.guitarrapc.com

さて、結果としてみるとどの表現でも配列が生成されます。が、AST を見てもそれぞれ違うことから「表現可能な方法が複数ある場合にどれを使うのがいいのか」を考えてみましょう。

目次

PowerShell の構文木 AST を見る

もし PowerShell の AST が見たい場合は、ShowPSAst でモジュールを入れておくといいでしょう。

# 今のユーザーにのみ導入する
Install-Module ShowPSAst -Scope CurrentUser

github.com

配列の生成

今回は、ベンチマークでは単純に配列評価の時間を測定したいため、配列の生成は事前に文字列を起こしておきましょう。

以下のようにすると配列が生成できます。

gist.github.com

これで2種類の配列文字列が取得できたので準備ok です。

1,2,3,4,5,....

1
,2
,3
,4
,5
....

ベンチマークの測定対象

それではベンチマークを測ってみましょう。

測定対象として選んだのは記事にあった表現とその派生です。

  1. ArrayLiteralAst : カンマ演算子, による配列の生成 = 最速の予定
    • 1,2,3
  2. ParenExpressionAst + ArrayLiteralAst : () で カンマ演算子 , による配列の生成のラップ
    • (1,2,3)
  3. ArrayExpressionAst + ArrayLiteralAst : @() でカンマ演算子 , による配列の生成のラップ
    • @(1,2,3)
  4. SubExpressionAst + ArrayLiteralAst : $() でカンマ演算子 , による配列の生成のラップ
    • $(1,2,3)
  5. (ArrayExpression + ArrayLiteralAst) * PipelineOutput : @() でカンマ演算子 , による配列の生成のラップした結果をパイプラインでマップ
    • @(1,2,3) | % {$_}
  6. Constraints + ArrayLiteralAst : @() で生成した中身を前置カンマにしました
@(
1
,2
,3)

ベンチマーク結果

1000回実行したっけの平均/最大/最小を見ます。単位は ms です。

PowerShell でのベンチマークは、今回簡易に Measure-Command を用いました。

Code Target Count Average Maximum Minimum
1,2,3 ArrayLiteralAst 1000 6.72703 76.897 1.109
(1,2,3) ParenExpressionAst + ArrayLiteralAst 1000 6.70472 77.452 1.0702
@(1,2,3) ArrayExpressionAst + ArrayLiteralAst 1000 7.020254 185.7868 1.0828
$(1,2,3) SubExpressionAst + ArrayLiteralAst 1000 7.59060 85.0647 1.4674
前述参照 (ArrayExpression + ArrayLiteralAst) * PipelineOutput 1000 75.666 234.0299 52.0301
前述参照 Constraints + ArrayLiteralAst 1000 8.67313 195.6095 6.1331

いかがでしょうか? 予想通りですか?

ArrayLiteralAst

さすがに Average / Maximum / Minimum のいずれにおいても安定して最速です。

ArrayLiteralAst だけの場合、次のAST評価となっています。

# AST  : {1,2,3} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ArrayLiteralAst] > ConstantExpressionAst(s)

ParenExpressionAst + ArrayLiteralAst

こちらも ArrayLiteralAst のみと比較して、ParenExpressionAst + ArrayLiteralAst では、() で括った分一段要素が増えます。一方で実行速度にはほとんど差がなく、() は評価の軽い要素であるのが明確です。

# AST  : {(1,2,3)} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ParenExpressionAst] > PipelineAst > CommandExpressionAst > [ArrayLiteralAst] > ConstantExpressionAst(s)

ArrayExpressionAst + ArrayLiteralAst

Maximum 測定誤差がでたと考えられます。次のAST評価となっています。 ArrayExpressionAst + StatementBlock + CommandExpressionAst が増えていることからもそこそこ評価が増えてきました。が誤差レベルですね。

# AST  : {@(1,2,3)} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ArrayExpressionAst] > StatementBlockAst > PipelineAst > CommandExpressionAst > ArrayLiteralAst > ConstantExpressionAst(s)

SubExpressionAst + ArrayLiteralAst

こちらは、Minimum が少し大きいですが同様に誤差でしょう。

部分式は多用するのですが、AST評価を見ても [SubExpressionAst] > StatementBlockAst > PipelineAst > CommandExpressionAst となっており、ArrayExpressionAst とだいたい同様ですね。こちらも気にしなくてよさそうです。

# AST  : {$(1,2,3)} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [SubExpressionAst] > StatementBlockAst > PipelineAst > CommandExpressionAst > ArrayLiteralAst > ConstantExpressionAst(s)

(ArrayExpression + ArrayLiteralAst) * PipelineOutput

原因は明らかで パイプラインです。ASTを見ても明らかに要素数が多くなることが分かります。パイプラインほんと重いんですよね。配列を生成するためにこの利用は避けましょう。

# AST  : {@(1,2,3) | % {$_}} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ArrayExpressionAst] > StatementBlockAst > PipelineAst > CommandExpressionAst > ArrayLiteralAst > ConstantExpressionAst(s)
#                                                             | > CommandAst 
#                                                                         | > StringConstantExpressionAst
#                                                                         | > ScriptBlockExpressionAst > ScriptBlockAst > NamedBlockAst > PipelineAst > CommandExpressionAst > VariableExpressionAst

Constraints + ArrayLiteralAst

最初の要素 1 のみ 速やかに ConstantExpressionAst として評価されています。しかし後続は前置のカンマによってシングル要素の配列 とAST評価されてしまいArrayLiteralAst とついています。AST評価を見てみると明らかですね。

# AST  : {@(
# 1
# ,2
# ,3)
# } | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ArrayExpressionAst] > StatementBlockAst > PipelineAst > CommandExpressionAst > [ConstantExpressionAst]
#                                                                                                                    | > PipelineAst(s) > CommandExpressionAst > [ArrayLiteralAst] > ConstantExpressionAst
#                                                                                                                    | > PipelineAst(s) > CommandExpressionAst > [ArrayLiteralAst] > ConstantExpressionAst

まとめ

特に制約がない時に書くなら、すなおに , でくくるのみにするか () で括るのが良さそうです。

$a = 1,2,3
$b = (1,2,3)

String Interporation のような文字列埋め込みに使う 表現も悪くはなさそうです。

$c = "$(1,2,3)" // "1 2 3" となる

良く紹介される形も明らかな齟齬はなさそうです。

$d = @(1,2,3)

ただしパイプライン、お前はだめだ。

$e = @(1,2,3) | % {$_}

ベンチマークコード全体

コードを置いておきます。参考になれば幸いです。

https://gist.github.com/guitarrapc/8a89dc9438673871a71649ab8315e0e8

PowerShell のAPIデザインガイドライン

この記事は、PowerShell Advent Calendar 2017 5日目の記事です。

qiita.com

昨日は @atworks さんの PSRemoting を用いたリモートプロセス実行でした。 qiita.com

3日目の前回 PowerShell のコーディングスタイルについて触れました。

tech.guitarrapc.com

次は、残りのAPI デザインを見てみましょう。

※ 2回に分けて書きましたが個人的には個人/チームが書きやすいようにすればいい話だと思っています。しかし、「曖昧だった基本的な指針がわからず困ってた方」にとって、ベストプラクティスはいい材料となると思います。PowerShell コミュニティは結構活発なので、コミュニティの中で皆様がよい PowerShell 生活を送られることを祈っています。

目次

C#のデザインガイド

Required Development Guidelines

https://msdn.microsoft.com/en-us/library/dd878238.aspx

Design Guidelines と Coding Guidelines がありますが、Design Guidelinesのみ触れます。

Design Guidelines Use Only Approved Verbs (RD01)

  • PowerShell の Cmdlet 規則である Verb-Nown 形式で公開する時に、あらかじめ定義されてある 動詞(Verb) を用いましょう
  • 動詞は用途ごとにクラス分離されているので、適切なものを使うといいです
    • VerbsCommon
    • VerbsCommunications
    • VerbsData
    • VerbsDiagnostic
    • T:System.Management.Automation.VerbsLifeCycle
    • VerbsSecurity
    • VerbsOther
  • どの動詞をいつ使うかのガイドラインも公開されています

(Design Guidelines) Cmdlet Names: Characters that cannot be Used (RD02)

  • Cmdlet には用いることができない特殊文字があります。その一覧を示しています
  • 私は基本的にアルファベットのみ用いるようにすることで沿えるので単純にそう捉えています
  • Parameters Names that cannot be Used (RD03)
  • PowerShell Cmdlet のパラメータには予約語があります。それを避けるようにしましょう
  • Confirm, Debug, ErrorAction, ErrorVariable, OutBuffer, OutVariable, WarningAction, WarningVariable, WhatIf, UseTransaction, and Verbose

(Design Guidelines) Support Confirmation Requests (RD04)

  • もしシステム変更を伴う操作を提供する場合は、PowerShell が持っている 確認機構を用いることをさしています
    • いわゆる ShouldProcess を指しています

(Design Guidelines) Support Force Parameter for Interactive Sessions (RD05)

  • 対話的実行を提供する場合に、Force パラメータは提供しましょう
  • これは PowerShell が自動化を念頭に置かれた言語なため、対話実行でそれを妨害することを防ぐためです
    • 以下のような操作が特に注意です
    • Prompt
    • PSHostUserInterface.PromptForChoice
    • IHostUISupportsMultipleChoiceSelection.PromptForChoice
    • Overload:System.Management.Automation.Host.PSHostUserInterface.PromptForCredential
    • ReadLine
    • ReadLineAsSecureString

(Design Guidelines) Document Output Objects (RD06)

  • 出力の記述です
  • C# のドキュメントXML で説明を公開するといいです

Strongly Encouraged Development Guidelines

次は強く要請するガイドラインです。

https://msdn.microsoft.com/en-us/library/dd878270.aspx

Design Guidelines と Coding Guidelines がありますが、Design Guidelinesのみ触れます。

(Design Guidelines) Use a Specific Noun for a Cmdlet Name (SD01)

Server のような汎用的な言葉ではなく、操作対象を明示した Process のような命名が好まれます。

(Design Guidelines) Use Pascal Case for Cmdlet Names (SD02)

  • Cmdlet 名は、PascalCase で表現しましょう。Clear-ItemProperty のほうが clear-itemProperty より好ましいです

(Design Guidelines) Parameter Design Guidelines (SD03)

  • 標準的なパラメータ名が公開されているのでここにあるものはそれを利用しましょう
    • たとえば、名前ならName、出力なら Output といった具合です
    • ただ、処理によっては ServiceName のようなより明示的な名前も提供したい場合があるでしょう。その場合は、パラメータ用プロパティにAlias属性 を用いることで [Alias("ServiceName")]のように表現できます
  • 単一要素を受けるパラメータには単数名を用いて表現しましょう
    • もし-es のような複数名を用いる場合は、そのbパラメータがいつでも複数要素を受け入れる場合にしましょう
  • パラメータはPascalCaseで。C# であれば Property を用いるので、C# デザインガイドと同じで違和感はないかと思います
    • errorAction や erroraction より、ErrorAction が好ましいです
  • パラメータの組み合わせで操作が変わる Cmdlet を提供する場合2つの方法があります
    • enum を用いて、enumごとに操作を分岐し パラメータを処理する方法
    • ValudateSet属性.aspx) を用いて、パラメータの入力を制約する方法
    • 私の経験上、複数の操作を提供する Cmdlet は作りたくなります。パラメータ1つだけが特定のパラメータ時に用いないようなの「単純な組み合わせ」であれば、ValidateSet が楽でしょう。が、複数の パラメータの組み合わせを ParameterSet で表現するのはおススメしません。Cmdlet を分離することを検討するといいでしょう
  • パラメータ名にはStandard Type を用いましょう
    • あらかじめ、どんな用途(Activity) にどんなパラメータが期待されるのかリストされています
    • Append など利用者が直感的に利用しやすい API デザインとして一貫性を保つため、該当するものを利用するといいでしょう
  • 強く型付けされた .NET Framework 型を利用する
    • Object を利用するということは、型に対する意識が強いということです
    • .NET Framework の型を意識して利用すると適切な型が入ることがほしょうされるため、よいでしょう
    • たとえば、URI には Uri 型を用いれば、String 型が入ってこないことを保証できます
  • パラメータの型に首尾一貫性をもたせる
    • たとえば、Process パラメータというのにInt16 型を当てた場合、他のCmdlet の Process パラメータで Uint16 を用いるのは避けましょう
    • 利用者の直感に反するので触り心地に大きく影響します
  • true/false をとるパラメータは避けて、Switch Parameter を用いましょう
  • Switch Parameter は、もし利用していれば true、なければ false とみなします
  • もし 3値 (true, false, Unspecified) が必要な場合は、Nullable<bool> が適切でしょう
    • 個人的に、Unspecified と null を合わせるのが適切なのかは一考の余地があります
  • 可能であれば、パラメータに配列を許容しましょう
    • 例えば Get-Process は Name に String配列を許容します
    • 利用者の使い心地として、複数回 Cmdlet を実行するより、1回で済む方がうれしいことは多いでしょう
  • PassThru パラメータのサポートを検討しましょう
    • Stop-Process のような値を返さない Cmdlet (Void型) であっても、時に結果オブジェクトが必要です
    • こういった場合に、PassThru パラメータを与えることで、結果オブジェクトを返すオプションを提供しましょう
    • Add, Set, New といった Verb の Cmdlet はサポートしているものが多いです
  • ParameterSet のサポート
    • Cmdlet は1つの目的のために作ります
    • が、時に1つの操作を複数の表現で呼べることがあるでしょう。つまり、パラメータの組み合わせということです
    • このパラメータの組み合わせの表現に、ParameterSet を用いることが多いです
    • ParameterSet を用いる場合、DefaultParameterSetCmdlet 属性に指定しましょう

(Design Guidelines) Provide Feedback to the User (SD04)

ユーザーは実行中ただ待つのは苦痛です。実行に対して何かしらのフィードバックを返しましょう。

  • WriteWarning, WriteVerbose, WriteDebug メソッドのサポート

    • もし意図しない結果が起こった場合は、WriteWarning メソッドで結果をユーザーに伝えましょう
    • もしユーザーがさらなる詳細情報を求める場合、WriteVerbose で結果を返しましょう。例えば、実行シナリオが意図した状態になっているかを伝えることもいいでしょう
    • 開発者がプロダクトサポートのために必要とする情報は、WriteDebug メソッドで返すといいでしょう
  • 長時間実行時の WriteProgress サポート

    • 長時間実行する場合、進捗を WriteProgress メソッドで表示するといいでしょう
  • Host Interface を用いた対話実行

    • 時に ShouldProcess 以外に、Host を通してユーザーとやり取りをする必要に迫られます。そんなときに Host プロパティを用いましょう
    • たとえば、PromptForChoiceWriteLine/ReadLine などです
    • もしCmdlet が GUI を生成しないなら、Out-GridView Cmdlet の利用も検討できます
    • また Cmdlet は、Console API は利用すべきではありません
  • Cmdlet ヘルプファイルの生成

    • Help.xml ファイルで、Cmdlet のヘルプを伝えることができます

(Design Guidelines) Advisory Development Guidelines

アドバイスとしてのガイドラインです。

https://msdn.microsoft.com/en-us/library/dd878291.aspx

Design Guidelines と Coding Guidelines がありますが、Design Guidelinesのみ触れます。

適用時は、Code Guideline も参考にしてください。

(Design Guidelines) Support an InputObject Parameter (AD01)

  • 特定の操作で良く用いられる名前があります。InputObject です
  • パイプラインからの入力をサポートしてプロセッシングするパラメータ名によく用いられ、.NET Framework のオブジェクトを取り扱います

(Design Guidelines) Support the Force Parameter (AD02)

  • Force パラメータを用いたユーザーの権限処理や対話を操作できるようにしましょう
  • Remove-Item Cmdlet の場合、通常は readonlyファイルを消せません。しかしForce パラメータを用いることで消すことができます
    • しかし、もしユーザーがそもそもそのファイルにアクセスする権限がない場合、Force をつけても何ら変わらず「失敗」します

(Design Guidelines) Handle Credentials Through Windows PowerShell (AD03)

  • Credential パラメータをサポートし魔装。このパラメータは PSCredential型を受け認証を処理することが期待されます
  • このサポートにより、ユーザーに対して自動的にポップアップを表示し、ユーザー名やパスワード入力ができるようになります
  • Credential パラメータには、Credential属性をあてます

Support Encoding Parameters (AD04)

  • テキストやバイナリを扱うときは、Encoding パラメータをサポートします

Test Cmdlets Should Return a Boolean (AD05)

  • Test- とつく Cmdlet は Boolean を返すことが期待されます

PowerShell のデザインガイド

実は、コーディングスタイルに含まれてしまっている部分が強いので、API デザインとしては存在しません。

ただし、Best Practice が存在します。

github.com

一度目を通してみると面白いのではないでしょうか?

  • Naming Conventions
  • Building Reusable Tools
  • Output and Formatting
  • Error Handling
  • Performance
  • Security
  • Language, Interop and .Net
  • Metadata, Versioning, and Packaging

ざくっと上げます。PURE とあるものは、議論の余地があるため記載しません。

Building Reusable Tools

再利用性に注目しています。

TOOL-01 Decide whether you're coding a 'tool' or a 'controller' script

  • 自分がツールを作ろうとしているのか、ツールの操作を作ろうとしているのか意識しましょう
    • なにかをするためのツールとして書かれている場合、re-usable でしょう
    • ツールをビジネスロジックに合わせて「操作」するために書かれている場合、re-usable ではないと考えられます

TOOL-02 Make your code modular

  • 処理を、関数にすることで、re-usable になります

TOOL-03 Make tools as re-usable as possible

  • 入力をパラメータで受け取り、パイプラインに出力する
  • この仕組みは re-usable さが最大限高まります

TOOL-04 Use PowerShell standard cmdlet naming

  • PowerShell の標準のネーミングをしましょう
  • Verb-Noun大事。Get-Verb Cmdlet で標準の Verb 一覧が見れます

TOOL-05 Use PowerShell standard parameter naming

  • 標準のパラメータ名を用いましょう

TOOL-06 Tools should output raw data

  • ツールの場合、Cmdlet の処理中に、データをなるべく触らず生で出力することをコミュニティとしては期待することが多いです
  • もし出力データを操作する場合でも、最小限にとどめましょう。そうすることで、多くのシーンで re-usable になります

TOOL-07 Controllers should typically output formatted data

  • 操作する場合、re-usable は主眼ではないので適切にわかるデータにフォーマットして返しましょう

WAST-01 Don't re-invent the wheel

  • 車輪の再発明だめ
  • 下の例は、Test-Connection $computername -Quiet で表現できます
function Ping-Computer ($computername) {
    $ping = Get-WmiObject Win32_PingStatus -filter "Address='$computername'"
    if ($ping.StatusCode -eq 0) {
        return $true
    } else {
        return $false
    }
}

WAST-02 Report bugs to Microsoft

  • バグは共有しよう

Output and Formatting

出力に関してです。

Don't use Write-Host unless you really mean it

  • Write-Host だめ。良く言われますね。Host にしか出力しないので、「見せるためだけ」「フォーマットするだけ」に利用しましょう
  • 特に Show Verb を使っていたり、Format Verb を使っている関数を書いた時にしか、使わないぐらいがいいです
  • なるべく他のWrite-* Cmdlet の利用を検討してください

Use Write-Progress to give progress information to someone running your script

  • ユーザーに何かしら進捗を示すとき Write-Progress が最適です
  • ただし、パイプライン上のなんでも流せばいいというものではありません。伝えたいことにしぼりましょう

Use Write-Debug to give information to someone maintaining your script

  • スクリプトのメンテナンスをする人に向けて、Write-Debug でメッセージを送ってください
  • $DebugPreference = "Continue" とすることで、Breakpoint で止まらず結果をみることもできます

Use CmdletBinding if you are using output streams

  • [CmdletBinding()] を使うだけで、出力ストリームを操作する -Verbose などが利用できるようになります

Use Format Files for your custom objects

  • カスタム型を使う場合は、modulename.format.ps1xml を使ってフォーマットを検討してください

Only output one "kind" of thing at a time

  • 1つの関数で、複数の型を返すことを避けてください
  • [OutputType()] で伝える型とのずれが生じるのは相当なコストをユーザーに強いることになります

Two important exceptions to the single-type rule

  • もし内部関数の場合は、複数の型を返すのはありです
    • $user, $group, $org = Get-UserGroupOrg のように分けて受け取れます
  • もし複数の型を返す場合、個別に Out-Default に包んで返すことでフォーマットが混在することを避けられます

Error Handling

エラー処理です。

ERR-01 Use -ErrorAction Stop when calling cmdlets

  • Cmdlet の呼び出し時は、-ErrorAction Stop をつけてエラー時に捕まえましょう

ERR-02 Use $ErrorActionPreference='Stop' or 'Continue' when calling non-cmdlets

  • Cmdlet ではない場合、呼び出し前に $ErrorActionPreference='Stop' を実行し、呼び出し後に$ErrorActionPreference='Continue' に戻しましょう
  • 特に自動化時に適切にエラーで止めることは重要です

ERR-03 Avoid using flags to handle errors

  • フラグで失敗制御はしないでください
try {
    $continue = $true
    Do-Something -ErrorAction Stop
} catch {
    $continue = $false
}

if ($continue) {
    Do-This
    Set-That
    Get-Those
}
  • try, catch で制御しましょう
    Do-Something -ErrorAction Stop
    Do-This
    Set-That
    Get-Those
} catch {
    Handle-Error
}

ERR-04 Avoid using $?

  • $? の利用は避けましょう
  • これはエラーが前回のコマンドで発生したか示すものではなく、前回のコマンドが成功したかみるだけです。この結果に関しては、ほぼ意味がないでしょう

ERR-05 Avoid testing for a null variable as an error condition

  • null チェックを全部いれるとかやめましょう

ERR-06 Copy $Error[0] to your own variable

  • 直前のエラーが $Error[0] に収められています。catch 句の $_ も同様です
  • ただ、次のエラーですぐに上書きされるので必要なら変数にいれてください
    • $Error 配列に過去のものは入っています

Performance

PERF-01 If performance matters, test it

  • PowerShell のパフォーマンスは、妙なくせだらけです
  • パフォーマンスかな、とおもったらテストしましょう
  • たとえば、以下の例なら2つ目が早いです
[void]Do-Something
Do-Something | Out-Null
  • いくつか方法が思いつく場合、計測しましょう

PERF-02 Consider trade-offs between performance and readability

  • パフォーマンスと読みやすさはトレードオフな場合があることを考慮してください
  • 例えば、式で表現とパイプラインで表現でも変わります
$content = Get-Content file.txt

ForEach ($line in $content) {
  Do-Something -input $line
}
Get-Content file.txt |
ForEach-Object -Process {
  Do-Something -input $\_
}

あるいは、.NET Framework を直接触ることでも変わります。

$sr = New-Object -Type System.IO.StreamReader -Arg file.txt

while ($sr.Peek() -ge 0) {
   $line = $sr.ReadLine()
   Do-Something -input $line
}

さらにこんな書き方もあるでしょう。

$handle = Open-TextFile file.txt

while (-not Test-TextFile -handle $handle) {
    Do-Something -input (Read-TextFile -handle $handle)
}
  • どれがいいかといえば、なるべく PowerShell に沿った書き方が読みやすいでしょう。が、基本的には .NET Framework のラッパーにすぎません
  • いくつものパターンがある中から、パフォーマンスとご自身の美学に沿って選択してください

Security

Always use PSCredential for credentials/passwords

  • Credential や パスワードには、PSCredentail を使います
  • SecureString でパスワードが保持されるため、基本的にこれを使いましょう
param (
    [System.Management.Automation.PSCredential]
    [System.Management.Automation.Credential()]
    $Credentials
)
  • どうしても生パスワードを、そこから拾う必要がある場合、メソッドから取得しましょう。なるべくさけてください
$Credentials.GetNetworkCredential().Password

Other Secure Strings

  • 他にも、Read-Host -AsSecureString でも SecureString を受け取ることgあできます
  • 万が一 SecureString を String にする必要があるなら、ZeroFreeBSTRE を用いてメモリリークを抑えてください
    # Decrypt a secure string.
    $BSTR = [System.Runtime.InteropServices.marshal]::SecureStringToBSTR($this);
    $plaintext = [System.Runtime.InteropServices.marshal]::PtrToStringAuto($BSTR);
    [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR);
    return $plaintext
  • もしディスクに認証を保持する必要がある場合、Export-CliXml を使ってパスワードを守ってください
    # Save a credential to disk
    Get-Credential | Export-CliXml -Path c:\creds\credential.xml

    # Import the previously saved credential
    $Credential = Import-CliXml -Path c:\creds\credential.xml
  • さらにもし、String がセンシティブでディスクに保持する必要がある場合は、ConvertFrom-SecureString で暗号化してください。ConvertTo-SecureString で戻すことができます。Windows Data Protection API (DPAPI)をつかっているため、同一Windowsマシンの同一ユーザーでのみ Decrypt できるので注意です。処理として、AES 共通鍵での暗号化もサポートしています
   # Prompt for a Secure String (in automation, just accept it as a parameter)
    $Secure = Read-Host -Prompt "Enter the Secure String" -AsSecureString

    # Encrypt to Standard String and store on disk
    ConvertFrom-SecureString -SecureString $Secure | Out-File -Path "${Env:AppData}\Sec.bin"

    # Read the Standard String from disk and convert to a SecureString
    $Secure = Get-Content -Path "${Env:AppData}\Sec.bin" | ConvertTo-SecureString

Language, Interop and .Net

VER-01 Write for the lowest version of PowerShell that you can

  • サポートする、もっとも低い PowerShell バージョンのために書いてください
  • ただし、新しいほどパフォーマンスメリットがあったりします
  • たとえば、PoweShell v3 では2番目の書き方のほうがかなり高速化されます
Get-Service | Where-Object -FilterScript { $\_.Status -eq 'Running' }
Get-Service | Where Status -eq Running

VER-02 Document the version of PowerShell the script was written for

  • #requires -version 3.0 といった形でサポートしているバージョンを明記してください
  • Module の場合、PowerShellVersion = '3.0' とマニフェストの psd1 に設定することで表明できます

余談 : 個人的に注意していること

私が特に多くの人から苦しいと耳にすることで、個人的に気を付けているものは次のものです。だいたい記事にしていたので参考にしていただけると幸いです。

  • [Object]型デフォルトに起因する型をつかった操作が影響受けやすいこと

tech.guitarrapc.com

  • $null の扱い

tech.guitarrapc.com

winscript.jp

  • パイプラインを通したときの実行速度と式の違い

tech.guitarrapc.com

tech.guitarrapc.com

  • 型の明示をしない場合の暗黙の型変換 (左辺合わせ)

tech.guitarrapc.com

  • 単一要素配列が返却時に自動的なアンラップがかかる

tech.guitarrapc.com

  • より安全に書くためには StrictMode の利用がいいでしょう

blog.shibata.tech

まとめ

PowerShell Script で書く場合も、C# で書く場合と同じように気を付ければ問題なさそうです。

特に、パラメータ入力、パイプラインが最も入り組んでいる印象が強いです。独自の構文$? はコンソールでの入力以外は使わないんですよねぇ。実際、私はほぼ使わないです。

PowerShell も .NET に限らず、一般的なプログラミング言語のやり方が生きます。言語自体の構文サポートの弱さやなど癖がありますが、ゆるく付き合うといいでしょう。