2016/12/20

この記事はPowerShell Advent Calendar 2016の20日目です。

はじめに

前々回はASTの概要について述べ、最後にAST.FindAllメソッドを使って、ASTから指定のASTノードを検索する方法について説明しました。

前回はASTを再帰的に検索して、木構造を視覚化してみました。

今回もASTを検索する話なのですが、静的解析機能を実装するためのAST Visitorを用いる方法について説明します。が、あらかじめお断りしておきますが、静的解析の実装までは今回はたどり着きません。静的解析ツールをどう作るかorどう作られているか、ということを雰囲気で味わっていただければと。

Visitorパターン

AST Visitorの説明をする前に、まず、Visitorパターンについて簡単に。

Visitorパターン[Wikipedia]というのは、オブジェクト指向言語におけるデザインパターンの1つで、対象オブジェクトを巡回する「訪問者」クラスを定義するものです。Visitorクラスでは、対象クラスごとに行う処理を、個別にvisitメソッドをオーバーロードさせることで定義します。共通のVisitor抽象クラスを継承することで、異なる機能を持ったVisitorクラスを作ることができます。

一方、処理対象クラスには、Visitorオブジェクトを引数に受け取る、acceptメソッドを定義します。acceptメソッドでは、引数として受け取ったVisitorオブジェクトのvisitメソッドを呼ぶことで、処理を実行させます。

なお、処理対象クラスが子要素クラスを持つ場合には、acceptメソッド内で、子要素クラスのacceptメソッドを呼ぶようにします。こうしておくことで、Visitorは処理対象を再帰的に巡回できるようになります。

このように処理対象クラスから、実際に処理を行う機能をVisitorクラスとして分離することで、処理対象クラスに手を加えることなく、Visitorクラスを追加して、処理内容を増やしたりすることが可能になります。

AST Visitorの呼び出し

Visitorパターンを念頭において、AST Visitorの呼び出し方を見ていきましょう。Ast抽象クラスには、以下の2つのVisitメソッドが定義されています。

説明に入る前に注意点。メソッド名は"Visit"となっていますが、Visitorパターンでいうところの"accept"メソッドのことです。なぜメソッド名がAcceptじゃないのかは不明ですが…。

ともかく、AstクラスのVisitメソッドは、AstVisitor抽象クラスを継承したクラスのオブジェクトか、ICustomAstVisitorインターフェースを実装したクラスのいずれかを引数に取ることで、ASTに対する処理を実施します。

AstVisitor抽象クラスを継承、もしくはICustomAstVisitorインターフェースを実装することで、ASTの種類に応じた巡回処理を行うクラスを、自分で定義していきます。

AstVisitor抽象クラス

AstVisitor抽象クラスは、Visitorとしての基本的な機能があらかじめ実装されています。具体的には既に以下の機能は用意されています。

  • ASTの種類に応じたVisitメソッドの定義
    すべての種類のASTに対応するVisitメソッド(50個以上)がVirtualメソッドとして定義されています(※)。例えば、IfStatementAstに対する処理を行うための、VisitIfStatementメソッドがあります。

    ※一般的なVisitorパターンでは、Visitメソッドを対象クラス分オーバーロードさせますが、PowerShellのAstVisitorは対象クラスに応じた別名のメソッドを定義する方式です。これも理由は分かりませんが、オーバーロードにするには多すぎるからかもしれません。

  • 子ノードの再帰的な巡回
    各Visitメソッドには、ASTの子ノードに対し、再帰的にVisitメソッドを呼ぶ仕掛けがあらかじめ備わっています。
  • ノード巡回の停止
    各VisitメソッドはAstVisitAction列挙型を返却します。以下のように返却する値によって、ノード巡回の継続、停止を制御できます。
    • Continue:ノード巡回を継続(デフォルト)
    • SkipChildren:子ノードの巡回を行わない
    • StopVisit:巡回を終了する
カスタムAstVisitorクラスを作成する

以上の基本的な機能を踏まえて、AstVisitor抽象クラスを実装したカスタムVisitorクラスを作ります。C#で書くのが一般的ですが、せっかくなのでPowerShell v5で追加された、クラス構文を使って書いてみましょう。

例えば、「利用しているコマンドのリストを取得する。ただし、コマンドのパラメータ内で別コマンドを呼び出している場合は除く。」というお題を解くことを考えます。

ASTのFindAllメソッドだと、配下に含まれるすべてのCommandAstを取得してしまうので、単純にはいきません。そこでカスタムAstVisitorクラスの出番です。

このお題を実現するVisitorクラスは以下のようになるでしょう。

using namespace System.Management.Automation.Language

class GetCommandNamesVisitor : AstVisitor
{
    [string[]]$CommandNames = @()

    [AstVisitAction]VisitCommand([CommandAst]$commandAst)
    {
        $this.CommandNames += $commandAst.CommandElements[0].Extent.Text
        return [AstVisitAction]::SkipChildren
    }
}

PowerShellのクラス構文において、Virtualメソッドのオーバーライドは、単に同名のメソッドを定義するだけですので、ここではVisitCommandメソッドをオーバーライドします。

プロパティとフィールドの区別はないので、コマンド名の一覧を格納するCommandNamesプロパティは上記のような定義になります。メソッド内でクラスメンバを参照する際には$thisを用います。

作成したGetCommandNamesVisitorクラスをインスタンス化し、解析対象スクリプトブロックのASTのVisitメソッドに引数として渡します。

$scriptBlock = {
    $files = Get-ChildItem -Path (Get-Location | Split-Path -Parent) -File
    $files | 
        Sort-Object -Property LastWriteTime -Descending | 
        Select-Object -First 5
}

$visitor = New-Object GetCommandNamesVisitor
$scriptBlock.Ast.Visit($visitor)
$visitor.CommandNames

実行すると、結果は

Get-ChildItem
Sort-Object
Select-Object

のようになるかと思います。

AstVisitorクラスの具体的な実装については、PSReadLinePowerShellEditorServicesにありますので、参考にしてみてください。

ICustomAstVisitorの実装

前項で述べた、AstVisitor抽象クラスを継承したカスタムAstVisitorクラスの場合、基本的な処理を実装する必要はないですし、目的とするASTクラスに対するVisitメソッドだけオーバーライドすればいいので、非常に簡便です。

ただ、本格的にPowerShellの構文解析を行いたい場合、ノードの巡回順だとか、その他もろもろをもっと細かく自分で実装したいケースが出てきます。

そういった場合にはICustomAstVisitorインターフェースを実装したクラスを作って対応します。ICustomAstVisitorインターフェースも、AstVisitor抽象クラス同様、各ASTクラスに応じたVisitメソッドが定義されているのですが、各VisitメソッドはAstVisitAction列挙体ではなく、object型のオブジェクトを返します。つまり、自分で好きなオブジェクトを返すように定義できるわけです。

Ast.Visit(ICustomAstVisitor)はAstVisitor抽象クラスを引数に取る場合と異なり、objectを返却するのですが、このとき返却されるのは、最初に実行されたVisitメソッドの戻り値になります。

