2015/12/10

この記事はPowerShell Advent Calendar 2015の10日目の記事です。

はじめに

前編では、Invoke-WebRequestコマンドレットやWebClientクラスを用いて、WebページからHTMLの文字列を取得するところまで説明しました。

後編の今回は、取得したHTML文字列をパースして、オブジェクトとして利用可能しやすい形に変換する話です。

IEエンジンによるHTMLパース(DOM)

前編でも触れましたが、Invoke-WebRequestコマンドレットは、レスポンス文字列を取得すると同時に、HTMLをパース(構文解析)し、結果をオブジェクトとして構造化してくれます。

実はこのHTMLパース、内部的にInternet Explorerのエンジンを呼び出すことで実現されています。(ちなみに後で説明しますが、-UseBasicParsingパラメータを付与すると、IEエンジンを使わずごく基本的なパースのみ行うようになります。)

Invoke-WebRequestコマンドレットの出力であるHtmlWebResponseObjectオブジェクトのParsedHtmlプロパティを経由することで、HTMLパースされたオブジェクトを、DOM(Document Object Model)に従ってアクセスすることができます。(-UseBasicParsing指定時は不可)

HTMLのtable要素を切り出し、table各行を1オブジェクト、各セルをプロパティとして、オブジェクト配列化する例を以下に示します。

$response = Invoke-WebRequest http://winscript.jp/powershell/301

# DOMを利用して1つ目のtable要素を取得
$table = $response.ParsedHtml.getElementsByTagName("table")| select -First 1

# tableの1行目をプロパティ名として取得
$properties = ($table.rows| select -first 1).Cells| foreach {$_.innerText}

# tableの残りの行に対して、各セルのinnerTextをプロパティ値としてオブジェクト化
$objs = foreach($row in ($table.rows| select -skip 1))
{
    $row.Cells| foreach -Begin {
        $index = 0
        $obj = [ordered]@{}
    } -Process {
        $obj += @{$properties[$index] = $_.innerText}
        $index++
    } -End {
        [pscustomobject]$obj
    }
}

$objs| Format-List

ところで前編で軽く触れましたが、IEエンジンによるパースは、Invoke-WebRequestコマンドレットを用いずとも、以下のようにして直接IEのCOMインターフェースを呼ぶことで利用可能です。

$client = New-Object System.Net.WebClient
$content = $client.DownloadString("http://winscript.jp/powershell/301")
$parsedHtml = New-Object -com "HTMLFILE"
$parsedHtml.IHTMLDocument2_write($content)
$parsedHtml.Close()
$table = $parsedHtml.getElementsByTagName("table")| select -First 1
# 以下同様…

というより実際に試すと直接IEエンジンを呼び出す方がずっと速いです。理由はよく分かりませんが…。

HTML要素コレクションの取得

Invoke-WebRequestコマンドレットを用いると、DOMとは別に、すべての要素(AllElementsプロパティ)、input要素(InputFieldsプロパティ)、img要素(Imagesプロパティ)、a要素(Linksプロパティ)、script要素(Scriptsプロパティ)を含むコレクションを、HtmlWebResponseObjectオブジェクトの対応するプロパティからそれぞれ取得することができます。

コレクションに含まれる各要素は、innerText(タグ内の文字列)、innerHTML(タグ内のHTML)、tagName(タグ名)等のプロパティが共通して利用可能です。また要素の属性(たとえばa要素ならリンク先を示すhref属性)に、プロパティとしてアクセス可能となります。

以下はBingでWeb検索した結果から、ページタイトルとURLを抜き出す例です。HtmlWebResponseObjectのLinksプロパティでa要素の配列を取ってきて、次に検索結果では無いっぽいURLを、hrefプロパティの値を見てwhereで除外し、最後にinnerTextプロパティとhrefプロパティをTitle、Urlとリネームしてから値を出力しています。泥臭い処理が混じってますが、この泥臭さがスクレイピングなのかもなぁと思います。

