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でコンソールの入出力を制御するのは、こういう単純なものなら意外と簡単にできるという、サンプルにはなるかと思います。

2014/12/11

MMLとは

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

突然ですが、MMLってご存知ですか? MMLとはMusic Macro Languageの略で、その名の通り音楽演奏データを記述するための言語です。BASICのPLAY文で使うあれです。MIDIファイルに変換するコンパイラもありましたよね。

たとえば、「ねこふんじゃった」の最初の部分をMMLで書くとこんな感じです。MMLはいろんな記法があるのですが、よくあるのはこういうのです。

e-d-<g-4>g-rg-4e-d-<g-4>g-rg-4e-d-<g-4>g-4<e-4>g-4<d-4>frf4
e-d-<d-4>frf4e-d-<d-4>frf4e-d-<d-4>f4<e-4>f4<g-4>g-rg-4

ドの音をc、レの音をd、のように以下シの音をbまでアルファベット音階で割り当てます。半音上げる(シャープ)場合は音名のあとに+、半音下げる(フラット)場合は-を付与します。音の長さは4分音符なら4、8分音符なら8と書きます。付点は数字のあとに.を付けます。数字省略時は8分音符と解釈するのが普通のようです。オクターブを上げる場合は>、下げる場合は<と書きます(コンパイラによっては逆の解釈をする)。休符はrです。

本物のMMLでは、音色の指定だとかトラックの割り当てだとか、もっとたくさん命令があるんですが、基本はだいたいこんなもんです。

PowerShellで音を鳴らすには

PowerShellで音を鳴らすには、.NETのSystem.Console.Beepメソッドを使うのが一番手っ取り早いです。ビープ音ですね。比較的古いWindowsではハードウェアのビープスピーカーから、比較的新しいWindowsではサウンドデバイス上で鳴ります。今回のMMLシーケンサーも結局はBeepメソッドです。

そしてぎたぱそ先生がずっと以前に書いておられます。なので今回のはまあ、MMLシーケンサー部分を除けば二番煎じなんですけどもね。(そしてSmallBasicLibraryを使えば実はMML演奏もできるので、車輪の再発明でもあるんですが)

ちなみにwavやmp3なんかを鳴らすには、Windows Media PlayerをCOM経由で実行する方法や.NET(WPF)のSystem.Windows.Media.MediaPlayerクラスを使う方法などがあります。

音程の決め方

Beepメソッドでは第1引数にビープ音の周波数をHzで、第2引数に再生時間をミリ秒で指定します。つまりは音程(音の高さ)はHzで指定する必要があります。ここでドの音は何ヘルツで、みたいな対応表をどこかから探してきてそのデータを使ってもいいんですが、今回はせっかくなので、PowerShellで計算して求めてみます。

まず、ある音程の一つ上のオクターブの音程の間の周波数比は、1:2です。

ピアノ等の鍵盤楽器が近くにあれば、それを見てもらえば分かると思いますが、1オクターブには12の音程が含まれています。1オクターブの周波数比を12個均等に分割します(この周波数の決め方を平均律といいます)。つまり、隣り合う音程の周波数比は、1:12√2となるわけです。

さて、音程の周波数比はこれでわかりました。あとは基準となる音の周波数が分かれば、全ての音の周波数が計算できるわけです。時代や地域、演奏する楽器等によって微妙に違ってきますが、現代日本においては普通は、A(ラ)=440Hzが基準周波数です。

あとは単純にかけ算して周波数を求めて連想配列に入れておくだけです。コードにすればこんな感じですね。

$baseFrequency = 440.0
$pitches = @{
            "c" = $baseFrequency * [math]::Pow(2, 3/12)
            "d" = $baseFrequency * [math]::Pow(2, 5/12)
...
}

あと、+で半音上がる場合は[math]::Pow(2, 1/12)をかけ、-で半音下がる場合は割ればいいだけです。(別に$pitchesテーブルで最初から定義してもいいかもですが)

音価の決め方

音価というのは音の長さです。Beepメソッドの第2引数にはミリ秒で実際の時間を指定する必要があるので、これも計算で求めてやります。

4分音符というのは、1小節(全音符)を4分割、つまり1/4した音価を持ちます。同様に8分音符は1/8ですね。付点がつくと音価は1.5倍になります。

ではたとえば4分音符の具体的な音価はどうやって定めるのでしょうか。それを決めるのがテンポと拍子です。

テンポは1分間(つまり60000ミリ秒)の拍数(BPM、Bit Per Minuteともいう)で定義されます。今回作るシーケンサーは4/4拍子、すなわち1拍=4分音符とする4拍子固定とするので、1分間に4分音符がBPM回含まれることになります。

つまり、4分音符の音価=60000ミリ秒/BPMの値、全音符の音価=60000*4ミリ秒/BPMの値 となるわけです。