ICustomAstVisitorはインターフェースですので、処理はすべて自分で定義しなくてはなりません(※)。ノードの再帰的探索も、必要ならもちろん自前で実装する必要があります(前回紹介した、JSON化スクリプトのような処理になるかと思います)。

※ISEだとインターフェースの実装を一発で行うリファクタリング機能はないので、今回みたく実装すべきメンバがたくさんある場合は、こんな感じのひな形を作るスクリプトを使うと良いでしょう。

今回はICustomAstVisitorインターフェースを実装したクラスの実例まではご紹介できませんでしたが、興味のある方は、PSScriptAnalyzerで用いられているので参考にしてみてください。

まとめ

PowerShellのASTについてきちんと解説している記事が英語圏を含めてもあまりないようでしたので、3回に渡って、一通りの基礎知識をまとめてみました。

普通にPowerShellを使っている分には、滅多に使うことはないと思いますが、たとえばPSScriptAnalyzerカスタムルールを自分で作る場合には、ASTの知識は必須になってきますので、必要に応じて参考にしていただければ幸いです。

2015/10/17

はじめに

がりっち氏がWindows10 UWPに日本語解析のAPIが備わっていた件 | garicchi.com というエントリを上げていました。

実はWin10に限らず、Win8.1 / Server 2012R2以降であれば、Windows ランタイム(WinRT)のWindows.Globalization名前空間に含まれるJapanesePhoneticAnalyzerクラスを用いた形態素解析ができます。

形態素解析とは要するに文字列を単語(正確には形態素という、文字列の最小構成要素)ごとに分割し、それぞれの単語の品詞を判別する処理になります。(JapanesePhoneticAnalyzerクラスだと分割までで、品詞の情報は取得できない?ようですが…)

またJapanesePhoneticAnalyzerでは分割した単語の読み仮名を取得することができます。

WinRTならPowerShellからも使えるんじゃないかなーと思ってやったらできたので、紹介します。

WinRTのPowerShellからの利用法

WinRTについての説明は他サイト様に譲りますが、要はWindowsストアアプリやUWP(ユニバーサルWindowsプラットフォーム)アプリを動作させる実行環境とAPI群です。

じゃあデスクトップアプリであるPowerShellは関係ないのかというとそうではなくて、例えばストアアプリのサイドローディングを行うAppxモジュールというものがあります。

つまりWinRTは従来のデスクトップアプリからの相互運用もできるようになっています。(すべてのコンポーネントではない)

このあたりの話は、荒井さんの記事が参考になるかと思います。:特集:デスクトップでもWinRT活用:開発者が知っておくべき、ライブラリとしてのWindowsランタイム (1/5) - @IT

PowerShellからWinRTを利用するには.NET Frameworkに含まれるクラスを利用するのと基本は同じです。

ただし注意点としては、クラス名を指定する時は、クラスの「アセンブリ修飾名」を指定する必要があります。

今回の例ではJapanesePhoneticAnalyzerクラスを使いますが、アセンブリ修飾名はWindows.Globalization.JapanesePhoneticAnalyzer, Windows.Globalization, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime

となります。

このうち必須となるのは

Windows.Globalization.JapanesePhoneticAnalyzer:クラスの完全修飾名
Windows.Globalization:クラスの含まれる名前空間
ContentType=WindowsRuntime:WinRTのコンポーネントであること

の3つだけのようです。

つまり、PowerShellからは

[Windows.Globalization.JapanesePhoneticAnalyzer, Windows.Globalization, ContentType=WindowsRuntime]

とすればクラスを参照することができます。

ちなみに任意の型のアセンブリ修飾名を知るには、[型名].AssemblyQualifiedName のようにすればOKです。

なお、WinRTにはPowerShellからも利用価値の高いクラスが他にも色々あるようです。ぎたぱそ氏が認証系のクラスについて書かれていますので、参考にしてみてください。:PowerShell も Windows Store Apps 同様に Windows.Security.Credentials namespace を使って認証情報を管理できるようにしてみる - tech.guitarrapc.com

単語を分割する

早速、形態素解析をやってみましょう。具体的にはJapanesePhoneticAnalyzerのGetWordsメソッドを呼び出すだけです。

すべての基本になるので軽く関数としてラップしておきます。

function Get-JpWord
{
    param(
        [parameter(ValueFromPipeline=$true)]
        [ValidateLength(1,99)]
        [string[]]
        $Text,
        
        [switch]
        $MonoRuby
    )
    process
    {
        foreach($t in $Text)
        {
            [Windows.Globalization.JapanesePhoneticAnalyzer, Windows.Globalization, ContentType=WindowsRuntime]::GetWords($t, $MonoRuby)
        }
    }
}

GetWordsメソッドはスタティックメソッドなので、::演算子で呼び出します。戻り値はIReadOnlyList<JapanesePhoneme>というコレクションです。

GetWordsメソッドの第2引数にTrueを指定すると、漢字の含まれた単語をルビの振れる最小単位にまで分割する、Mono Rubyモードが有効になります。

なお、GetWordsメソッドはどうも文字数制限があるようです。だいたい100文字を超えると何も出力しない感じです。この制限値はリファレンスに書いてないようなので詳細不明ですが、一応関数では99文字までという制限を入れておきました。

例えば、Get-JpWord "最近急に寒くなってきました。" のようにすると結果は以下のように表示されます。

DisplayText          IsPhraseStart YomiText
-----------          ------------- --------
最近                          True さいきん
急に                          True きゅうに
寒くな                        True さむくな
って                         False って
き                            True き
ました                       False ました
。                            True 。

出力されるJapanesePhonemeオブジェクトは3つのプロパティを持ちます。

DisplayText 分割された単語
IsPhraseStart 単語が文節の開始であるかどうか
YomiText 単語の読み仮名

このように、入力した文章を単語単位に分割し、それぞれの単語の読み仮名を取得することができます。ただこれだけだと、「で、どうしろと」という感じなので、この出力結果を利用する、より実用的な関数を書いていきましょう。

長くなったので次回に続く。

2011/03/25

PowerShellは一次元のobject配列を多用しますが、実は他言語と同様に、多次元配列やジャグ配列(配列の配列)もちゃんとあります。

ジャグ配列

ジャグ配列の作成

PS > $array = @(@("a","b"),@("c","d","e"))

配列の参照

PS > $array
a
b
c
d
e
PS >  $array.Length
2
PS >  $array[0]
a
b
PS >  $array[0][1]
b

ジャグ配列の作成は、配列をまず作って、その配列を要素に持つ配列を作成するとできます。とても直感的かと思います。ただしここで注意しなければならない点は、

PS > $array = @(@("a","b") + @("c","d","e"))

のように、配列を+演算子で連結するのは駄目であるという点です。こうしてしまうと単に一次元配列同士が連結された要素数5の一次元配列が生成されてしまいます。

さて、このようにジャグ配列に含まれる配列とその数があらかじめ分かっている場合はこのように,演算子を使えば問題ありませんが、そうではない場合はちょっと工夫が必要です。たとえば「テキストファイルの一行ごとにchar[]配列を作り、それらを要素に持つジャグ配列を作成する」ことを考えてみましょう。まず最初に駄目な例。