$searchWord = "PowerShell 配列"
$notSearchResults = "/","#","javascript:","http://go.microsoft.com/"
$response = Invoke-WebRequest "https://www.bing.com/search?q=$([Uri]::EscapeDataString($searchWord))"
$response.Links | 
    where {
        $href = $_.href
        !($notSearchResults|? {$href.StartsWith($_)})
    }|
    select @{L = "Title"; E = "innerText"}, @{L = "Url"; E = "href"}|
    Format-List

form要素についてもほぼ同様にFormsプロパティからコレクションを取得できますが、このコレクションにはFormObjectという特別なオブジェクトが含まれます。FormObjectのFieldsプロパティは、Key=パラメータ名、Value=パラメータ値が格納された連想配列となっています。この連想配列は書き替えが可能なので、前編で説明した、ログオンを要するWebサイト等で用いると便利かと思います。

以下に、HtmlWebResponseObjectオブジェクトのプロパティをまとめます。(×印は使用不可を表す)

プロパティ名 説明 -UseBasicParsing
指定時
AllElements 本文に含まれるすべての要素のコレクション ×
Forms フォーム(form要素)のコレクション ×
InputFields 入力フィールド(input要素)のコレクション  
Images 画像(img要素)のコレクション  
Links リンク(a要素)のコレクション  
Scripts スクリプト(script要素)のコレクション ×

このように、一部のプロパティについては-UseBasicParsing指定時でも利用可能です。サーバーOS等でIEエンジンが利用できない場合には-UseBasicParsingパラメータが必須となりますが、その場合でも最低限のパースはしてくれるわけです。

HTML要素のコレクションを利用する方法は、DOMを使う方法に比べると自由度は少ないですが、「ページから画像のリストを取得したい」等の処理は簡便に行うことができます。

その他のHTMLパース手法

最後に、Invoke-WebRequestコマンドレットとIEエンジン以外のHTMLパース手法について軽くご紹介します。

XMLとしてパース(XHTML限定)

XHTMLというのはごくかいつまんで言うと、HTMLをXMLで定義したものです。XHTMLはXMLなので、XMLとしてパースして用いることができます。

PowerShellは[xml](XmlDocument)型アクセラレータと型アダプタにより、XML要素への簡便なアクセス手段を提供しています。以下のように、[xml]型アクセラレータを用い、取得したXHTML文字列を[xml]型に変換すると、以降は型アダプタの機能により、ドット演算子で要素を辿っていくことができます。

$client = New-Object System.Net.WebClient
$content = $client.DownloadString("XHTMLなページ")
$xml = [xml]$content
$xml.html.body.h2.'#text'

ただ世の中のWebページ上のXHTML文書が、すべてXML文書としてvalidなものであるかと言われると、現実はかなり厳しいです。そしてXML文書としてエラーがある場合は、型アクセラレータの処理は容赦なく失敗します。なのでこの手法は「使えたら強いが、大抵使えない」レベルのものと思って頂ければいいと思います。

SgmlReader

標準機能にこだわらなければ、.NET製のHTMLパーサーを使うのが楽かと思います。SgmlReaderは通常のHTML文書(当然、XHTMLに限らず)をXmlDocumentへとパースしてくれるので、PowerShellと相性が良いのではないかと思います。

以下にサンプルを載せておきます。

Add-Type -Path .\SgmlReaderDll.dll

function Get-HTMLDocument
{
    param([uri]$Uri)
    $sgmlReader = New-Object Sgml.SgmlReader -Property @{
        Href = $Uri.AbsoluteUri
        CaseFolding = [Sgml.CaseFolding]::ToLower
    }
    $doc = New-Object System.Xml.XmlDocument
    $doc.Load($sgmlReader)
    $doc
}

$xml = Get-HTMLDocument http://winscript.jp/
$xml.html.body.div|? id -eq outer|% div|? id -eq main|% {$_.p.innerText}

ぎたぱそ氏も以前SgmlReaderを取り上げておられるので、そちらも参考にして下さい。:Html Agility Pack と SgmlReader を使って PowerShell でスクレイピングしてみる - tech.guitarrapc.cóm

正規表現等で自前パース