具体的なコードは…略します。単なるかけ算と割り算だけなんで。

MMLのパース

えっと時間がなくなってきたので、駆け足で説明します。

MMLのパースとは、要するに最初の例のようなMML(テキストデータ)から、実際に演奏する音程と音価の組み合わせのデータとして組み立てる作業になります。

今回のシーケンサーでは、繰り返し記号等はサポートしないので、単にMMLを一文字ずつ読み込んで、音名(cなど)を見つけたら、その後の文字を読んで、+があれば半音上げるなどして周波数を求め、その後に数字があればn分音符とみなし、先ほどの計算式から実際の音価を求めてやる。というのを全部の音でくり返すだけです。

この処理をやっているのがConvertFrom-MMLという関数です。MMLテキストを入力してやると、周波数(Frequency)や発音時間(Duration)などをプロパティとして含んだpscustomobject(Noteオブジェクト)を出力します。(Noteとは音符のことです)

MMLの演奏

パースしたMMLを実際に演奏するのがInvoke-Beep関数です。入力は、ConvertFrom-MMLで生成したNoteオブジェクトです。実際の処理は至って単純で、 [System.Console]::Beep($note.Frequency, $note.Duration)するだけです(えー)。あ、あと休符の時はDuration分だけSleepを入れてやっています。

MMLの>と<の意味を逆転する-Reverseスイッチ、BPMを指定する-Bpmパラメータ(デフォルト120)なんかも用意してます。

実際にはMMLから直接演奏できるように、Invoke-Mmlというラッパー関数を用意しています。

冒頭のねこふんじゃったのMMLを再生するにはこんな感じにします。

$s=@"
e-d-<g-4>g-rg-4e-d-<g-4>g-rg-4e-d-<g-4>g-4<e-4>g-4<d-4>frf4
e-d-<d-4>frf4e-d-<d-4>frf4e-d-<d-4>f4<e-4>f4<g-4>g-rg-4
"@
Invoke-Mml $s
コード
function ConvertFrom-MML
{
    [CmdletBinding()]
    param(
        [parameter(ValueFromPipeline=$true)][string[]]$mml,
        [int]$bpm = 120,
        [switch]$reverse = $false
    )
    begin
    {
        $baseFrequency = 440.0
        $scaleRatio = [math]::Pow(2, 1/12)
        $baseDuration = 60000 * 4 / $bpm
        $pitches = @{
            "c" = $baseFrequency * [math]::Pow(2, 3/12)
            "d" = $baseFrequency * [math]::Pow(2, 5/12)
            "e" = $baseFrequency * [math]::Pow(2, 7/12)
            "f" = $baseFrequency * [math]::Pow(2, 8/12)
            "g" = $baseFrequency * [math]::Pow(2, 10/12)
            "a" = $baseFrequency * [math]::Pow(2, 12/12)
            "b" = $baseFrequency * [math]::Pow(2, 14/12)
            "r" = -1
        }
        $currentOctave = 0
    }

    process
    {
        $chars = [string[]]$mml.ToCharArray()

        0..($chars.Length - 1)|%{
            if($null -ne $pitches[$chars[$_]])
            {
                $frequency = $pitches[$chars[$_]]
                $suffix = ""
            
                $denominator = ""
                $dot = $false

                if($_ -lt $chars.Length-1)
                {
                    switch($chars[($_ + 1)..($chars.Length - 1)])
                    {
                        {$pitches.Contains($_)}
                            {break}
                        "+"
                            {$frequency *= $scaleRatio; $suffix += $_}
                        "-"
                            {$frequency /= $scaleRatio; $suffix += $_}
                        {$_ -match "\d"}
                            {$denominator += $_}
                        "."
                            {$dot = $true}
                    }
                }

                $frequency *= [math]::Pow(2, $currentOctave)

                $denominator = [int]$denominator
                if($denominator -eq 0){$denominator = 8}
                $multiplier = 1 / $denominator
                
                if($dot){$multiplier *= 1.5}
                $duration = $baseDuration * $multiplier

                if($frequency -le 0){$on = $false}else{$on = $true}
                [pscustomobject]@{
                    Frequency = [int]$frequency
                    Duration = $duration
                    Note = "$($chars[$_])$suffix$denominator$(if($dot){"."})"
                    Octave = $currentOctave
                    Pitch = $chars[$_]
                    Suffix = $suffix
                    Denominator = "$denominator$(if($dot){"."})"
                    On = $on
                }
            }
            elseif($chars[$_] -eq ">")
            {
                if($reverse)
                {
                    $currentOctave--
                }
                else
                {
                    $currentOctave++
                }
            }
            elseif($chars[$_] -eq "<")
            {
                if($reverse)
                {
                    $currentOctave++
                }
                else
                {
                    $currentOctave--
                }
            }
        }
    }
}