$lines = @(Get-Content file.txt)
$array = @()
foreach($line in $lines)
{
	$array += [char[]]$line
}

これは一見うまくいきそうですが、先ほどと同じパターンで$arrayは単なるchar[]の一次元配列になってしまいます。目的のジャグ配列を得るには次のようにします。

$lines = @(Get-Content file.txt)
$array = @()
foreach($line in $lines)
{
	$array += ,[char[]]$line
}

ここでは配列要素の追加処理の際、配列に「,」を前置することによって「配列要素を展開することなく、配列そのものとして扱う」ようにしています。こうすることで$arrayにはchar[]配列そのものが要素として追加され、結果としてジャグ配列が格納されます。

 

多次元配列

2x2の多次元配列を作成

PS > $array = New-Object "object[,]" 2,2

要素に値を代入

PS > $array[0,0] = "a"
PS > $array[0,1] = "b"
PS > $array[1,0] = "c"
PS > $array[1,1] = "d"

配列の参照

PS > $array
a
b
c
d

配列のスライス

PS > $array[@(0,0)]
a
PS > $array[@(0,0),@(0,1)]
a
b
PS > $array[@(0,0),@(1,0)]
a
c

配列の作成の仕方がちょっと気持ち悪いですが、これはサイズ固定の一次元配列を作るときと同じ要領です。

配列のスライスも可能で、切り出したい要素のインデックスを「要素のインデックスを表す配列」で指定します。たとえば$array[0,0]を取り出したい場合は$array[@(0,0)]になるわけですね。複数の要素を切り出したい場合は「要素のインデックスを表す配列」の配列(つまりはジャグ配列)を指定することになります。

 

おまけ:一次元配列のちょっとしたtips

変数、プロパティ、コマンドレットの戻り値などで、その値が配列かそうでないかが事前に分からない場合でも、必ず配列として処理したい場合には@を用います。

$lines = Get-Content file.txt

Get-Contentコマンドレットはテキストファイルが1行の場合は、$linesには文字列の配列ではなく文字列が格納されます(複数行なら行ごとの文字列が格納された配列になる)。よってこの場合$linesが配列かどうかは事前には分からないことになります。テキストファイルの1行目を取得するつもりで $lines[0] とやっても、もしテキストファイルが1行だった場合は、その1行の一文字目が返ってきてしまいます。このような事態を防ぐには、

$lines = @(Get-Content file.txt)

のようにすると、$linesには必ず配列が格納されます。たとえテキストファイルが1行でも、要素数1の配列になります。

これとは逆のケースで、「配列か非配列かわからないが、必ず非配列として扱いたい」場合は次のようにするのも手でしょう。

$value = @($unknown)[0]

この場合だともし$unknownが配列だったとしても、その1要素目をとってきて$valueに格納してくれます。本来なら$unknownが配列かどうか確かめて、その要素数がいくつであるかなどを確認すべきなんですが、「非配列か要素数1の配列どちらかが返されるパターン」というのはADSIやXMLなど扱う際に意外と多くて、そういう場合に有用かと思います。

JavaScriptの配列操作と同じようなことをする方法

$array = @(1..5)
# push(配列末尾に値を追加)
$array += 6

# unshift(配列先頭に値を追加)
$array = @(0) + $array

# shift(配列先頭の値を削除)
$array[0]; $array = $array[1..($array.length-1)]

# pop(配列末尾の値を削除)
$array[$array.length-1]; $array=$array[0..($array.length-2)]
元記事:http://blogs.wankuma.com/mutaguchi/archive/2011/03/25/197857.aspx

2010/03/04

PowerShell 2.0ではscriptblockにGetNewClosure()メソッドが追加され、クロージャを記述することができるようになりました。

クロージャを作るには、まず関数の中に変数(レキシカル変数)と関数(PowerShellではスクリプトブロック)を定義します。外側の関数内に定義された変数(レキシカル変数)を関数内に定義された内側の関数から操作するようにしておきます。そして外側の関数は内側の関数そのものを返却するようにしておきます。これでクロージャができました。

クロージャを使用するには、まず外部から外側の関数の戻り値(これは内側の関数です)を変数に代入します。そしてこの変数に含まれる関数(内側の関数)を実行すると、レキシカル変数に何らかの変化を及ぼしつつ結果が返却されます。ポイントは、レキシカル変数の値が保持されることです。その結果、内側の関数を実行するたびにその時のレキシカル変数の値に基づいた結果を返却するようにできるわけです。

とまあ書いても何のことかよくわからないかと思いますので実例を示します。よくある例題なのですが、「呼び出すたびに1が加算された結果を返す関数。すなわち、呼び出すと結果が、1,2,3,4…と続いていく関数」を考えます。

function counter()
{
	$x=0
	return {$script:x++;return $x}.GetNewClosure()
}

これがクロージャの本体です。ポイントは、scriptスコープを使用することで内側のスクリプトブロックからレキシカル変数(ここでは$x)の値を変更している点です。

使用法は次のようになります。変数$fにcounter関数の内側のスクリプトブロックを代入し、&演算子で実行しています。

PS C:\Users\daisuke> $f=counter
PS C:\Users\daisuke> &$f
1
PS C:\Users\daisuke> &$f
2
PS C:\Users\daisuke> &$f
3
PS C:\Users\daisuke> &$f
4
PS C:\Users\daisuke>

見事、お題を実現することができました。

さて、GetNewClosure()メソッドが追加されたことで、関数のカリー化も可能になります。カリー化とは、たとえばf(x,y)という関数があった場合、g(x)(y)という、f(x,y)と常に同じ値を返却する関数を作ることです。一般には、複数の引数をとる関数fを、fの最初の引数だけを引数にとり、「fの残りの引数をとり結果を返す関数」が戻り値であるgという関数に変換することです。これまた何のことかわかりにくいですね。

たとえば一番簡単な例。引数同士を加算する関数sumをカリー化してcurried_sumという関数を作ってみます。

function sum([int]$x,[int]$y)
{
	return [int]($x+$y)
}

function curried_sum([int]$x)
{
	return {param([int]$y);return sum $x $y}.GetNewClosure()
}

これでsumのカリー化ができました。実行してみます。

PS C:\Users\daisuke> &$(curried_sum 3) 4
7
PS C:\Users\daisuke> &$(curried_sum -6) 14
8
PS C:\Users\daisuke> &$(curried_sum -2) -3
-5
PS C:\Users\daisuke> $sum5 = curried_sum 5
PS C:\Users\daisuke> &$sum5 11
16
PS C:\Users\daisuke> &$sum5 -4
1
PS C:\Users\daisuke> $sum2 = curried_sum 2
PS C:\Users\daisuke> &$sum2 8
10
PS C:\Users\daisuke> &$sum2 1
3

PowerShellの関数の呼び出し方がわりと特殊であるため、少々分かりにくいですがサブ式$()と呼び出し演算子&をつかってカリー化されたsum関数を実行して目的通りの結果を得ています。たとえば最初の例は3+4を実行していることになり、結果はsum 3 4と同じ7になります。以下同様です。$sum5は、「引数に5を加える関数」になります。