これまではHTMLパースを既存のコマンドやライブラリを用いて行ってきましたが、対象のHTMLが非常にシンプルである場合とか、HTMLですらなく単なるテキストの場合だとか、対象ページは分量が多いものの必要箇所はごくわずかで、かつピンポイントに取得可能な場合等々は、むしろ自前でパースするコードを書いた方が手っ取り早いこともあります。

例えばYAMAHAのルーターで、管理Webのシステム情報レポートからグローバルIPアドレスを取ってくる、みたいなことは、

$response = Invoke-WebRequest http://サーバー/detail/status.html -UseBasicParsing -Credential $credential
if($response.Content -match "PP IP Address Local\: (.+)\,")
{
    $ipAddress = $Matches[1]
}

のようなコードで十分かと思います。

ConvertFrom-String

これはまだ検証してないんですが、PowerShell5.0の新機能、Auto-Generated Example-Driven Parsingの実装であるConvertFrom-Stringコマンドレットを用いて、HTMLパースができないかな、と考えています。

ConvertFrom-Stringについては過去記事参照:[v5] Auto-Generated Example-Driven Parsing について - PowerShell Scripting Weblog

まとめ

前後編に渡って、PowerShellでのWebスクレイピングの手法について解説しました。スクレイピングはWeb APIが用意されていない場合の苦肉の策ですが、背に腹は代えられない場合というのは稀によくあると思います。そういうときに今回の記事が参考になれば幸いです。

次回あたりには、Web APIがちゃんと用意されてる場合に、PowerShellから利用する話をやろうかと思います。

2014/12/22

はじめに

この記事はPowerShell Advent Calendar 2014の22日目の記事です。

前回はMMLをパースし、音楽をBeepで再生するところまで作りました。

MMLはテキストデータなので、テキストエディタで入力しても良いのですが、どうせなら、PowerShellでエディタも作ってしまいましょう。

コード

いきなりですが、エディタの本体コードを。

function Enter-MusicEditor
{
    $keyMap=@{
        A="<G+4>"
        Z="<A4>"
        S="<A+4>"
        X="<B4>"
        C="C4"
        F="C+4"
        V="D4"
        G="D+4"
        B="E4"
        N="F4"
        J="F+4"
        M="G4"
        K="G+4"
        ","="A4"
        L="A+4"
        "."="B4"
        "/"=">C4<"
        ":"=">C+4<"
        "\"=">D4<"
        "]"=">D+4<"
        "R"="R4"
    }

    $mml=@()
    cls
    while($true)
    {  
        $k = [System.Console]::ReadKey($true)

        if($k.Key -eq [System.ConsoleKey]::Escape -or $k.Key -eq [System.ConsoleKey]::Enter)
        {
            cls
            break
        }
        elseif($k.Key -eq [System.ConsoleKey]::Backspace)
        {
            if($mml.length -eq 1)
            {
                $mml=@()
            }
            elseif($mml.length -ge 2)
            {
                $mml=$mml[0..($mml.length-2)]
            }
            cls
            [console]::write((-join $mml))
        }
        else
        {
            $key=$k.KeyChar.ToString().ToUpper()
        
            if($keyMap.Contains($key))
            {
                $mml+=$keyMap[$key]
                [console]::write($keyMap[$key])
                Invoke-Mml $keyMap[$key]
            }
        }
    }
    -join $mml
}
使い方

PowerShellコンソール上(ISEは不可)で、前回公開した、ConvertFrom-MML、Invoke-Beep、Invoke-Mmlの3つの関数をまず読み込み、続いて上記のEnter-MusicEditor関数を読み込みます。

この状態でコンソール上でEnter-MusicEditorを実行すると、MMLエディタモードに入ります。

MMLエディタモードでは、PCのキーボードを鍵盤代わりにして入力できます。

たとえば、Cキーを押下すると、「ド」の音がBeepで鳴り、コンソールに"C4"と出力されます。同様にFキー押下で「ド♯」が鳴り"C+4"と出力されます。どのキーがどの音に対応しているかは、上記コードの$keyMap変数に格納された連想配列を参照してください。

(なんとなくですけど、楽器の鍵盤の配置と合わせてあります)

Backspaceキーを押下すると、直前の入力を削除できます。

EnterキーかEscapeキーを押下すると、エディタモードを終了します。