function Invoke-Beep
{
    [CmdletBinding()]
    param([parameter(ValueFromPipeline = $true)][psobject[]]$note)
    
    process
    {
        if($note.On)
        {
            [System.Console]::Beep($note.Frequency, $note.Duration)
        }
        else
        {
            Start-Sleep -Milliseconds $note.Duration
        }
    }
}

function Invoke-Mml
{
    [CmdletBinding()]
    param(
        [string]$mml,
        [int]$bpm,
        [switch]$reverse
    )
    
    ConvertFrom-Mml @PSBoundParameters|Invoke-Beep
}
おわりに

今回はMMLをパースしてBeepを演奏するスクリプトをPowerShellで作ってみました。これをシーケンサーというのはおこがましいと思いますが、まあたまにはこういう柔らかいネタもいいんじゃないでしょうか。ドレミの音がどうやって決められてるのかというのも、もしかして話のネタくらいにはなるかも。

そしてこれ、シーケンサーといいつつ、入力機能がないですよね。それについては次回やります。

PowerShellアドベントカレンダー2014はまだまだ残席ございます。ぜひ、あなたのPowerShell話を聞かせて下さい。

2014/12/01

はじめに

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

次期バージョンのPowerShell 5.0について、そろそろ情報が出回ってきました。現在のところWindows Management Framework 5.0 Preview November 2014、もしくはWindows 10 Technical PreviewWindows Server Technical Previewに同梱のもので試すことができます。

v5での新機能、改善点は多岐に上ります。OneGet / PowerShellGet / クラス定義 / DSC機能増強 / ODataエンドポイントのコマンドレット化 / zipファイル / シンボリックリンク 等々。詳しくは、リリースノートが一番充実しているかと思います。日本語だとぎたぱそ氏の記事がまとまっているかと思ます。

さて、ここまで挙げた新機能や改善点は、とても順当でまっとうな進化点なのですが、v5にはちょっと異彩を放つ新機能がしれっと追加されています。それが、Auto-Generated Example-Driven Parsing です。

Auto-Generated Example-Driven Parsing とは

CSV、JSON、XMLのような既知のフォーマットではないが、何らかの法則性のあるテキストデータがあるとします。そんなテキストデータは(不幸なことに)割と世の中にあふれていますが、そのままでは(人が読む以外には)利用できないので、データとして扱うには、解析し、レコード(プロパティ:プロパティ値)として再構築する必要があります。

しかしながらフォーマットが既知のものではないため、既存のパーサーを使って解析することはできません。

従来のアプローチだと、このようなデータに対しては、まずユーザー(人間)がデータの法則性を読み取り、その法則をコンピュータに分かる表現(コードや正規表現など)に変換してやる必要がありました。

Auto-Generated Example-Driven Parsing とは、事前にユーザーがテキストデータの一部分のみを取り出し、各項目に対してプロパティ名を指示したデータ(テンプレート)を用意しておくと、元のテキストデータとユーザーが用意したテンプレートから法則性を解析し、元のテキストデータ全体を自動的にテキストデータからオブジェクトに変換してくれる機能です。

Auto-Generated Example-Driven Parsing とはもともとMicrosoft Research で研究されているFlashExtract というデータ解析手法の PowerShell コマンドレット(ConvertFrom-String)による実装になります。ConvertFrom-StringData じゃないですよ。全然別物です。これうっかりしてるとスルーしてしまいそうです。

具体例

たとえば、こんなデータがあったとします。

山内 佳乃 (やまうち よしの)
生年月日...1982/1/27 (32歳)、女性
田畑 真帆 (たばた まほ)
生年月日...1966/4/14 (48歳)、女性
三好 一樹 (みよし かずき)
生年月日...1972/7/10 (42歳)、男性
酒井 幸平 (さかい こうへい)
生年月日...1954/3/1 (60歳)、男性
藤島 恵子 (ふじしま けいこ)
生年月日...1969/5/4 (45歳)、女性
加藤 美優 (かとう みゅう)
生年月日...1986/12/8 (27歳)、女性
金谷 康文 (かなや やすふみ)
生年月日...1983/10/7 (31歳)、男性
岸本 紗季 (きしもと さき)
生年月日...1984/5/16 (30歳)、女性
永野 ケンイチ (ながの けんいち)
生年月日...1961/7/8 (53歳)、男性
小関 三郎 (こぜき さぶろう)
生年月日...1975/1/22 (39歳)、男性
山岸 光 (やまぎし ひかる)
生年月日...1939/2/13 (75歳)、女性
黒谷 恵麻 (くろたに えま)
生年月日...1949/2/13 (65歳)、女性

名前や生年月日が書かれたデータで、一応、法則性はあるようです。が、これをまともにパースしようと思うと、2行ごとに切り出して、正規表現を書いて…と、ちょっと面倒ですね。