いかがでしたでしょうか。PowerShellでも(なんか文法とか奇妙ですが)クロージャやカリー化ができて楽しいですね。というか私は本記事を書くにあたってクロージャとカリー化がなんぞやということを勉強しました…。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2010/03/04/186768.aspx

2010/02/13

PowerShellは.NET Framework 2.0を利用するWindowsのシステム管理用シェルである。シェルであるためコンソールで対話的にコマンドを実行することができるのはもちろん、スクリプトファイル(*.ps1)を記述しバッチ的に実行することも可能である。ここではPowerShellスクリプトで(コンソールでも使用は可能だが)用いることのできる基礎文法を紹介する。なお、PowerShellでは文法上、大文字小文字を区別しない。

※(★2.0)の注釈があるものはPowerShell 2.0で新たに追加された要素である。

1.基礎

表示

コンソールに文字列を表示。

"Hello world" 

コマンドレット(後述)を使用した場合。

Write-Host "Hello world" 

コマンドレット

PowerShellはコマンドレットと呼ばれる100種類以上のコマンドライン・ツール群を単独で、あるいはパイプライン(後述)で連結して使用するのが基本となる。コマンドレットは原則verb-nounという命名規則にしたがっている。パラメータをつける場合は「-パラメータ名」あるいは「-パラメータ名 パラメータ値」を指定する。

# コマンドレットの一覧表示
Get-Command 

# サービスの一覧を表示
Get-Service 

# アプリケーション イベントログの最新15個のエントリを表示
Get-EventLog -logName Application -newest 15

パイプライン

コマンドレットが値を返却する場合、.NET Frameworkのオブジェクトが含まれる配列であることが多い。このオブジェクト配列がパイプラインを渡って後続のコマンドレットに入力される。

# プロセスのリスト(System.Diagnostics.Processオブジェクトの配列)を取得し、
# Where-Objectコマンドレットでハンドル数(handlesプロパティ)の値が500より大きいものだけを取り出し
# Select-Objectコマンドレットで最初の5つのオブジェクトだけを切りだして表示
Get-Process | Where-Object {$_.handles -gt 500} | Select-Object -first 5

# C:\Windows 配下のフォルダ、ファイルの一覧(System.IO.DirectoryInfo,System.IO.FileInfoオブジェクトの配列)を取得し、
# ForEach-Objectコマンドレットで配列を列挙しすべてのオブジェクトのFullNameプロパティ(フルパス)の値を表示
Get-ChildItem C:\Windows | ForEach-Object {$_.FullName} 

# 通常の配列に関してもパイプラインを使用可能。
# 重複を取り除き、ソートをかける
@(3,5,10,1,2,1,1,1,2,6,4,4)|Sort-Object|Get-Unique 

コメント

# コメント 

マルチラインコメント(★2.0)

<#
複数行に渡る
コメントです
#> 

変数の宣言

PowerShellは変数の宣言をしなくても変数を使用可能。以下のようにするとどのような型でも代入可能な変数が作られる。

$a = 1
$a = $b = $c = 1 #複数変数に一度に同じ値を代入する場合
$items = Get-ChildItem # コマンドレットの戻り値を格納 

変数の型を指定することは可能。以下のようにするとint型のみ格納可能な変数が作られる。

[int]$a = 1 

あるいは、コマンドレットを用いて$aという変数を宣言することもできる。この場合変数の型は指定できない。

New-Variable -name a 

変数のスコープ

# どのスコープからも読み書き可能
$global:a = 1

# 現在のスコープからのみ読み書き可能
$private:a = 1 

# 現在のスクリプトからのみ読み書き可能
$script:a = 1 

文法チェック

以下を実行することで未定義の変数を参照するとエラーが出るようになる。

Set-PSDebug -strict 

スクリプトの実行

デフォルトの実行ポリシーではスクリプトの実行は不許可であるため、以下のようにポリシーを変更しておく。(RemoteSignedはローカルにあるスクリプトファイルは無条件で実行可、リモートにあるスクリプトファイルは署名付きのもののみ実行可)

Set-ExecutionPolicy RemoteSigned 

スクリプト/コマンドを実行するにはコマンドラインで次のようにする。

コマンドを実行する

powershell -command {Get-ChildItem C:\} 

ファイルを実行する

powershell  .\script.ps1 

ドットソース(スクリプトの内容をグローバルスコープに読み込む)

powershell  . .\script.ps1 

ファイルを実行する(★2.0)

powershell -file script.ps1 

PowerShellスクリプトから別のスクリプトを実行する場合(関数のインクルードにも用いられる)

.\script.ps1
. .\script.ps1 # ドットソース 

デバッガの起動

Set-PSDebug -trace 2 

ステップ実行

Set-PSDebug -step 

2.数値

数値の表現

PowerShellにおける数値は.NET Frameworkの数値を表す構造体のインスタンスである。数値には整数、浮動小数点があり、変数に代入した段階で適切な型が設定される。

# int型(System.Int32型)
$int = 1

# System.Double型
$double = 1.001

四則演算

# 足し算
$i = 1 + 1

# 引き算
$i = 1 - 1 

# 掛け算
$i = 1 * 1 

# 割り算
$i = 1 / 1 

余りと商の求め方

# 割り算の余り
$mod = 7 % 3 

# 上記の場合の商
$div = (7 - 7 % 3) / 3 

べき乗

# 2の8乗
$i = [math]::Pow(2,8) 

インクリメントとデクリメント

# インクリメント
$i++ 

# デクリメント
$i-- 

3.文字列

PowerShellにおける文字列は.NET Frameworkの System.Stringクラスのインスタンスである。

文字列の表現

文字列はシングルクォーテーションかダブルクォーテーションで囲む。ダブルクォーテーションの中では`t(タブ)や`r`n(改行)などの特殊文字が使用でき、変数が展開される。

$str1 = 'abc'
$str2 = "def"
$str3 = "a`tbc`r`n" 

#変数展開(結果は abc def)
$str4 = "$str1 def" 

文字列操作

各種文字列操作

# 結合
$join1 = "aaa" + "bbb"
$join2 = [string]::Join(",",@("aaa","bbb","ccc") )

# 結合(★2.0)
$join2 = @("aaa","bbb","ccc") -join "," 

# 分割
$record1 = "aaa,bbb,ccc".Split(",") 

# 分割(★2.0)
$record2 = "aaa,bbb,ccc" -split "," 

# 長さ
$length = "abcdef".Length 

# 切り出し
$substr = "abcd".SubString(0,2) # ab

正規表現検索

# hitした場合はTrue,しなかった場合はFalse
$result = "abcd" -match "cd"

# 最初に見つかった文字列。添え字の1,2…には()内のサブ式にhitした文字列が格納。
$matches[0] 

正規表現置換

$result = "abc" -replace "c","d" 

4.配列

PowerShellにおける配列は.NET Frameworkの System.Arrayクラスのインスタンスである。

配列の参照と代入

# 5個の要素を持つ配列宣言と代入
$arr1 = @(1,3,5,7,9)
 
# 以下のようにも記述できる
$arr1 = 1,3,5,7,9 

# 型指定する場合
[int[]]$arr1 = @(1,3,5,7,9) 