出力したMMLは、コンソール上に表示が残るので、あとはコピーしてInvoke-Mml関数に渡してやると演奏することができます。

また、$mml=Enter-MusicEditorとしてやると、入力したMMLをそのまま変数に格納できます。この場合だとInvoke-Mml $mmlと実行すれば演奏できます。

解説

技術的には全然大したことをしてないですが、一応解説。

まず、[System.Console]::ReadKey()で入力したキーコードを判別します。引数に$trueを指定すると、入力したキー名をそのままコンソールに出力するのを抑制できます。

ReadKeyメソッドはConsoleKeyInfoオブジェクトを返します。アルファベットキーについてはKeyCharプロパティの値、特殊キーについてはConsoleKey列挙体を返すKeyプロパティの値で調べることができます。

あとは入力キーから対応する音名を連想配列$keyMapから取ってきて、その音名を[System.Console]::Write()でコンソール出力すると同時に、Invoke-Mml関数でその音をBeepとして鳴らしているだけです。

まとめ

入力も再生もできるようになったので、これはもう完全にシーケンサーですね! いや、やはり無理があるか…。

しかし、他のMMLコンパイラと併用して、ちょっと演奏しながらMML入力したいな、というときにもしかすると役に立つかもしれません。

ちょっと機能が少なすぎるんで、せめて↑で半音上げ↓で半音下げ、→で伸ばす←で短く、くらいはそのうち実装してみたいですね。

まあ、MML作成にはあんまり役に立たないかもしれませんが、PowerShellでコンソールの入出力を制御するのは、こういう単純なものなら意外と簡単にできるという、サンプルにはなるかと思います。

2011/05/16

昨日の記事で取り上げたJavaScriptSerializerを用いると、連想配列から楽にJSONを作成できることが分かったのですが、この記事を書いていて思ったのは、「PowerShellの連想配列って意外に使えるな」という点でした。