ちなみにこのダミーデータ作成にはなんちゃって個人情報を使わせていただきました。CSVで出力した後、以下のようなスクリプトでわざわざ醜く変形しました。

Import-Csv -Encoding Default -Path dummy.cgi|%{"$($_.名前) ($($_.ふりがな))`n生年月日...$($_.誕生日) ($($_.年齢)歳)、$($_.性別)性"}|set-content -Encoding UTF8 -Path dummy.txt

さて、このテキストデータに対し、Auto-Generated Example-Driven Parsingで用いるテンプレートを書いてやりましょう。たとえば、以下のように適当に3件(ここでは3〜5個目のレコード)抜き出して、プロパティ名をつけてやります。赤字が、手動で元データに付与した文字列です。

{Name*:三好 一樹} ({Furigana:みよし かずき})
生年月日...{BirthDay:1972/7/10} ({Age:42}歳)、{Sexuality:}{Name*:酒井 幸平} ({Furigana:さかい こうへい})
生年月日...{BirthDay:1954/3/1} ({Age:60}歳)、{Sexuality:}{Name*:藤島 恵子} ({Furigana:ふじしま けいこ})
生年月日...{BirthDay:1969/5/4} ({Age:45}歳)、{Sexuality:}

みて頂ければ分かると思いますが、基本は、各データ項目に対して、{プロパティ名:データ}のように指定してやるだけです。主キーとなるデータ項目にはプロパティ名の後に「*」をつけてやります。こうやって作ったテンプレートをtemplate.txtと名前を付けて保存しましょう。

元データとテンプレートが揃ったので、あとは以下のようにしてConvertFrom-Stringコマンドレットを実行するだけです。

image

テンプレートを元に、元テキストに含まれるすべてのデータが、プロパティ値を持ったオブジェクトデータに変換されていることが分かるかと思います。これちょっとすごくないですか?

まとめ

Auto-Generated Example-Driven Parsingは個人的には、非常に面白い機能だと感じています。コンピュータに対して、「手本見せるよ、これはこう、これはこう。わかった? じゃ、あとは同じようにまとめといてね!」というのができるようになったわけで、ちょっと未来を感じました。

研究所レベルの研究成果を、製品として実装した初の例が、PowerShellだったというのも面白味を感じます。

ただ、CSVでもJSONでもXMLでもない、わけのわからない謎フォーマットで保存されたテキストデータを解析しなきゃならない事態というのは、そもそも不幸な状況であることも、また事実かと思います。

ConvertFrom-Stringは、そんな訳の分からないものを撲滅して、今度こそまともなフォーマットのデータに変換して保存するための、最終兵器のようなものかもしれません。

なお、Auto-Generated Example-Driven Parsingでは他にもプロパティに型を指定したり、部分的に正規表現を用いたり、階層構造を持つデータにも対応してたりと、かなり色々なことができるようになっています。ぜひ、v5環境を整えて、ConvertFrom-Stringを試してみてください。

さてさて、PowerShell Advent Calendar 2014、今年は参加者が少なく、完走はかなり危ぶまれますが、できるところまで行きたいですね! これをお読みのあなたの記事が読みたいです! ぜひ、ご参加いただけると幸いです。

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

2006/12/09

元記事はhttp://winscript.s41.xrea.com/mt/archives/2005/09/post_5.htmlです。

クリップボードを扱う.NET Frameworkのクラスに、Clipboardクラスがあります。そのSetDataObjectメソッドを使うとクリップボードにテキストデータを格納できる、はずです。が、

[void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 
[System.Windows.Forms.Clipboard]::SetDataObject("あああ")

のようなスクリプトを実行すると、

Exception calling "SetDataObject" with "1" argument(s): "OLE が呼び出される前に、現在のスレッドが Single Thread Apartment (STA) モードに設定されていなければなりません。Main 関数に STAThreadAttribute が設定されていることを確認してください。".

というエラーが出てしまいます。MSHでMain関数にSTAThreadAttributeを指定するにはどうすれば良いのでしょうか? ちなみに

[System.Threading.Thread]::CurrentThread.ApartmentState = 
[System.Threading.ApartmentState]::STA

のようにしてもSTAにはなってくれませんでした。

クリップボードアクセスは、newpopsさんが提案されている、IEのCOMコンポーネントを使う方法で可能になります。

PowerShell Memo - 文字列をクリップボードにコピーする
http://d.hatena.ne.jp/newpops/20061104/p1

元記事:http://blogs.wankuma.com/mutaguchi/archive/2006/12/09/49650.aspx

Copyright © 2005-2016 Daisuke Mutaguchi All rights reserved

mailto: mutaguchi at roy.hi-ho.ne.jp

Awards

Books

Twitter