# 1〜10までの要素を持つ配列宣言と代入
$arr2 = @(1..10) 

# 1要素の配列宣言と代入
$arr3 = @(1) 
$arr3 = ,1 

# 空の配列宣言と代入
$arr4 = @() 

配列の要素の参照と代入

# 4番目の要素を参照 
$ret = $arr2[3] 

# 6〜9番目の要素を含んだ配列を参照
$ret = $arr2[5..8] 

# 1〜4番目と8番目の要素を含んだ配列を参照
$ret = $arr2[0..3+7] 

# 配列の末尾の要素を取り出す
$ret = $arr2[-1] 

# 5番目の要素に値を代入
$arr2[4] = 11 

# 3より小さな要素を含んだ配列を返す
$ret = $arr2 -lt 3 

配列の個数

$arr1_num = $arr1.Length 

配列の操作

$arr1 = @(1,3,5,7,9) 
$arr2 = @(1..10) 

# 配列の末尾に要素を加える(push)
$arr2 += 50 

# 配列を結合し新しい配列を作成
$arr5 = $arr1 + $arr2 

# 配列にある要素が含まれるかどうか(ここではTrue)
$arr2 -contains 2 

5.ハッシュ

PowerShellにおけるハッシュは.NET Frameworkの System.Collections.Hashtableクラスのインスタンスである。

ハッシュ変数の宣言と代入

# 3つの要素を持つハッシュの宣言と代入
$hash1 = @{a=1;b=2;c=3}
 
# 空のハッシュの宣言と代入
$hash2 = @{} 

ハッシュの要素の参照と代入

# 要素の参照
$hash1.a 
$hash1["a"] 

#要素の代入
$hash1.b = 5
$hash1["b"] = 5 

ハッシュの操作

# ハッシュに要素を追加
$hash1.d = 4 
$hash1.Add("e",5)
 
# ハッシュの要素の削除
$hash1.Remove("a") 

# ハッシュのキーの取得
$keys = $hash1.Keys 

# ハッシュの値の取得
$values = $hash1.Values 

# ハッシュの要素を列挙
foreach ($key in $hash1.Keys)
{
    $key + ":" + $hash1[$key]
} 

# キーの存在確認
$hash1.Contains("b") 

6.制御文

if文

if (条件) {

}

if 〜 else文

if (条件) {

}
else{

}

if 〜 elsif 文

if (条件) {

}
elseif (条件) { 

} 

while/do文

while (条件) {

}

do {

} while (条件)

for文

for ($i = 0; $i -lt 5; $i++) {

} 

foreach文

foreach ($item in $items) {

} 

switch文

case を書かないのが特徴的。またスクリプトブロックを条件文に記述できる。

switch ($i) {
    1 {"1";break}
    2 {"2";break}
    {$_ -lt 5} {"5より小さい";break}
    default {"default句";break}
}
# ここで$iに配列を指定すると配列要素すべてに対してswitch文が実行される。 

比較演算子

比較演算子の一覧。PowerShellではPerlの文字列比較演算子のような記述をおこなうが、Perlとは異なり文字列も数値も同じ書式である。

$num1 -eq $num2 # $num1は$num2と等しい
$num1 -ne $num2 # $num1は$num2は等しくない
$num1 -lt $num2 # $num1は$num2より小さい
$num1 -gt $num2 # $num1は$num2より大きい
$num1 -le $num2 # $num1は$num2以下
$num1 -ge $num2 # $num1は$num2以上 

論理演算子

# 論理否定
$ret = -not $true
$ret = !$true

# 論理積
$ret = $true -and $false 

# 論理和
$ret = $true -or $false 

# 排他的論理和
$ret = $true -xor $false 

ビット演算子

# ビット単位の否定
$ret = -bnot 0x14F4

# ビット単位の積
$ret = 0x14F4 -band 0xFF00 

# 上記結果を16進数で表示する場合
$ret = (0x14F4 -band 0xFF00).ToString("X") 

# ビット単位の和
$ret = 0x14F4 -bor 0xFF00 

# ビット単位の排他的論理和
$ret = 0x14F4 -bxor 0xFF00 

7.サブルーチン

PowerShellのサブルーチンには関数とフィルタがある。関数とフィルタは呼び出し行の前で宣言する必要がある。 filter構文もfunction構文と並んで独自関数を記述するものだが、filter構文はパイプラインに渡されたオブジェクトをフィルタするのに用いる。 functionとの違いは、パイプラインに渡した配列を一度に処理するか(function)個別に処理するか(filter)

# 関数宣言の基本
function Get-Test {
    return "test"
}
# 注:returnを付けなくても関数内で出力された値はすべて呼び出し元に返却される。返却したくない場合は出力値をを[void]にキャストするか|Out-Nullに渡す。

# 引数を指定する場合
function Get-Test {
    param($param1,$param2)
    return $param1 + $param2
}
 
# 引数を指定する場合の簡易的な記述法
function Get-Test($param1,$param2) {
    return $param1 + $param2
} 

# 引数の型を指定する場合
function Get-Test {
    param([string]$param1,[string]$param2)
    return $param1 + $param2
} 

# 関数の呼び出し方(,区切りではなくスペース区切りであることに注意)
Get-Test "引数1" "引数2"

# 引数の順序はパラメータ名(引数名)を指定すると自由に指定可能
Get-Test -param2 "引数2" -param1 "引数1" 

# フィルタ宣言の基本
filter Get-Odd {
    if($_ % 2 -eq 1){
        return $_ 
    }else{
        return
    }
} 

# フィルタの使用
@(1..10) | Get-Odd

8.テキストファイル入出力

コマンドレットで可能。エンコーディングは日本語環境のデフォルトではShift-JIS。コマンドレット出力のテキストファイルへの書き出しに関してはリダイレクトも可能。この場合エンコーディングはUnicode。

$str1 = "testテスト"
Set-Content test.txt $str1 # 書き込み
Add-Content test.txt "追記" # 追記
$str2 = Get-Content test.txt # 読み込み

Set-Content test.txt $str1 -encoding UTF8 # UTF-8で書き込み

# リダイレクト
Get-Process > test.txt # 書き込み
Get-Process >> test.txt # 追記
Get-Process | Out-File test.txt -encoding UTF8 # エンコーディングを指定する場合

9.例外

PowerShellで例外が発生すると、デフォルトではエラーメッセージを表示し次の行を実行する(シェル変数$ErrorActionPreferenceの設定により挙動の変更可能)。VBでいうとOn Error Resume Nextに近い。エラーが発生すると$Errorにエラー情報の配列が格納され、$?にFalseが格納される。エラーをトラップするには次の構文を使用する。VBでいうとOn Error Goto lineに近い。

# すべてのエラーをトラップ
trap {

}

# エラーの型名を指定してトラップ
trap [System.Management.Automation.CommandNotFoundException] {

} 

# エラーを発生させる
throw "エラー"
throw New-Object NullReferenceException 

構造化例外処理(★2.0)

# 基本
try{

}
catch{

}
finally{

} 

# エラーの型を指定してcatch
try{

}
catch [System.Net.WebException],[System.IO.IOException]{

}