現在のところ、PowerShellは独自のクラスを記述する方法がありません(Add-Typeコマンドレットを用いてC#などでクラスを書いて利用することはできますが)。Add-Memberコマンドレットを用いると、既存のオブジェクトに対し、任意のプロパティやPowerShellスクリプトで記述したメソッドを追加することはできます。素のオブジェクトであるPSObjectをNew-Objectして作ったオブジェクトでもこれは可能なので、一応ユーザー定義オブジェクトを作ることは可能です。ですが、Add-Memberコマンドレットを使うのはちょっとめんどくさいです。

Windows PowerShellインアクション」ではAdd-Memberを使いPowerShellの関数を駆使してクラス定義構文のようなものを実装した例はありますが、いささか大仰な感は否めません。

しかし連想配列をユーザー定義オブジェクト代わりに使うと、簡単にできますしそこそこ便利に使えます。

連想配列をオブジェクトの代わりにすることのメリットとデメリット

連想配列をオブジェクトの代わりにすることのメリットは以下の三点があるかと思います。

  1. 簡単な記述(連想配列のリテラル)でオブジェクトが作成できる
    PowerShellの連想配列リテラル@{}を使うことで、簡単に記述できます。またそれを配列化するのも@()を使うと容易です。
    $pcItems=
    @(
        @{
            code=25;
            name="ハードディスク2TB";
            price=7000;
        },
        @{
            code=56;
            name="メモリ8GB";
            price=8000;
        }
    )
  2. ドット演算子で値の参照、設定ができる
    PowerShellの連想配列は「連想配列[キー名]」のほかに、「連想配列.キー名」でもアクセスできる。
    Write-Host $pcItems[1].name # 値の参照
    $pcItems[1].name = "test" # 値の設定
    $pcItems[0].maker = "Seagate" # 要素の追加
    
  3. 連想配列の配列に対してWhere-Objectコマンドレットでフィルタをかけることができる
    これは2とも関係しているのですが、通常のオブジェクトと同様にWhere-Objectコマンドレットでのフィルタ、ForEach-Objectコマンドレットでの列挙が可能です。
    $pcItems|?{$_.price -gt 7000}|%{Write-Host $_.name}

このようにメリットはあるのですが、本物のオブジェクトではないのでそれに起因するデメリットがいくつかあります。

  1. 要素(プロパティ)をいくらでも自由に追加できてしまう
    これはメリットではあるのですが、デメリットでもある点です。後述するデメリットのせいで、同じキーをもつ連想配列の配列を作ったつもりでも、どれかのキー(プロパティ名)を間違えていた場合、それを検出するのが困難です。
  2. メソッドがうまく記述できない
    連想配列要素にスクリプトブロックを指定し、&演算子で実行することでメソッド的なことはできます。しかしこのスクリプトブロック内では$thisが使えず、オブジェクトのプロパティにアクセスすることができないのでいまいちです。
    $pcItem= @{
        name="ハードディスク2TB";
        price=7000;
        getPrice={Write-Host $this.price};
    }
    &$pcItem.getPrice # 何も表示されない。$thisが使えない
    # getPrice={Write-Host $pcItem.price}ならOKだが…
  3. Get-Member、Format-List、Format-Tableなどが使えない
    これらのコマンドレットはあくまで連想配列オブジェクト(Hashtable)に対して行われるので、意図した結果になりません。たとえば$pcItems|Format-Listした場合、
    Name  : name
    Value : ハードディスク2TB
    
    Name  : code
    Value : 25
    
    Name  : price
    Value : 7000
    
    Name  : name
    Value : メモリ8GB
    
    Name  : code
    Value : 56
    
    Name  : price
    Value : 8000
    こんな表示になってしまいます。
連想配列をユーザー定義オブジェクトに変換する関数ConvertTo-PSObject

このように、連想配列の記述のお手軽さは捨てがたいものの、いくつかの問題点もあるのが現実です。そこで連想配列のお手軽さを生かしつつ、ユーザー定義オブジェクトの利便性も取るにはどうすればいいか考えました。結論は、「連想配列を変換してユーザー定義オブジェクトにする関数を書く」というものでした。それが以下になります。

#requires -version 2
function ConvertTo-PSObject
{
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [System.Collections.Hashtable[]]$hash,
        [switch]$recurse
    )
    process
    {
        foreach($hashElem in $hash)
        {
            $ret = New-Object PSObject
            foreach($key in $hashElem.keys)
            {
                if($hashElem[$key] -as [System.Collections.Hashtable[]] -and $recurse)
                {
                    $ret|Add-Member -MemberType "NoteProperty" -Name $key -Value (ConvertTo-PSObject $hashElem[$key] -recurse)
                }
                elseif($hashElem[$key] -is [scriptblock])
                {
                    $ret|Add-Member -MemberType "ScriptMethod" -Name $key -Value $hashElem[$key]
                }
                else
                {
                    $ret|Add-Member -MemberType "NoteProperty" -Name $key -Value $hashElem[$key]
                }
            }
            $ret
        }
    }
}

ご覧のようにコード的には割にシンプルなものが出来ました。連想配列またはその配列をパラメータにとり、またはパイプラインから渡し、連想配列要素をプロパティまたはメソッドに変換してPSObjectにAdd-Memberしてるだけです。-recurseパラメータを付けると連想配列内に連想配列がある場合に再帰的にすべてPSObjectに変換します。

それでは実際の使用例を挙げます。

# 一番単純な例。パラメータに連想配列を渡すとPSObjectに変換する。
$book = ConvertTo-PSObject @{name="Windows PowerShell ポケットリファレンス";page=300;price=2000}
Write-Host $book.name # 「Windows PowerShell ポケットリファレンス」と表示
$book.name="test" # プロパティに値をセットする
Write-Host $book.name # 「test」と表示
#$book.size="A5" # 存在しないプロパティに値を代入しようとするとエラーになる

# 連想配列をコードで組み立てていく例。
$mutaHash=@{} # 空の連想配列を作る
$mutaHash.name="mutaguchi" # キーと値を追加
$mutaHash.age=32
$mutaHash.introduce={Write-Host ("私の名前は" + $this.name + "です。")} # スクリプトブロックを追加
$mutaHash.speak={Write-Host ($args[0])} # パラメータを取るスクリプトブロックを追加
$muta = $mutaHash|ConvertTo-PSObject # 連想配列はパイプラインで渡すことができる
$muta.introduce() # 「私の名前はmutaguchiです。」と表示
$muta.speak("こんにちは。") # 「こんにちは。」と表示

# 連想配列の配列→PSObjectの配列に変換
$stationeryHashes=@()
$stationeryHashes+=@{name="鉛筆";price=100} 
$stationeryHashes+=@{name="消しゴム";price=50}
$stationeryHashes+=@{name="コピー用紙";price=500}
$stationeryHashes+=@{name="万年筆";price=30000}
$stationeries = ConvertTo-PSObject $stationeryHashes
# "200円以上の文具を列挙"
$stationeries|?{$_.price -ge 200}|%{Write-Host $_.name} # 「コピー用紙」と「万年筆」が表示

# 連想配列の配列をリテラルで一気に記述する
$getPrice={Write-Host $this.price} # 共通のメソッドを定義
$pcItems=
@(
    @{
        code=25;
        name="ハードディスク2TB";
        price=7000;
        getPrice=$getPrice
    },
    @{
        code=56;
        name="メモリ8GB";
        price=8000;
        getPrice=$getPrice
    },
    @{
        code=137;
        name="23インチ液晶ディスプレイ";
        price=35000;
        getPrice=$getPrice
    }
)|ConvertTo-PSObject
$pcItems[1].getPrice() # 「8000」と表示
$pcItems|Format-List
<#
表示:
name  : ハードディスク2TB
code  : 25
price : 7000

name  : メモリ8GB
code  : 56
price : 8000

name  : 23インチ液晶ディスプレイ
code  : 137
price : 35000
#>

# 連想配列の中に連想配列を含めたもの→PSObjectをプロパティの値に持つPSObject
$blog=
@{
    utl="http://winscript.jp/powershell/";
    title="PowerShell Scripting Weblog";
    date=[datetime]"2011/05/16 00:25:31";
    keywords=@("スクリプト","PowerShell","WSH"); # 配列を含めることもできる
    author=@{name="mutaguchi";age=32;speak={Write-Host "ようこそ私のブログへ"}} # 連想配列を含める
}|ConvertTo-PSObject -recurse # -recurseパラメータを指定すると再帰的にすべての連想配列をPSObjectに変換する
$blog.author.speak() # 「ようこそ私のブログへ」と表示
Write-Host $blog.keywords[1] # 「PowerShell」と表示
# ※配列要素に連想配列以外の値が含まれている場合は展開しない

このように簡単な関数一つで、連想配列にあった問題点をすべて解消しつつ簡単な記述で独自のオブジェクトを記述できるようになりました。おそらくかなり便利だと思いますのでぜひ使ってみてください。

余談:ScriptPropertyを使う場合

余談ですが、今回使用したNotePropertyはプロパティに代入できる型を指定したり、リードオンリーなプロパティを作ったりすることができません。そういうのを作りたい場合はScriptPropertyを使います。Add-Memberコマンドレットの-valueパラメータにゲッターを、-secondValueパラメータにセッターをそれぞれスクリプトブロックで記述します。

しかしこいつはあまりいけてないです。これらのスクリプトブロック内で参照するフィールドを別途Add-MemberでNotePropertyを使って作成する必要があるのですが、これをprivateにすることができません。よってGet-Memberでもばっちり表示されてしまいますし、フィールドを直接書き換えたりもできてしまいます。

また今回のように連想配列をPSObjectに変換する場合はprivateフィールド名も自動生成する必要があるのですが、それをScriptProperty内のゲッター、セッターから取得する方法がなく、たぶんInvoke-Expressionを使うしかありません。

これらを踏まえて元の連想配列要素の値の型を引き継ぎ、それ以外の型を代入できないようにしたScriptPropertyバージョンも一応書いてみました。ConvertTo-PSObject関数のelse句の部分を以下に置き換えます。

#$ret|Add-Member -MemberType "NoteProperty" -Name ("_" +$key) -Value $hash[$key]
"`$ret|Add-Member -MemberType ScriptProperty -Name $key -Value {[" + $hash[$key].gettype().fullname + "]`$this._" + $key + "} -SecondValue {`$this._" + $key + "=[" + $hash[$key].gettype().fullname + "]`$args[0]}"|iex