10.知っておいたほうがよい文法

行継続文字

1行にすると長いコードを複数行に書くには行継続文字`を用いる。VBの_。

$items = Get-ChildItem a*,b*,c*,d*,e* `
-force -recurce 

ただし以下のような場合は`を使用しなくてもよい

$items =
    Get-ChildItem a*,b*,c*,d*,e* -force –recurse
    
Get-Process | 
    Where-Object {$_.handles -gt 500} |
    Select-Object -first 5 

ステートメント分割

ステートメントを分割するには改行コードもしくは;を使用する。VBの:。JavaScriptと同様、文末に;はつけてもつけなくてもよい。

$i = 1; $j = 5; $k = $i + $j 

ヒア文字列

複数行の文字列を記述する方法。

$str = @"
aaaaaa
bbbbb
cccc
ddd
ee
"@ 

.NET Frameworkクラスの利用

.NET Frameworkに含まれているクラスのプロパティやメソッドを使用できる。基本的に完全修飾名を指定しなければいけないが、"System."は省略可能。また、intなど型エイリアスがいくつか定義されている。

# スタティックメンバの使用
[System.Math]::Pow(2,8) 

# インスタンスの生成とメソッドの実行
$arrayList = New-Object System.Collections.ArrayList
$arrayList.Add("a") 

# コンストラクタがある場合。複数ある場合は配列として指定
$message = New-Object System.Net.Mail.Message from@example.com,to@example.com

# COMオブジェクトの生成
$wshShell = New-Object -com WScript.Shell 

# デフォルトで読み込まれていないアセンブリを読み込む
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.MessageBox]::Show("hello!") 

# クラスにどんなメンバがあるかの確認
# インスタンスメンバ
Get-ChildItem | Get-Member 

# スタティックメンバ
[math] | Get-Member -static 

キャスト

-asを使った場合はキャスト失敗時もエラーにならずNullが格納される。

$dt = [System.DateTime]"2010/02/13"
$dt = "2010/02/13" -as [System.DateTime] 

ユーザー定義オブジェクト

PowerShellにはクラスを定義する構文はないが、空のオブジェクト(PSObject)を生成し、任意のプロパティ(ノートプロパティ)を付加することができる。

$obj = New-Object PSObject
$property = New-Object System.Management.Automation.PSNoteProperty "Name","名前"
$obj.PSObject.Members.Add($property) 

シェル変数

あらかじめ定義されている変数。シェル変数には自動変数(変更不可能)とユーザー定義変数(変更すると挙動を変更することができる)がある。自動変数の例を挙げる。

$_ :現在パイプラインにわたっているオブジェクト
$args :関数やスクリプトに与えられたパラメータの配列
$pshome :PowerShellがインストールされているフォルダのフルパス
$MyInvocation :スクリプトの実行情報。$myInvocation.ScriptNameでスクリプトのフルパス取得(★2.0)。$myInvocation.MyCommand.Path(1.0の場合)
$true :true。
$false :false。
$null :null。

 

サブ式

$()内には複数行のコードが記述できる。

$arr = $(1;2;1+4)

式モードとコマンドモード

PowerShellの構文解析は式モードとコマンドモードがある。式モードは通常のモード。コマンドモードは引用符がなくても文字列を文字列として扱う。コマンドレットのパラメータなどはコマンドモードで扱われる。ただしコマンドモードになるところでも()もしくは$()もしくは@()をつけるとその中身は式モードとして解釈、実行される。

$i = 1 + 1 # 式モード
Write-Host aaa # コマンドモード(表示:aaa)
Write-Host aaa bbb # コマンドモード(表示:aaa bbb)
Write-Host 1+1 # コマンドモード(表示:1+1)
Write-Host (1+1) # 式モード(表示:2)
$itemCount = @(Get-ChildItem).Length # 式モード

実行演算子とスクリプトブロック

&演算子を用いるとスクリプトブロック{}の内容を実行できる。この場合、スクリプトブロック内のコードは別スコープになる。

$script = {$i = 1+6; Write-Host $i}
&$script
& 'C:\Program Files\Internet Explorer\iexplore.exe' # パスにスペースの含まれるファイルを実行したりするのにも使える

フォーマット演算子

-f演算子を使うと、.NET Frameworkのカスタム書式が使用可能。

"{0:#,##0}Bytes" -f 38731362 # 表示:38,731,362Bytes

バイト数の簡易表記

$i = 1KB # 1024が代入される
$i = 1MB # 1048576が代入される
$i = 1GB # 1073741824が代入される

そのほかの基礎文法最速マスターへのリンク

プログラミング基礎文法最速マスターまとめ - ネットサービス研究室
http://d.hatena.ne.jp/seikenn/20100203/programmingMaster

PowerShellの詳しい機能解説についてはこちらの記事を参照してください。
PowerShell的システム管理入門 ―― PowerShell 2.0で始める、これからのWindowsシステム管理術 ―― ─ @IT
進化したPowerShell 2.0 ─ @IT

文法や機能について詳しく学びたい方には書籍もあります
Windows PowerShellポケットリファレンス
PowerShellによるWindowsサーバ管理術

元記事:http://blogs.wankuma.com/mutaguchi/archive/2010/02/13/186034.aspx

2007/11/27

True

どっちか要らない子なんじゃ・・・違いがわからないよー

これだけではなんなのでミクシィから適当にコピペ

----------------------------------------------------------------------------------------------------
 
[powershell]new-service何のために
2007年11月26日19:04

あるのかよくわからんー
新しくサービスを登録するっていうんだけど、そういうのってインストーラーの仕事じゃ・・・
おまけにRemove-Serviceコマンドレットがないから作っても削除できないw
sc.exe delete hoge
としないといけない。
sc delete hogeだとSet-Contentのエイリアスが動いちゃうw
なんかすげー危ないコマンドレットな気がするよ。

----------------------------------------------------------------------------------------------------

VistaにはWin32_LogicalMemoryConfigurationないんだ

http://msdn2.microsoft.com/en-us/library/aa394181.aspx
Windows XP and Windows Server 2003: This class is no longer supported. Use the Win32_OperatingSystem class instead.

ほう

 

----------------------------------------------------------------------------------------------------

http://www.anchorsystems.co.jp/anchor/ashp/netmon/faq.html
ファイヤウォールが WMI 呼び出しをブロックしてしまうためです。 Windows 2003 SP1 と Windows XP では、デフォルトでファイヤウォールが ON になっています。ファイヤウォールに WMI 呼び出しを通過させるようにするには、以下のスクリプトを実行してください。
Set objFirewall = CreateObject("HNetCfg.FwMgr")
Set objPolicy = objFirewall.LocalPolicy.CurrentProfile
Set objAdminSettings = objPolicy.RemoteAdminSettings
objAdminSettings.Enabled = TRUE
これで WMI 呼び出しが許可されます。

ファイアウォール嫌いー

リモートでGet-WMIObjectするときにひつよう

----------------------------------------------------------------------------------------------------

[PowerShell]Get-Serviceしょぼすぎ
2007年11月26日00:55

Get-Serviceの戻り値が.NETのSystem.ServiceProcess.ServiceControllerなんすけど、Descriptionプロパティとかないねんな。
でもSet-ServiceでDescriptionを設定できたりする。どうやってちゃんと設定できたかを確認するかはget-wmiobject win32_serviceで調べるらしいwなんだこの中途半端な実装は。
ServiceControllerオブジェクトに対しps1xmlファイルでDescriptionやStartModeをScriptPropertyにして実装しとけよーと思った。せっかく拡張できるんだからさ。

----------------------------------------------------------------------------------------------------
Select-String使えん・・・
2007年11月25日00:10

PS C:\script> select-string "aa" *.ps1
attrib.ps1:7: # Get-Item?R?}???h???b?g??p???AAttributes?v???p?e?B??B
文字化けしとるがな
Shift-JISのファイルも検索・表示できないとはかなり終わってますね
せめて文字コードを指定できるようにしてくれー

UTF8はいけます
 
.NET Frameworkには文字コード判別のクラスとかないのかな・・・
前探してなかった気もする
文字コードを判別する: .NET Tips: C#, VB.NET, Visual Studio
http://dobon.net/vb/dotnet/string/detectcode.html
こういうごり押しが必要なのねー

----------------------------------------------------------------------------------------------------

あと&{スクリプトブロック} は、C#の{}空ブロックと同じことができるらしいー

要するに変数がその中でのみ使われてスコープ抜けたら破棄されるという

これを応用すればtrap文でtry catchみたいなこともできるらしいー

詳しくはPowerShellインアクションを買おう!w

元記事:http://blogs.wankuma.com/mutaguchi/archive/2007/11/27/110583.aspx

2007/07/21

Get-Helpコマンドレットに-fullオプションを付けると、コマンドレットのパラメータの説明に「必須」、「位置」、「既定値」、「パイプライン入力を許可する」、「ワイルドカード文字を許可する」という項目が追加されます。この中で「パイプライン入力を許可する」がtrueになっている場合は、パイプラインからの入力がそのパラメータに渡されるという意味なのですが、これにはByValueとByPropertyNameの二種類があります(同時に指定されていることも)。

この意味お分かりになられますか?

mixiコミュでいろいろと議論した結果、ようやく分かったのでここでご報告しておきます。

ByValueはオブジェクトがそのまま渡ります。これは特に問題ないでしょう。

ByPropertyNameは、パイプを渡ってきたオブジェクトのプロパティが、パラメータ名と一致した場合、そのプロパティをパラメータとして解釈するという意味です。

具体的にGet-ChildItemコマンドレットを取り上げましょう。

Get-ChildItemコマンドレットは-pathパラメータがtrue (ByValue, ByPropertyName)、-literalPathパラメータ(エイリアスは-PSPath。ちなみにパラメータのエイリアスを調べるには(Get-Command Get-ChildItem).parametersetsのようにするとパラメータの一覧が出ますので、そのAliasを見てください)がtrue (ByPropertyName)です。

よって、入力オブジェクトにPathプロパティがあればその値が-pathパラメータに渡ります。(なければ入力オブジェクトがそのまま-pathパラメータに渡されます)。また、入力オブジェクトにLiteralPathプロパティまたはPSPathプロパティがあれば、-literalPathパラメータにその値が渡ります。これを検証します。

Get-ChildItemコマンドレットの戻り値はファイルシステムプロバイダにおいてはFileInfoオブジェクトとDirectoryInfoオブジェクトを含んだ配列です。これらのオブジェクトにはPSPathプロパティがあるので、この結果をパイプで次のGet-ChildItemコマンドレットに渡すと、そのPSPathプロパティが、-literalPathパラメータに渡ります。すなわちこういうことです。

PS C:\script> Get-ChildItem a*|Get-ChildItem


    ディレクトリ: Microsoft.PowerShell.Core\FileSystem::C:\script


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2007/07/20     18:33          0 a.txt
-a---        2007/07/21     16:17       8826 about_Alias.help.txt

このコマンドに意味があるかどうかは別にして、そういうことが可能だということです。

もっと分かりやすいと思われる例を示しましょう。$aというオブジェクトを作成し、それにAdd-MemberコマンドレットでPathという名前のNotePropertyを追加します。そして$aをパイプラインを通じてGet-ChildItemコマンドレットに渡すとどうなるかご覧ください。

PS C:\script> $a = New-Object PSObject
PS C:\script> $a = $a | Add-Member noteproperty Path "a*" -passthru
PS C:\script> $a.path
a*
PS C:\script> $a|Get-ChildItem

    ディレクトリ: Microsoft.PowerShell.Core\FileSystem::C:\script

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2007/07/20     18:33          0 a.txt
-a---        2007/07/21     16:17       8826 about_Alias.help.txt

というわけで無事、Pathプロパティが-pathパラメータに渡っていることがお分かりいただけると思います。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2007/07/21/86361.aspx

2006/12/08

前のブログを閉鎖するので、これから元記事を一気にコピーします。

元記事はこちらですhttp://winscript.s41.xrea.com/mt/archives/2005/09/post_4.html

.NET FrameworkのSystem.Windows.Formsに含まれるコントロールを利用して、簡易ファイラをつくってみました。テキストボックスにパスを入れて「移動」ボタンを押すとそのフォルダの中身をリストボックスに表示します。リストボックスのファイルをダブルクリックすると実行、フォルダをダブルクリックすると移動します。イベントハンドラの使い方に注目してください。

function InvokeItem {
    Param($path)
    
    # 現在のパス+選択したファイル/フォルダ名を組み立てる
    $path2 = $(get-location).ToString() +"\" + $path
    
    if ([System.IO.Directory]::Exists($path2) ){
    # フォルダなら移動
        ChDirectory($path2)
    }else{
    #ファイルなら実行
        invoke-item $path
    }
}
 
function ChDirectory {
    Param($path)
    
    set-location $path #ディレクトリ移動
    $listBox1.Items.Clear() #リストボックスを空にする
    
    # get-childitem Cmdletの戻り値をパイプラインに渡し、
    # foreachしてリストボックスに追加する
    get-childitem | foreach{$r = $listBox1.Items.Add($_)}
}
 
 
[void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 
[void] [System.Windows.Forms.Application]::EnableVisualStyles()
$form = new-object System.Windows.Forms.Form
$form.Size = new-object System.Drawing.Size(300, 400)
 
$textbox1 = new-object System.Windows.Forms.TextBox
$textbox1.Size = new-object System.Drawing.Size(250, 20)
$textbox1.Location = new-object System.Drawing.Point(0, 0)
$textbox1.Text = get-location
 
$button1 = new-object System.Windows.Forms.Button
$button1.Size = new-object System.Drawing.Size(40, 20)
$button1.Location = new-object System.Drawing.Point(250, 0)
$button1.Text = "移動"
# ButtonのClickイベント
$button1.Add_Click({ChDirectory($textbox1.Text)})
 
$listbox1 = new-object System.Windows.Forms.ListBox
$listBox1.Size = new-object System.Drawing.Size(250, 300)
$listBox1.Location = new-object System.Drawing.Point(0, 50)
# ListBoxのDoubleClickイベント
$listBox1.Add_DoubleClick({InvokeItem($listBox1.SelectedItem)})
 
# 初期ディレクトリの設定
ChDirectory($textbox1.Text)
 
# コントロールをフォームに配置して表示
$form.Controls.Add($textbox1)
$form.Controls.Add($button1)
$form.Controls.Add($listBox1)
$form.showDialog()
元記事:http://blogs.wankuma.com/mutaguchi/archive/2006/12/09/49648.aspx

コマンドレットオンラインヘルプ作成
http://blogs.wankuma.com/mutaguchi/archive/2006/11/07/43965.aspx
の続編です。本文中のコマンドレットにリンクを張るようにしました。その他いろいろ改善。
旧バージョンをお使いの方は一度*.htmlを削除してください。ファイル名が変わりました。

function Sanitize{
    #サニタイズ処理
    param ([string]$strSource)
    return ($strSource.Replace("&","&").Replace("<","<").Replace(">",">"))
}
 
function MakeLink{
    #用語にリンクを張る
    param ([string]$strSource)
    foreach ($key in $keys){
        $strSource = $strSource -replace "(\s)($key)(\s)",
        ("`$1`$2`$3")
    }
    return ($strSource);
}
 
#キーワードを格納するArrayList
$keys = new-object System.Collections.Arraylist
get-help -category all | ?{"Cmdlet","Provider","HelpFile" -contains $_.category}|
%{if ($_.Name -ne $null) {[void] $keys.Add($_.Name)}}
 
# "Cmdlet","Provider","HelpFile"のカテゴリを持つヘルプをHTML化
get-help -category all | ?{"Cmdlet","Provider","HelpFile" -contains $_.category}|
%{"
" + 
$(MakeLink $(Sanitize (get-help $_.Name -detail|out-string))).Replace("`r`n","
") + "戻る
"| out-file($_.name + ".html")} $temp="" # ヘルプのHTMLのインデックスを作成する # Aliasのヘルプ get-help -category Alias |sort name| %{$temp+=""} # Cmdlet,Provider,HelpFileの各ヘルプ get-help -category all | ?{"Cmdlet","Provider","HelpFile" -contains $_.category} | sort category,name| %{$temp+=""} $temp+="
名前種類簡易説明
" + $_.name + "" + $_.category + "" + $_.synopsis + "
" + $_.name + "" + $_.category + "" + $_.synopsis + "
" out-file index.html -inputobject $temp

コマンドレットとパイプを多用するスクリプトの見やすい記法ってなんかないですかね?自分で読んでもよくわかりませんなこれw

あと気づいたこと。メソッドを呼び出すときはmethodname (parameter)のようにメソッド名と引数の間にスペースを入れてはいけない!VBSとかになれているとはまります。

関数の呼び出しはfuncname param1 param2のようにする(引数は,で区切るのではない!)。関数の戻り値を読むときは$(funcname param1 param2)のようにする。関数中で何か値が返されるとそれがそのまま関数の戻り値になる(returnは明示しなくてもいいということ。複数の値を返すと戻り値は[object[]]の配列になる。逆に値を返したくない場合は[void]にキャスト)

-replace演算子でサブマッチ文字列は$1,$2..に格納される。$は変数の頭文字なので ` (アクサン グラーブ)でエスケープし、`$1とする。もしくは''(シングルクォーテーション)でくくり'$1'のようにする。

そうそう、
get-help -category all | ?{"Cmdlet","Provider","HelpFile" -contains $_.category}|
は、なんで
get-help -category Cmdlet,Provider,HelpFile
にしなかったかというと、このようにしてもなぜかAliasが含まれてしまうからです。Cmdletを指定するとAliasが含まれる仕様のようです。
でも-containsの使い方がわかってよかったです(ちょっとトリッキー?)

元記事:http://blogs.wankuma.com/mutaguchi/archive/2006/12/08/49387.aspx

2006/07/14

.NET Framework 2.0にはタスクトレイにアイコンを表示させるためのクラス、NotifyIconクラスがあります。これを使ってみましょう。

function CreateNotifyIconMenu()
{
 # menuItem1オブジェクトの作成
 $menuItem1 = new-object System.Windows.Forms.MenuItem("終了(&X)")
 
 # Clickイベント
 $menuItem1.Add_Click({Form_Closing})
 
 # contextMenuオブジェクトの作成
 $contextMenu = new-object System.Windows.Forms.ContextMenu
 
 # menuItemオブジェクトをcontextMenuオブジェクトのMenuItemsコレクションに追加
 [void]$contextMenu.MenuItems.Add($menuItem1)
 #↑ここの[void]を忘れるとAddメソッドの戻り値がreturnされてしまう。
 
 return ($contextMenu)
}
function Form_Closing()
{
 # フォームとシステムトレイアイコンを非表示に
 $form.Visible = $false
 $notifyIcon.Visible = $false
 
 # PowerShellの終了
 [System.Environment]::Exit(0)
}
[void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 
[void] [System.Windows.Forms.Application]::EnableVisualStyles()
# notifyIconオブジェクトの作成とプロパティの設定
$notifyIcon = new-object System.Windows.Forms.NotifyIcon
# System.Drawing.Iconクラスのコンストラクタにはicoファイルのパスを指定する
$notifyIcon.Icon = new-object System.Drawing.Icon C:\script\test\a.ico
$notifyIcon.Text = "PowerShell実行中"
$notifyIcon.ContextMenu = CreateNotifyIconMenu
$notifyIcon.Visible = $true
# formオブジェクトの作成
$form = new-object System.Windows.Forms.Form
# Closingイベント
$form.Add_Closing({Form_Closing})
# フォームの表示
[void]$form.showDialog()

理論上はフォームを表示させなくても良いはずなんですが、ループをまわすいい方法が思いつきませんでした(start-sleepを使うとその間イベントが実行されない)。あと、showDialogで表示したフォームを閉じるいい方法も思いつかなかったので[System.Environment]::Exit(0)
としてPowerShell自体のプロセスを終了させています(exitとやるとエラーが出るんですよね。なんでしょうこれは)。これらに対する良い解決策をお持ちの方は教えてください。

PowerShellのfunctionって引数がなしのとき、呼び出す際()を使うとエラーになるんですよね。あと、文頭に持ってこないといけないのも気に入りません。何とかならなかったのでしょうか…。

せっかくタスクトレイが使えるのに、フォームとコンソールまで表示されてしまいます。そこでWSHを併用してごまかす方法を。

Set WshShell=CreateObject("WScript.Shell")
WshShell.Run "powershell test.ps1",0

このようなvbsファイルを作ってps1ファイルをキックしてやります。Runメソッドの第二引数に0を指定すると、コンソールおよびフォームが非表示になりますが、タスクトレイは表示されます。この手を使うとまるでタスクトレイだけが表示されているかのような状態を作り出せます。ちなみに、MessageBoxも同じようにそれだけを表示させることができます。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2006/07/14/32470.aspx


Copyright © 2005-2018 Daisuke Mutaguchi All rights reserved
mailto: mutaguchi at roy.hi-ho.ne.jp
プライバシーポリシー

Twitter

Books