まあこれはいまいちなんで参考程度に。

2012/08/23追記
この記事を書いた時は知らなかったのですが、実は単にNotePropertyだけを持つユーザー定義オブジェクトを作成するのであれば、もっと簡単な方法があります。

# PowerShell 1.0
$o=New-Object PSObject|Add-Member noteproperty Code 137 -pass|Add-Member noteproperty Name 23インチ液晶ディスプレイ -pass

# PowerShell 2.0
$o=New-Object PSObject -Property @{Code=137;Name="23インチ液晶ディスプレイ"}

# PowerShell 3.0
$o=[pscustomobject]@{Code=137;Name="23インチ液晶ディスプレイ"}

3つのコードはほぼ等価です。PowerShell 2.0と3.0では連想配列リテラルを用いて簡単にカスタムオブジェクトを作れるようになりました。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2011/05/16/199086.aspx

2011/05/14

PowerShellでJSONをパースする方法はいくつかあると思います。

1. System.Runtime.Serialization.Json.JsonReaderWriterFactoryクラスを用いる

これは.NET Framework 3.5から追加されたクラスで、JSONデータを読み書きするXMLReader/Writerを提供します。すなわちJSONをパースしてXMLに変換することが可能です。XMLはPowerShellから簡単に扱えるので有用な方法と言えるでしょう。

PowerShellからの使用方法についてはこちらの記事が参考になります。:JSON Serialization/Deserialization in PowerShell | Keith Hill&apos;s Blog

2. 頑張って自力でパースする

.NET 3.5が入っていない環境では1の方法が使えないので別の方法を考える必要があります。JSONはテキストデータなので、頑張って自力でパースすることもできなくはないでしょう。

PowerShellでやっている例はこちらになります。:Convert between PowerShell and JSON - Home("Source Code"のリンクをたどっていくとソースがあります)

3. ScriptControl+JScriptを用いる

もう少し簡便な方法はないかなと思っていろいろ考えたんですが、PowerShellではScriptControlを用いるとJScriptやVBScriptを実行することができます。そしてJSONはJavaScriptで扱うことを想定しているだけあって、JScriptではeval()するだけでJSONをオブジェクトに変換することができます。そこで実際にやってみたのですが…

$json=@'
{"items":
    [
        {
            "code":25,
            "name":"ハードディスク2TB",
            "price":7000
        },
        {
            "code":56,
            "name":"メモリ8GB",
            "price":8000
        },
        {
            "code":137,
            "name":"23インチ液晶ディスプレイ",
            "price":35000
        }
    ]
}
'@
$sc=new-object -com ScriptControl
$sc.Language = "JScript"
$jscode="function parseJSON(json){return eval('(' +json + ')').toString();}"
$sc.AddCode($jscode)
$jsobj=$sc.CodeObject.parseJSON($json)
$jsobj

このコードを実行すると、確かにJSONがパースされ、結果が$jsobjという変数に格納されるのですが、残念ながらPowerShellはJScriptのオブジェクト(JScriptTypeInfo)を展開することができないようなのです。

JScriptTypeInfoオブジェクトはVBScriptでは扱うことができるので、まずJScriptでパースし、その結果オブジェクトをVBScriptに渡し、オブジェクトはScripting.Dictionaryオブジェクトに変換し、配列はVBScriptの配列(Safe Array)に変換し、その結果オブジェクトをPowerShellに戻すという方法を考えました。PowerShellはCOMオブジェクトやSafe Arrayは扱えるので理屈の上ではうまくいきます。(参考までに、ASPでこの方法を実際にコードにしてる方がいらっしゃいました。:ASPでJSONパーサーを書いてみた - ゆるゆると

しかしこの方法は当初の目的「簡便にJSONをパースする」からだいぶ離れてしまっています。

4. JScript.NETを用いる

そうだ、JScriptが駄目ならJScript.NETを使えばいいじゃない。JScript.NETなら結果は.NETのオブジェクトで返るしPowerShellでも読めるだろう、ということで、この前このブログで紹介したAdd-Typeコマンドレットを使ってJScript.NETのコードを実行する方法を利用してやってみました。

($jsonの値は先ほどのスクリプトのを使います)

$code=@"
static function parseJSON(json)
{
    return eval('(' +json + ')');
}
"@
$JSONUtil = (Add-Type -Language JScript -MemberDefinition $code -Name "JSONUtil" -PassThru)[1]
$jsobj = $JSONUtil::parseJSON($json) # $jsobjはJSObject

$jsobj["items"][1]["name"] #「メモリ8GB」と表示される

$items=$jsobj["items"] # $itemsはJSArrayObject
$items|%{$items[$_]["name"]} # 名前が列挙される

という感じでうまくいきました。

ここで$jsobjに格納されているのはMicrosoft.JScript.JSObjectクラスのオブジェクトです。このクラスのItemプロパティ(引数付きプロパティ、PowerShellではParameterizedPropertyと呼ばれる)にプロパティ名を引数として渡すと、その値が返却されます。PowerShellでは引数付きの既定プロパティはC#のインデクサと同様の構文で値が参照できるので、$jsobj[“items”]のように[]でアクセス可能です。これは$jsobj.Item(“items”)としても同様の結果が得られます(プロパティなのに()で値を取るところはVB風味?)。

配列の列挙ですが、JSObjectと、オブジェクトが配列の場合はその派生クラスであるJSArrayObjectクラスになりますが、これらはIEnumerableインターフェースを実装しているのでforeachで列挙が可能です。しかしここで列挙されるのはあくまでkey、すなわちプロパティ名の方です。値が列挙されるわけではありません。ご存じのとおり、JavaScriptの配列、連想配列、オブジェクトは同じものであり、配列の場合はkeyが配列インデックスの数字に相当します。そのため配列をforeachしても「0,1,2…」という数字が列挙されるだけです。

なので配列を列挙する場合は、この例のように、一旦JSArrayObjectを変数で受けて、それに対してforeachし、列挙した要素(インデックスの数字)をJSArrayObject.Itemプロパティの引数に与えることで、JS配列要素の値を取得してやる必要があると思います。

1のXMLを経由する方法のように、JSONをドット演算子でプロパティアクセスできないのは残念ですが、.NET 3.5が入っていない(がPowerShell 2.0は入ってる)環境では、それほど手間をかけずJSONを扱えるという点でそれなりに有用ではないでしょうか。

JSObjectもXMLみたいに型アダプタがあればプロパティアクセスできるようになるでしょうし、Add-Memberコマンドレットを駆使してJSObjectに動的にプロパティを追加する関数を書くのもいいかもしれません。が、そこまでいくとやはりお手軽からはかけ離れてしまうので今回はこの辺にとどめておきましょう。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2011/05/14/199047.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

2008/05/19

PS C:\Users\daisuke> $a=new-object System.Management.ManagementObject "\\.\root\
cimv2:Win32_LogicalDisk.DeviceID='D:'"
PS C:\Users\daisuke> $a


DeviceID     : D:
DriveType    : 3
ProviderName :
FreeSpace    : 139523874816
Size         : 237961736192

こんな感じ。Get-WMIObjectではできない気がします。

keyを指定する場合

PS C:\Users\daisuke> $a=new-object System.Management.ManagementObject "Win32_Log
icalDisk.DeviceID=""D:"""
元記事:http://blogs.wankuma.com/mutaguchi/archive/2008/05/19/138394.aspx

2008/01/21

PS C:\script> $hash = @{a=1;b=2}
PS C:\script> foreach ($entry in $hash){$entry.Key + ":" + $entry.Value }
:

このようにPowerShellだと$entryにはDictionaryEntryは格納されないんです。何が格納されているかというと、要素数1のHashtableなんですね。ちょっと注意が必要です。

これを意図したようにコードを動かすには

PS C:\script> foreach ($key in $hash.keys){$key + ":" + $hash[$key] }
a:1
b:2

てな感じ

元記事:http://blogs.wankuma.com/mutaguchi/archive/2008/01/21/118432.aspx

2006/12/08

コマンドレットオンラインヘルプ作成
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


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

Books

Twitter