PowerShellで作るdeliciousクライアント ラスト

前回の続きです。

タグの一覧を取得

タグの一覧を取得する機能を実装します。

まずは「DeliciousTag.vb」というファイル名でクラスファイルを追加します。

DeliciousTag.vb
Public Class DeliciousTag
     Private _name As String

     Public Property Name() As String
         Get
             Return _name
         End Get
         Set(ByVal value As String)
             _name = value
         End Set
     End Property

     Private _count As Integer
     Public Property Count() As Integer
         Get
             Return _count
         End Get
         Set(ByVal value As Integer)
             _count = value
         End Set
     End Property
End Class

このクラスはタグの情報を格納します。

DeliciousクラスにGetTagsという名前でメソッドを定義します。

Delicious.vb
Function GetTags() As List(Of DeliciousTag)
     Dim address = "https://api.del.icio.us/v1/tags/get"

     Dim contents = GetContents(address)
     Dim xml = XDocument.Parse(contents)
     Dim results = From ele In xml.Descendants("tag") _
                   Select New DeliciousTag With { _
                     .Name = ele.Attribute("tag"), _
                     .Count = ele.Attribute("count") _
                   }

     Return results.ToList()
End Function

Private Function GetContents(ByVal address As String)
     Dim wc = New Net.WebClient()
     wc.Credentials = _credential
     wc.Encoding = Text.Encoding.UTF8

     Return wc.DownloadString(address)
End Function

処理的には特に説明する必要がありませんね。

ちなみにWebClientを使ってリクエストを送る処理をGetContentsというメソッドに分離しておきました。

後は、このメソッドをプロバイダークラスから呼び出すように変更します。

GetChildItemsParametersクラスにTagOnlyというプロパティを追加します。 このパラメータが指定された時だけタグ一覧を出力するという感じです。

DeliciousProvider.vb

Private Class GetChildItemsParameters
     Private _start As Integer = 0
     <Parameter(Position:=0)> _
     Public Property Start() As Integer
         Get
             Return _start
         End Get
         Set(ByVal value As Integer)
             _start = value
         End Set
     End Property

     Private _count As Integer = 20
     <Parameter(Position:=1)> _
     Public Property Count() As Integer
         Get
             Return _count
         End Get
         Set(ByVal value As Integer)
             _count = value
         End Set
     End Property

     Private _tag As String()
     <Parameter(Position:=2)> _
     Public Property Tag() As String()
         Get
             Return _tag
         End Get
         Set(ByVal value As String())
             _tag = value
         End Set
     End Property

     Private _tagOnly As SwitchParameter
     <Parameter()> _
     Public Property TagOnly() As SwitchParameter
         Get
             Return _tagOnly
         End Get
         Set(ByVal value As SwitchParameter)
             _tagOnly = value
         End Set
     End Property
End Class

GetChildItemsメソッドを以下のように変更します。

DeliciousProvider.vb

Protected Overrides Sub GetChildItems(ByVal path As String, ByVal recurse As Boolean)
     Dim dynaParams = CType(DynamicParameters, GetChildItemsParameters)

     If dynaParams.TagOnly.IsPresent Then
         For Each tag In Me.Delicious.GetTags()
             WriteItemObject(tag, path, False)
         Next
     Else
         For Each bookmark In Me.Delicious.GetBookmarks(dynaParams.Start, dynaParams.Count, dynaParams.Tag)
             WriteItemObject(bookmark, path, False)
         Next
     End If
End Sub

TagOnlyプロパティが指定された時だけタグを取得して、出力していますね。

タグの情報を見易く表示する為に書式設定ファイルにDeliciousTagクラスの設定を追加しておきます。

psdelicious.format.ps1xml

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
     <ViewDefinitions>
         <View>
             <Name>Tags</Name>
             <ViewSelectedBy>
                 <TypeName>PSDelicious.DeliciousTag</TypeName>
             </ViewSelectedBy>
             <TableControl>
                 <TableHeaders>
                     <TableColumnHeader>
                         <Label>タグ名</Label>
                     </TableColumnHeader>
                     <TableColumnHeader>
                         <Label>タグ数</Label>
                     </TableColumnHeader>
                 </TableHeaders>
                 <TableRowEntries>
                     <TableRowEntry>
                         <TableColumnItems>
                             <TableColumnItem>
                                 <PropertyName>Name</PropertyName>
                             </TableColumnItem>
                             <TableColumnItem>
                                 <PropertyName>Count</PropertyName>
                             </TableColumnItem>
                         </TableColumnItems>
                     </TableRowEntry>
                 </TableRowEntries>
             </TableControl>
         </View>
         <View>
             <Name>Tags</Name>
             <ViewSelectedBy>
                 <TypeName>PSDelicious.DeliciousBookmark</TypeName>
             </ViewSelectedBy>
             <ListControl>
                 <ListEntries>
                     <ListEntry>
                         <ListItems>
                             <ListItem>
                                 <Label>タイトル</Label>
                                 <PropertyName>Title</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>URL</Label>
                                 <PropertyName>Url</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>コメント</Label>
                                 <PropertyName>Comment</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>タグ</Label>
                                 <PropertyName>Tags</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>更新日付</Label>
                                 <PropertyName>Date</PropertyName>
                             </ListItem>
                         </ListItems>
                     </ListEntry>
                 </ListEntries>
             </ListControl>
         </View>
     </ViewDefinitions>
</Configuration>

これで、以下のようにするとタグの一覧を表示することができます。

PS  > dir -TagOnly

タグ名                                  タグ数
------                                  ------
.NET                                    67
ADO.NET                                 3
AIR                                     2
AOP                                     1
API                                     2
ASP.NET                                 19
ActionScript                            1
Ajax                                    2
Apache                                  3
AppEngine                               4

残りの機能を一気に実装します。

タグの編集と削除

DeliciousクラスにDeleteTagRenameTagというメソッドを追加します。

Delicious.vb
Sub DeleteTag(ByVal tag As String)
     Dim address = "https://api.del.icio.us/v1/tags/delete?tag=" + Web.HttpUtility.UrlEncode(tag)

     Dim contents = GetContents(address)
     Dim result = GetResult(contents)

     If result <> "done" Then Throw New ArgumentException(result)
End Sub

Sub RenameTag(ByVal oldValue As String, ByVal newValue As String)
     Dim address = String.Format( _
         "https://api.del.icio.us/v1/tags/rename?old={0}&new={1}", _
         Web.HttpUtility.UrlEncode(oldValue), Web.HttpUtility.UrlEncode(newValue) _
     )
     Dim contents = GetContents(address)
     Dim result = GetResult(contents)

     If result <> "done" Then Throw New ArgumentException(result)
End Sub

Private Shared Function GetResult(ByVal contents As String) As String
     Dim result = Text.RegularExpressions.Regex.Match( _
         contents, "<result>(?<msg>.*?)</result>", Text.RegularExpressions.RegexOptions.Multiline _
     )
     Return IIf(result.Success, result.Groups("msg").Value, String.Empty)
End Function

見たまんまですね。

RemoveItemParametersクラスにTagNameというプロパティを追加します。 このパラメータが指定されたらタグを削除するようにします。

DeliciousProvider.vb
Private Class RemoveItemParameters
     Private _bookmark As DeliciousBookmark
     <Parameter(Position:=0, ValueFromPipeline:=True)> _
     Public Property Bookmark() As DeliciousBookmark
         Get
             Return _bookmark
         End Get
         Set(ByVal value As DeliciousBookmark)
             _bookmark = value
         End Set
     End Property

     Private _tagName As String
     <Parameter()> _
     Public Property TagName() As String
         Get
             Return _tagName
         End Get
         Set(ByVal value As String)
             _tagName = value
         End Set
     End Property
End Class

RemoveItemメソッドを以下のように変更します。

DeliciousProvider.vb
Protected Overrides Sub RemoveItem(ByVal path As String, ByVal recurse As Boolean)
     Dim dynaParams = CType(Me.DynamicParameters, RemoveItemParameters)

     If String.IsNullOrEmpty(dynaParams.TagName) Then
         Me.Delicious.DeleteBookmark(dynaParams.Bookmark.Url)
     Else
         Me.Delicious.DeleteTag(dynaParams.TagName)
     End If
End Sub

これで以下のようにして、タグを削除する事ができます。

PS delicious:\> del -Tag .NET

SetItemParametersクラスにTagというプロパティを追加します。このパラメータが指定されたらタグ名を変更するようにします。

DeliciousProvider.vb
Private Class SetItemParameters
     Private _bookmark As DeliciousBookmark
     <Parameter(Position:=0, ValueFromPipeline:=True)> _
     Public Property Bookmark() As DeliciousBookmark
         Get
             Return _bookmark
         End Get
         Set(ByVal value As DeliciousBookmark)
             _bookmark = value
         End Set
     End Property

     Private _share As SwitchParameter
     <Parameter(Position:=1)> _
     Public Property Share() As SwitchParameter
         Get
             Return _share
         End Get
         Set(ByVal value As SwitchParameter)
             _share = value
         End Set
     End Property

     Private _tag As SwitchParameter
     <Parameter()> _
     Public Property Tag() As SwitchParameter
         Get
             Return _tag
         End Get
         Set(ByVal value As SwitchParameter)
             _tag = value
         End Set
     End Property
End Class

SetItemメソッドを以下のように変更します。

DeliciousProvider.vb
Protected Overrides Sub SetItem(ByVal path As String, ByVal value As Object)
     Dim dynaParams = CType(Me.DynamicParameters, SetItemParameters)

     If dynaParams.Tag.IsPresent Then
         Me.Delicious.RenameTag(path, value.ToString())
     ElseIf Not value Is Nothing Then
         Me.Delicious.AddBookmark( _
             dynaParams.Bookmark, replace:=True, share:=dynaParams.Share.IsPresent _
         )
     End If
End Sub

これで以下のようにして、タグ名を変更することができます。

PS delicious:\> si -Tag .net .net-framework

これでだいたい当初予定していた機能は実装できたはずです。インターフェースはまだまだ煮詰める余地がありますが、普通に使えると思います。

最後の方は駆け足になってしまいましたが、PowerShellでのプロバイダ開発の実際のやり方がなんとなくつかめたのではないでしょうか。今回は基本的な機能しか実装していませんが、アイデア次第でいくらでも便利にする事ができます。

こういった拡張性の高さもPowerShellのプロバイダ開発の魅力だと思います。皆さんも試しに作ってみて下さい。楽しいですよ!!

PowerShellで作るdeliciousクライアント その4

前回の続きです。

ブックマークの削除

今回はブックマークの削除機能から実装します。

DeliciousクラスにDeleteBookmarkという名前で、引数に削除するブックマークのURLを指定するメソッドを定義します。

Delicious.vb

Sub DeleteBookmark(ByVal url As String)
     Dim address = "https://api.del.icio.us/v1/posts/delete?url=" + Web.HttpUtility.UrlEncode(url)

     Dim wc = New Net.WebClient()
     wc.Credentials = _credential
     wc.Encoding = Text.Encoding.UTF8

     Dim contents = wc.DownloadString(address)
     Dim resultMsg = GetResultMessage(contents)

     If resultMsg <> "done" Then Throw New ArgumentException(resultMsg)
End Sub

Private Shared Function GetResultMessage(ByVal contents As String) As String
     Dim result = Text.RegularExpressions.Regex.Match( _
         contents, "<result code=""(?<msg>.*?)"" />", Text.RegularExpressions.RegexOptions.Multiline _
     )
     Return IIf(result.Success, result.Groups("msg").Value, String.Empty)
End Function

レスポンスのXMLから結果メッセージを取得する処理をGetResultMessageというメソッドに分離しておきました。

Remove-Itemコマンドレットのパラメータクラスをインナークラスで定義しておきます。

DeliciousProvider.vb
Private Class RemoveItemParameters
     Private _bookmark As DeliciousBookmark
     <Parameter(Mandatory:=True, Position:=0, ValueFromPipeline:=True)> _
     Public Property Bookmark() As DeliciousBookmark
         Get
             Return _bookmark
         End Get
         Set(ByVal value As DeliciousBookmark)
             _bookmark = value
         End Set
     End Property
End Class

後は、RemoveItemRemove-Itemコマンドレットに対応)メソッドをオーバーライドして、その中でDeleteBookmarkメソッドを呼び出すだけです。

DeliciousProvider.vb

Protected Overrides Sub RemoveItem(ByVal path As String, ByVal recurse As Boolean)
     Dim dynaParams = CType(Me.DynamicParameters, RemoveItemParameters)

     Me.Delicious.DeleteBookmark(dynaParams.Bookmark.Url)
End Sub

Protected Overrides Function RemoveItemDynamicParameters(ByVal path As String, ByVal recurse As Boolean) As Object
     Return New RemoveItemParameters()
End Function

これで以下のようにして、ブックマークを削除する事ができるようになります。

PS delicious:\> dir -Count 10 | del

次の機能に行く前に少しGet-ChildItemコマンドレットに機能を追加しておきます。

DeliciousクラスのGetBookmarksメソッドを以下のように変更します。

Delicious.vb

Function GetBookmarks(ByVal start As Integer?, ByVal count As Integer?, ByVal tags As String()) As List(Of DeliciousBookmark)
     Dim address = "https://api.del.icio.us/v1/posts/all"
     Dim queries = New Dictionary(Of String, Object)()
     If start.HasValue Then queries.Add("start", start.Value)
     If count.HasValue Then queries.Add("results", count.Value)
     If Not tags Is Nothing AndAlso tags.Length > 0 Then queries.Add("tag", String.Join(" ", tags))

     Dim wc = New Net.WebClient()
     wc.Credentials = _credential
     wc.Encoding = Text.Encoding.UTF8

     Dim contents = wc.DownloadString(JoinQueries(address, queries))
     Dim xml = XDocument.Parse(contents)
     Dim results = From ele In xml.Descendants("post") _
                   Select New DeliciousBookmark With { _
                     .Title = ele.Attribute("description"), _
                     .Url = ele.Attribute("href"), _
                     .Tags = CType(ele.Attribute("tag"), String).Split(" "), _
                     .Comment = ele.Attribute("extended"), _
                     .Date = ele.Attribute("time") _
                   }

     Return New List(Of DeliciousBookmark)(results)
End Function

「tags」という引数を追加して、タグ名でブックマークを検索できるようにしました。

Get-ChildItemコマンドレットのパラメータクラスにもTagプロパティを追加しておきます。

DeliciousProvider.vb
Private Class GetChildItemsParameters
     Private _start As Integer = 0
     <Parameter(Position:=0)> _
     Public Property Start() As Integer
         Get
             Return _start
         End Get
         Set(ByVal value As Integer)
             _start = value
         End Set
     End Property

     Private _count As Integer = 20
     <Parameter(Position:=1)> _
     Public Property Count() As Integer
         Get
             Return _count
         End Get
         Set(ByVal value As Integer)
             _count = value
         End Set
     End Property

     Private _tag As String()
     <Parameter(Position:=2)> _
     Public Property Tag() As String()
         Get
             Return _tag
         End Get
         Set(ByVal value As String())
             _tag = value
         End Set
     End Property
End Class

GetChildItemsメソッドを以下のように変更しておきます。

DeliciousProvider.vb

Protected Overrides Sub GetChildItems(ByVal path As String, ByVal recurse As Boolean)
     Dim dynaParams = CType(DynamicParameters, GetChildItemsParameters)

     For Each bookmark In Me.Delicious.GetBookmarks(dynaParams.Start, dynaParams.Count, dynaParams.Tag)
         WriteItemObject(bookmark, path, False)
     Next
End Sub

こうする事で、例えば以下のように「.net」というタグの付いたブックマークを10件取ってきて、それを一度に削除するという作業が一行でできるようになります。

PS delicious:\> dir -Tag .net -Count 10 | del

たいした事じゃないですけど、なんだか夢が広がりますね!!

次はタグ関係を一気にやります。

PowerShellで作るdeliciousクライアント その3

前回の続きです。

前回でブックマークの一覧を取得できるようにしましたが、あのままではURLがわかってもブラウザで開くという作業が面倒なので、その辺を簡略化できるように与えられたURLを既定のブラウザで開くコマンドレットを作っておきます。

「Commands」というフォルダを作成して、その中に「BrowseObjectCommand.vb」というファイル名でクラスファイルを追加します。

Commands\BrowseObjectCommand.vb
Namespace Commands
     <Cmdlet("Browse", "Object")> _
     Public Class BrowseObjectCommand
         Inherits PSCmdlet

         Private _url As String
         <Parameter(Mandatory:=True, Position:=0, ValueFromPipelineByPropertyName:=True)> _
         Public Property Url() As String
             Get
                 Return _url
             End Get
             Set(ByVal value As String)
                 _url = value
             End Set
         End Property

         Protected Overrides Sub ProcessRecord()
             Process.Start(Me.Url)
         End Sub
     End Class
End Namespace

PSCmdletクラスを継承します。コマンドレットである事を示す為にCmdletAttribute属性でマークしておきます。引数にはコマンドレットの動詞と名詞を指定します(Browse-Objectという書式になる)。

コマンドレットが実行されるとProcessRecordメソッドが呼び出されるので、これをオーバーライドして、その中ではURLプロパティの値をシェルで呼び出すだけです。

URLプロパティはParameterAttribute属性でマークして、必須属性(Mandatory:=True)、位置が0(Position:=0)、値をパイプラインから取得し、そのオブジェクトのプロパティと結びつけます(ValueFromPipelineByPropertyName:=True)。

このコマンドレットを使うと以下のようにして、URLをブラウザで開くことができるようになります。

PS delicious:\> dir -Count 1 | Browse-Object

話を元に戻しましょう。

ブックマークの追加・編集

ブックマークの追加・編集機能を実装します。

DeliciousクラスにAddBookmarkという名前で引数にDeliciousBookmark型を受け取るメソッドを定義します。

Delicious.vb

Sub AddBookmark(ByVal bookmark As DeliciousBookmark, _
                 Optional ByVal share As Boolean = False, Optional ByVal replace As Boolean = False)
     Dim address = "https://api.del.icio.us/v1/posts/add"
     Dim queries = New Dictionary(Of String, Object)()
     If String.IsNullOrEmpty(bookmark.Url) Then Throw New ArgumentException("[Url]を指定して下さい。")
     If String.IsNullOrEmpty(bookmark.Title) Then Throw New ArgumentException("[Title]を指定して下さい。")

     queries.Add("url", bookmark.Url)
     queries.Add("description", bookmark.Title)
     ' optional
     If Not String.IsNullOrEmpty(bookmark.Comment) Then queries.Add("extended", bookmark.Comment)
     If Not bookmark.Tags Is Nothing AndAlso bookmark.Tags.Length > 0 Then
         queries.Add("tags", String.Join(" ", bookmark.Tags))
     End If
     If Not bookmark.Date Is Nothing Then
         queries.Add("dt", bookmark.Date.Value.ToString("yyyy-MM-ddTHH:mm:ssZ"))
     End If
     queries.Add("shared", IIf(share, "yes", "no"))
     queries.Add("replace", IIf(replace, "yes", "no"))

     Dim wc = New Net.WebClient()
     wc.Credentials = _credential
     wc.Encoding = Text.Encoding.UTF8

     Dim contents = wc.DownloadString(JoinQueries(address, queries))
     Dim result = Text.RegularExpressions.Regex.Match( _
         contents, "<result code=""(?<msg>.*?)"" />", Text.RegularExpressions.RegexOptions.Multiline _
     )
     If result.Success Then
         Dim msg = result.Groups("msg").Value
         ' replaceを指定してね!
         If msg <> "done" Then Throw New ArgumentException(msg)
     End If
End Sub

Private Shared Function JoinQueries(ByVal address As String, ByVal queries As IDictionary(Of String, Object))
     If queries.Count = 0 Then Return address

     Return address + "?" + String.Join( _
         "&", queries.Select( _
             Function(q) String.Format("{0}={1}", q.Key, Web.HttpUtility.UrlEncode(q.Value)) _
         ).ToArray() _
     )
End Function

特に説明する必要は無いでしょう。単に「https://api.del.icio.us/v1/posts/add」というURLにリクエストを送っているだけです。

あと、JoinQueriesメソッドのq.Valueを取得する部分をHttpUtilityクラスを使ってURLエンコードしておきました(System.Webアセンブリへの参照が必要です)。

使うとしたら、以下のようになるでしょうか。

Program.vb
Sub Main()
     Dim obj = New Delicious(New Net.NetworkCredential("ユーザ名", "パスワード"))
     Dim bookmark = New DeliciousBookmark With { _
         .Title = "google", _
         .Url = "http://www.google.co.jp" _
     }
     ' 共有して、上書きする。
     obj.AddBookmark(bookmark, share:=True, replace:=True)
End Sub

では、このメソッドをプロバイダークラスから呼び出すように変更しましょう。

このメソッドを呼び出すには山ほどパラメータが必要になるので 、(インナークラスで)パラメータクラスを作っておきます。

Delicious.vb

Private Class NewItemParameters
     Private _url As String
     <Parameter(Mandatory:=True, Position:=0)> _
     Public Property Url() As String
         Get
             Return _url
         End Get
         Set(ByVal value As String)
             _url = value
         End Set
     End Property

     Private _tags As String()
     <Parameter(Position:=1)> _
     Public Property Tags() As String()
         Get
             Return _tags
         End Get
         Set(ByVal value As String())
             _tags = value
         End Set
     End Property

     Private _comment As String
     <Parameter(Position:=2)> _
     Public Property Comment() As String
         Get
             Return _comment
         End Get
         Set(ByVal value As String)
             _comment = value
         End Set
     End Property

     Private _date As DateTime?
     <Parameter(Position:=3)> _
     Public Property [Date]() As DateTime?
         Get
             Return _date
         End Get
         Set(ByVal value As DateTime?)
             _date = value
         End Set
     End Property

     Private _share As SwitchParameter
     <Parameter()> _
     Public Property Share() As SwitchParameter
         Get
             Return _share
         End Get
         Set(ByVal value As SwitchParameter)
             _share = value
         End Set
     End Property
End Class

URLTagsCommentDateShareというプロパティを定義しています。それぞれParameterAttribute属性でマークして、必須属性にはMandatory:=TruePostion:=nで順番を指定しています。

後はNewItemNew-Itemコマンドレットに対応)メソッドをオーバーライドして、その中でAddBookmarkメソッドを呼び出すだけです。
NewItemDynamicParametersメソッドをオーバーライドして、NewItemParametersクラスのインスタンスを返しておく事をお忘れなく。

Delicious.vb

Protected Overrides Sub NewItem(ByVal path As String, ByVal itemTypeName As String, ByVal newItemValue As Object)
     Dim dynaParams = CType(Me.DynamicParameters, NewItemParameters)

     Dim bookmark = New DeliciousBookmark With { _
         .Title = path, _
         .Url = dynaParams.Url, _
         .Tags = dynaParams.Tags, _
         .Comment = dynaParams.Comment, _
         .Date = dynaParams.Date _
     }
     Me.Delicious.AddBookmark( _
         bookmark, replace:=Me.Force, share:=dynaParams.Share.IsPresent _
     )
     WriteItemObject(bookmark, path, False)
End Sub

Protected Overrides Function NewItemDynamicParameters(ByVal path As String, ByVal itemTypeName As String, ByVal newItemValue As Object) As Object
     Return New NewItemParameters()
End Function

追加に成功すれば(例外が投げられなければ)、DeliciousBookmarkオブジェクトをパイプラインに出力しておきます。

これで、以下のようにしてブックマークを追加する事ができるようになります。

PS delicious:\> ni google http://www.google.co.jp -Tags google -Comment テストです。


タイトル : google
URL      : http://www.google.co.jp
コメント : テストです。
タグ     : {google}
更新日付 :


PS delicious:\> dir -Count 1


タイトル : google
URL      : http://www.google.co.jp/
コメント : テストです。
タグ     : {google}
更新日付 : 2009/03/12 4:30:46

次はブックマークを編集できるようにしておきます。

まずはSet-Itemコマンドレットのパラメータクラスをインナークラスで定義しておきます。

Delicious.vb
Private Class SetItemParameters
     Private _bookmark As DeliciousBookmark
     <Parameter(Mandatory:=True, Position:=0, ValueFromPipeline:=True)> _
     Public Property Bookmark() As DeliciousBookmark
         Get
             Return _bookmark
         End Get
         Set(ByVal value As DeliciousBookmark)
             _bookmark = value
         End Set
     End Property

     Private _share As SwitchParameter
     <Parameter(Position:=1)> _
     Public Property Share() As SwitchParameter
         Get
             Return _share
         End Get
         Set(ByVal value As SwitchParameter)
             _share = value
         End Set
     End Property
End Class

BookmarkShareというプロパティを定義しています。BookmarkプロパティはValueFromPipeline:=Trueにする事でパイプラインから取得できるようにしておきます。

後はSetItemメソッド(Set-Itemコマンドレットに対応)をオーバーライドして、その中でAddBookmarkメソッドをreplace:=Trueで呼び出しています。

Delicious.vb

Protected Overrides Sub SetItem(ByVal path As String, ByVal value As Object)
     Dim dynaParams = CType(Me.DynamicParameters, SetItemParameters)

     Me.Delicious.AddBookmark( _
         dynaParams.Bookmark, replace:=True, share:=dynaParams.Share.IsPresent _
     )
End Sub

Protected Overrides Function SetItemDynamicParameters(ByVal path As String, ByVal value As Object) As Object
     Return New SetItemParameters()
End Function

あと、Deliciousプロパティに一部不具合があったので、以下のように修正しておいて下さい。

Delicious.vb
Private _delicious As Delicious
Public ReadOnly Property Delicious() As Delicious
     Get
         If _delicious Is Nothing Then
             ' Me.PSDriveInfoプロパティでは駄目!!
             Dim driveInfo = Me.SessionState.Drive.Current

             _delicious = New Delicious(driveInfo.Credential.GetNetworkCredential())
         End If
         Return _delicious
     End Get
End Property

これで、以下のようにしてブックマークの情報を編集する事ができるようになります。

PS delicious:\> $bookmark = dir -Count 1
PS delicious:\> $bookmark.Title = "google.co.jp"
PS delicious:\> $bookmark | si

更新系のコマンドレットには、他にもRename-ItemMove-Itemがありますが、これらには対応しない事にします。

次はブックマークの削除機能を実装します。

次回に続く

PowerShellで作るdeliciousクライアント その2

前回の続きです。

ブックマーク一覧の取得 - dirコマンドへの対応

まずはブックマーク一覧の取得機能を実装します。操作的には「delicious」ドライブに移動して、dirコマンド(Get-ChildItemコマンドレット)を実行するとブックマークの一覧が表示されるといった具合です。

テストしやすくする為にdeliciousにアクセスする機能を別クラスとして定義する事にします。

「Delicious.vb」というファイル名でクラスファイルを追加します。

Delicious.vb
Public NotInheritable Class Delicious
     Private _credential As Net.ICredentials

     Sub New(ByVal credential As Net.ICredentials)
         _credential = credential
     End Sub

End Class

deliciousにアクセスする為には認証が必要になるので、コンストラクタでICredentialsインスタンスを受け取るようにします。
これは後述するPowerShellで使用される認証クラスであるPSCredentialGetNetworkCredentialメソッドでNetworkCredentialクラスのインスタンスを返すのでこのようにしています。

次に、deliciousブックマークの情報を格納する為のクラスを「DeliciousBookmark.vb」というファイル名のクラスファイルとして追加します。

DeliciousBookmark.vb

<Serializable()> _
<DebuggerStepThrough()> _
Public Class DeliciousBookmark
     Private _title As String
     Public Property Title() As String
         Get
             Return _title
         End Get
         Set(ByVal value As String)
             _title = value
         End Set
     End Property

     Private _url As String
     Public Property Url() As String
         Get
             Return _url
         End Get
         Set(ByVal value As String)
             _url = value
         End Set
     End Property

     Private _tags As String()
     Public Property Tags() As String()
         Get
             Return _tags
         End Get
         Set(ByVal value As String())
             _tags = value
         End Set
     End Property

     Private _comment As String
     Public Property Comment() As String
         Get
             Return _comment
         End Get
         Set(ByVal value As String)
             _comment = value
         End Set
     End Property

     Private _date As DateTime
     Public Property [Date]() As DateTime
         Get
             Return _date
         End Get
         Set(ByVal value As DateTime)
             _date = value
         End Set
     End Property

     Public Overrides Function ToString() As String
         Return String.Format("Title = {0}, Url = {1}, Tags = {2}", Title, Url, String.Join(",", Tags))
     End Function
End Class

プロパティがいくつか定義されていますか、だいたい名前から想像できると思います。

では、ブックマークの一覧を取得する機能を実装します。
GetBookmarksというメソッドで、引数として、取得する開始位置と件数を受け取ります。 返り値としてList(Of DeliciousBookmark)型を返します。

Delicious.vb

Function GetBookmarks(ByVal start As Integer?, ByVal count As Integer?) As List(Of DeliciousBookmark)
     Dim address = "https://api.del.icio.us/v1/posts/all"
     Dim queries = New Dictionary(Of String, Object)()
     If start.HasValue Then queries.Add("start", start.Value)
     If count.HasValue Then queries.Add("results", count.Value)

     Dim wc = New Net.WebClient()
     wc.Credentials = _credential
     wc.Encoding = Text.Encoding.UTF8

     Dim contents = wc.DownloadString(JoinQueries(address, queries))
     Dim xml = XDocument.Parse(contents)
     Dim results = From ele In xml.Descendants("post") _
                   Select New DeliciousBookmark With { _
                     .Title = ele.Attribute("description"), _
                     .Url = ele.Attribute("href"), _
                     .Tags = CType(ele.Attribute("tag"), String).Split(" "), _
                     .Comment = ele.Attribute("extended"), _
                     .Date = ele.Attribute("time") _
                   }

     Return New List(Of DeliciousBookmark)(results)
End Function

Private Shared Function JoinQueries(ByVal address As String, ByVal queries As IDictionary(Of String, Object))
     If queries.Count = 0 Then Return address

     Return address + "?" + String.Join( _
         "&", queries.Select(Function(q) String.Format("{0}={1}", q.Key, q.Value)).ToArray() _
     )
End Function

処理的には、与えられた引数でURLを組み立て、WebClientクラスを使ってリクエストを送っています。

レスポンスはXMLとして返ってくるので、それをLINQ to XMLを使ってパースを行い、DeliciousBookmark型に変換しています。

使い方としては以下のようになります。

Program.vb
Sub Main()
     Dim obj = New Delicious(New Net.NetworkCredentials("ユーザ名", "パスワード"))
     ' 10件だけ取ってくる
     Dim bookmarks = obj.GetBookmarks(0, 10)
     ' do something
End Sub

次は、このメソッドをプロバイダー側から呼び出すようにします。

そのためにはまず、プロバイダーに認証情報が渡されたかどうかをチェックする必要があります。
認証情報はNewDriveメソッド(New-PSDriveコマンドレットに対応)の引数として渡されるPSDriveInfoクラスのCredentialプロパティからアクセスできます。

DeliciousProvider.vb

Protected Overrides Function NewDrive(ByVal drive As PSDriveInfo) As PSDriveInfo
     If drive.Credential Is PSCredential.Empty Then Throw New ArgumentException("認証情報[-Credential]を指定して下さい。")

     Return MyBase.NewDrive(drive)
End Function

PSCredentialクラスのEmptyプロパティと比較する事で、認証情報が指定されているかどうかを調べられます。

認証情報を指定してドライブを作る時は以下のようにします。

PS > New-PSDrive delicious Delicious c:\temp -Credential ユーザ名

というコマンドを実行すると、以下のパスワードを入力するダイアログが表示されます。

ここでパスワードを入力して、「OK」ボタンをクリックすればプロバイダーに認証情報を渡す事ができます。

後は、GetChildItemGet-ChildItemコマンドレットに対応)メソッドをオーバーライドして、その中でGetBookmarksメソッドを呼び出し、WriteItemObjectメソッドでオブジェクトをPowerShellのパイプラインに出力します。

DeliciousProvider.vb
Private _delicious As Delicious
Public ReadOnly Property Delicious() As Delicious
     Get
         If _delicious Is Nothing Then
             _delicious = New Delicious(Me.PSDriveInfo.Credential.GetNetworkCredential())
         End If
         Return _delicious
     End Get
End Property

Protected Overrides Sub GetChildItems(ByVal path As String, ByVal recurse As Boolean)
     For Each bookmark In Me.Delicious.GetBookmarks(0, 20)
         WriteItemObject(bookmark, path, False)
     Next
End Sub

ちなみにDeliciousクラスのインスタンスをプロパティ経由でインスタンス化しているのは、プロバイダークラスはコマンドレットが呼び出される度にインスタンス化が行われる為、クラスのフィールドに格納してもそれが保持され続けるわけでは無いからです。

ここまでで一度ビルドして、PowerShellを起動して「delicious」ドライブにアクセスしてみましょう。

dirコマンドを実行すると以下のように表示されるはずです。

PS delicious:\> dir


PSPath        : Delicious\Delicious::c:\temp\
PSParentPath  :
PSChildName   : temp
PSDrive       : delicious
PSProvider    : Delicious\Delicious
PSIsContainer : False
Title         : Microsoft Semblio - Home Page
Url           : http://www.microsoft.com/learningspace/semblio/Default.aspx
Tags          : {Microsoft, Silverlight}
Comment       : 学習教材を作るサイト
Date          : 2009/03/11 6:10:36
....

見づらいですね。Format-Tableなどの整形系のコマンドレットを使えば見易くなりますが、毎回指定するのは面倒なのでオブジェクトの書式設定ファイルを用意します。

psdelicious.format.ps1xml

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
     <ViewDefinitions>
         <View>
             <Name>Tags</Name>
             <ViewSelectedBy>
                 <TypeName>PSDelicious.DeliciousBookmark</TypeName>
             </ViewSelectedBy>
             <ListControl>
                 <ListEntries>
                     <ListEntry>
                         <ListItems>
                             <ListItem>
                                 <Label>タイトル</Label>
                                 <PropertyName>Title</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>URL</Label>
                                 <PropertyName>Url</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>コメント</Label>
                                 <PropertyName>Comment</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>タグ</Label>
                                 <PropertyName>Tags</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>更新日付</Label>
                                 <PropertyName>Date</PropertyName>
                             </ListItem>
                         </ListItems>
                     </ListEntry>
                 </ListEntries>
             </ListControl>
         </View>
     </ViewDefinitions>
</Configuration>

この書式設定ファイルを以下のようにして、読み込みます。

PS > Update-FormatData -prependPath psdelicious.format.ps1xml

この状態で、再度dirコマンドを実行すると、

PS delicious:\> dir


タイトル : Microsoft Semblio - Home Page
URL      : http://www.microsoft.com/learningspace/semblio/Default.aspx
コメント : 学習教材を作るサイト
タグ     : {Microsoft, Silverlight}
更新日付 : 2009/03/11 6:10:36
...

とりあえず、これで20件だけですがブックマークの一覧を表示する事ができました。

せっかくなので、オプションを指定して取得する開始位置と件数を指定できるようにしておきましょう。

DeliciousProviderクラスのインナークラスとして、以下のクラスを定義します。

DeliciousProvider.vb
Private Class GetChildItemsParameters
     Private _start As Integer
     <Parameter()> _
     Public Property Start() As Integer
         Get
             Return _start
         End Get
         Set(ByVal value As Integer)
             _start = value
         End Set
     End Property

     Private _count As Integer = 20
     <Parameter()> _
     Public Property Count() As Integer
         Get
             Return _count
         End Get
         Set(ByVal value As Integer)
             _count = value
         End Set
     End Property
End Class

StartCountというプロパティを持つだけのクラスです。プロパティはそれぞれParameterAttribute属性でマークしておきます。

GetChildItemsDynamicParametersメソッドをオーバーライドして、このクラスのインスタンスを返しておきます。

DeliciousProvider.vb

Protected Overrides Function GetChildItemsDynamicParameters(ByVal path As String, ByVal recurse As Boolean) As Object
     Return New GetChildItemsParameters()
End Function

後は、GetChildItemsメソッドを以下のように変更しておきます。

DeliciousProvider.vb
Protected Overrides Sub GetChildItems(ByVal path As String, ByVal recurse As Boolean)
     Dim dynaParams = CType(Me.DynamicParameters, GetChildItemsParameters)

     For Each bookmark In Me.Delicious.GetBookmarks(dynaParams.Start, dynaParams.Count)
         WriteItemObject(bookmark, path, False)
     Next
End Sub

これで、以下のように開始位置と取得件数を指定して、dirコマンドを実行することができるようになります。

PS delicious:\> dir -Start 10 -Count 100

次回に続く・・・

PowerShellで作るdeliciousクライアント その1

以前、PowerShellのカスタムプロバイダーという機構を紹介する記事を書きました。

この時は具体的なプロバイダーではなく、プロバイダー作成の概要を説明するという形でしたが、今回いい題材を思いついたのでその事を記事にしたいと思います。

今回作るプロバイダーは、SBS(ソーシャルブックマークサービス)のdeliciousのクライアントです。

このプロバイダーでは以下の操作ができます。

  • ブックマークの一覧を取得
  • ブックマークの追加・編集
  • ブックマークの削除
  • タグの一覧を取得
  • タグの編集
  • タグの削除

全部実装できるかどうかはわかりませんが、とりあえずやっていきましょう。開発言語にはVB.NET 2008(VB9)を使用します。開発環境については以前の記事を参考にして下さい。

注: 開発には、deliciousのアカウントが必要になります。

プロジェクトの作成

Visual Studio 2008を起動して、言語は「Visual Basic」で「クラス ライブラリ」プロジェクトを作成します。プロジェクト名は「PSDelicious」にします。

プロジェクトのプロパティを開いて、参照設定に以下のアセンブリを追加します。

  • System.Configuration.dll
  • System.Management.Automation.dll

また、以下の名前空間をインポート設定に追加しておきます。

プロジェクトの設定は以上です。

とりあえず動かす

では、とりあえずプロバイダークラスを作って、PowerShellに組み込んで動くようにしておきましょう。

「DeliciousProvider.vb」というファイル名でクラスファイルを追加します。このクラスはContainerCmdletProviderNavigationCmdletProviderクラスから派生させます*1

DeliciousProvider.vb

<CmdletProvider("Delicious", ProviderCapabilities.ShouldProcess Or ProviderCapabilities.Credentials)> _
Public Class DeliciousProvider
     Inherits ContainerCmdletProvider

     Protected Overrides Function IsValidPath(ByVal path As String) As Boolean
         Return True
     End Function

     Protected Overrides Function ItemExists(ByVal path As String) As Boolean
         Return True
     End Function

     Protected Overrides Function IsItemContainer(ByVal path As String) As Boolean
         Return True
     End Function
End Class

このクラスがプロバイダークラスである事を示す為に、CmdletProviderAttribute属性でマークしておきます。引数としてプロバイダー名とプロバイダーの種類(このプロバイダーでは認証機構を使用するのでCredentialsを指定)を指定しておきます。

IsValidPathメソッドをオーバーライドする必要があるので、とりあえずTrueを返しておきます。

あと、とりあえずこのドライブに移動(Set-Location)できるようにする為に、ItemExistsメソッドとIsItemContainerメソッドをオーバーライドしておきます(いずれもTrueを返すだけ)。

次にこのプロバイダークラスをPowerShellに登録するためのSnapInクラスを作ります。

「DeliciousSnapIn.vb」というファイル名でクラスファイルを追加します。このクラスはPSSnapInクラスから派生させます。

DeliciousSnapIn.vb
Imports System.ComponentModel

<RunInstaller(True)> _
Public Class DeliciousSnapIn
     Inherits PSSnapIn

     Public Overrides ReadOnly Property Name() As String
         Get
             Return "Delicious"
         End Get
     End Property

     Public Overrides ReadOnly Property Description() As String
         Get
             Return "deliciousプロバイダーなどを追加するスナップインです。"
         End Get
     End Property

     Public Overrides ReadOnly Property Vendor() As String
         Get
             Return "coma2n"
         End Get
     End Property
End Class

このクラスがインストーラクラスである事を示す為に、RunInstallerAttribute属性でマークしておきます。

以下の読み取り専用のプロパティをオーバーライドしておきます(必須)。

  • Name
  • Description
  • Vendor

ここまでで一度ビルドしておきましょう。

ビルドに成功したらPowerShellを起動して、このプロジェクトの「bin\Debug」フォルダに移動して下さい。そこで以下のコマンドを実行します。

PS Debug> installutil.exe PSDelicious.dll

コマンドが正常に完了すれば、スナップインのインストールが成功です。

では、インストールしたスナップインを読み込んでみましょう。以下のコマンドを実行して下さい。

PS Debug> Add-PSSnapIn Delicious
PS Debug> New-PSDrive delicious Delicious c:\temp


Name       Provider      Root                                                                           CurrentLocation
----       --------      ----                                                                           ---------------
delicious  Delicious     c:\temp

Add-PSSnapInコマンドレットで「Delicious」スナップインを読み込み、New-PSDriveコマンドレットで「Delicious」プロバイダーで「delicious」ドライブを作成しています。

「delicious」ドライブに移動できるか試してみ下さい。

PS > cd delicious:\
PS delicious:\> 

移動はできますが、その他のコマンドレット(例えばdir)を実行するとエラーになります。

それでは、機能を実装していきましょう。

長くなったので次回に続きます・・・

*1:SessionStateProviderBaseでもいいのですが、このクラスから派生するとPowerShellへのインストール時に何故かエラーが出てしまいます

PowerShellでWebにアクセスしてみよう!

今回はPowerShellを使ってWeb(インターネット)にアクセスする方法を紹介します。

PowerShell.NET Framework(.NET)をベースにして作られているので、.NETに用意されている豊富なライブラリをそのまま使用することができます。

Webへのアクセスについても.NETのライブラリを使うことになります。基本的にはC#VB.NETなどの.NET対応言語と同じやり方になります。

WebへのアクセスにはSystem.Net.WebClientクラスかSystem.Net.HttpWebRequestクラスのどちらかを使って行います。
前者は簡単に使えますが細かい制御が出来ません、後者は少々手数が増えますが細かい制御が出来ます。今回はSystem.Net.HttpWebRequestクラスを使用します。

PowerShellからGoogle検索

Webと言えば「Google」と言えるほどの代名詞的な存在のGoogle検索をPowerShellから呼び出してみましょう。

今回作るのは引数として渡した文字列でGoogle検索を行い、その検索結果をコンソールに出力するPowerShellスクリプトです。

PowerShellを起動して、カレントディレクトリに「Find-Google.ps1」というファイルを作り、適当なテキストエディタ秀丸エディタなど)で開いて下さい。

PS > ni -File Find-Google.ps1
PS > hidemaru Find-Google.ps1

まずは引数の定義を行います。文字列型で「word」という引数を定義しています。

param([string]$word)

次にこの引数に日本語が渡された場合に備えて(URLの一部として渡すため)、ASCIIコードに変換しておきます。

ASCIIコードに変換するには、System.Web.HttpUtilityクラスのUrlEncodeメソッドを使います。
このクラスはSystem.Webアセンブリに定義されているので、System.ReflectionクラスのLoadWithPartialNameメソッドを使ってアセンブリを読み込んでおきます。

param([string]$word)

[void]([Reflection.Assembly]::LoadWithPartialName("System.Web"))

$word = [Web.HttpUtility]::UrlEncode($word)


次に実際にGoogleにアクセスするためのコードを記述します。

やることはGoogleに検索のクエリを投げて(HTTPリクエスト) 、その結果を文字列として取得する(HTTPレスポンス)だけです。

Googleに投げる検索クエリのフォーマットは以下のようになります。

  • http://www.google.co.jp/search?hl=ja&q=検索文字列

検索文字列の部分を引数で渡された文字列に置換します。

では、やってみましょう。

param([string]$word)

[void]([Reflection.Assembly]::LoadWithPartialName("System.Web"))

$word = [Web.HttpUtility]::UrlEncode($word)
$webReq = [Net.HttpWebRequest]::Create("http://www.google.co.jp/search?hl=ja&q=$word")
$webReq.Method = "GET"
$webReq.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)"

$webRes = $webReq.GetResponse()
$sr = New-Object IO.StreamReader($webRes.GetResponseStream(), $webRes.ContentEncoding)
$content = $sr.ReadToEnd()
$sr.Close()
$webRes.Close()

System.Net.HttpWebRequestクラスのCreateメソッドを使って、引数で渡したURLにHTTPリクエストを送る、このクラスのインスタンスを生成します。

Methodプロパティに「GET」を設定することで、GETメソッドでHTTPリクエストを行います。

UserAgentプロパティには適当なユーザーエージェントを設定しておきます。ユーザーエージェントについては以下のURLを参考にして下さい。

ここまででHTTPリクエストの準備が整いました。後はGetResponseメソッドで実際にリクエストを行い、HTTPレスポンスを取得します。

GetResponseメソッドの返り値として、System.Net.HttpWebResponseクラスのインスタンスが返ってきますので、GetResponseStreamメソッドでHTTPレスポンスの出力ストリームを取得します。

後はこの出力ストリームをSystem.IO.StreamReaderクラスを使って読み出すだけです。出力ストリームとHTTPレスポンスを閉じるの(Closeメソッドの呼び出し)を忘れないで下さい。


ここで「content」という変数の中には、Google検索を使って検索した結果のHTMLが文字列として格納されています。

どのようなHTMLかは以下のURLにアクセスして、ソースを見てみて下さい。

このHTMLの中から検索結果の以下の情報を抽出して、PowerShellのコンソールに出力するわけです。

  • タイトル
  • URL

しかし、見てわかるとおりこのHTMLはかなり汚いです。タグが閉じられていなかったり、属性の値が引用符で囲まれていなかったりしています。

ある程度ならば少し変更してXHTMLにしてから、System.Xml.XmlDocumentクラスやSystem.Xml.Linq.XDocumentクラスなどを使って簡単に目的の情報を取得するのですが、このHTMLをちゃんとしたXHTMLに変換するのはかなり大変そうです。

なので、正規表現を使ってテキストレベルで情報を抽出します。ちなみに、こういった手法を使ってWebサイトの一部の情報だけを抜き出すことを「スクレイピング」と呼びます。


まずは正規表現で探しやすいように改行を削除しておきます。あと、邪魔なのでscript要素とstyle要素も削除しておきます(これは個人的な好みです)。

param([string]$word)

[void]([Reflection.Assembly]::LoadWithPartialName("System.Web"))

$word = [Web.HttpUtility]::UrlEncode($word)
$webReq = [Net.HttpWebRequest]::Create("http://www.google.co.jp/search?hl=ja&q=$word")
$webReq.Method = "GET"
$webReq.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)"

$webRes = $webReq.GetResponse()
$sr = New-Object IO.StreamReader($webRes.GetResponseStream(), $webRes.ContentEncoding)
$content = $sr.ReadToEnd()
$sr.Close()
$webRes.Close()

# 改行を削除
$content = $content -replace "`n", ""
# スクリプトとスタイルを削除
$content = $content -replace "<(script|style)>.*?", ""

検索結果は一件ごとに「<li class=g>」というタグの中に入っていますが、なんとこのタグは閉じられていません。仕方が無いので、「</div>」か「</table>」というタグで閉じると仮定して正規表現で取りに行きます。

条件的には「<li class=g>(?<line>.*?)</(div|table)>」という感じになります。
これをSystem.Text.RegularExpressions.Regexクラスを使って行います(PowerShellには標準で-matchという正規表現用の演算子が用意されていますが、これは一件しか取得できないので使いません)。

param([string]$word)

[void]([Reflection.Assembly]::LoadWithPartialName("System.Web"))
$word = [Web.HttpUtility]::UrlEncode($word)
$webReq = [Net.HttpWebRequest]::Create("http://www.google.co.jp/search?hl=ja&q=$word")
$webReq.Method = "GET"
$webReq.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)"

$webRes = $webReq.GetResponse()
$sr = New-Object IO.StreamReader($webRes.GetResponseStream(), $webRes.ContentEncoding)
$content = $sr.ReadToEnd()
$sr.Close()
$webRes.Close()

# 改行を削除
$content = $content -replace "`n", ""
# スクリプトとスタイルを削除
$content = $content -replace "<(script|style)>.*?</(script|style)>", ""

$ptrn = New-Object Text.RegularExpressions.Regex("<li class=g>(?<line>.*?)</(div|table)>")
$m = $ptrn.Matches($content)
$m | % {
     if($_.Value -match '<h3 class=r><a href=\"(?<url>.*?)\" .*?>(?<title>.*?)</a>') {
         $obj = New-Object PSObject
         # タイトル
         $obj | Add-Member NoteProperty Title ($matches.title -replace "</*\w*>", "")
         # URL
         $obj | Add-Member NoteProperty Url $matches.url
         $obj
     }
}

後は正規表現の条件にマッチした結果をForEach-Objectコマンドレット(%エイリアス)を使って、要素の数だけぐるぐる回します。

その中では「<h3 class=r><a href=\"(?<url>.*?)\" .*?>(?<title>.*?)</a>」という条件でタイトルとURLを取得して、それらをPSObjectクラスのNotePropertyとして追加して、コンソールに出力します。

このスクリプトを実行してみると、以下のように表示されます。

PS > Find-Google powershell
Title                                                       Url
-----                                                       ---
Windows PowerShell でのスクリプティング                     http://www.microsoft.com/japan/technet/scriptcenter/hubs...
Windows PowerShell - Wikipedia                              http://ja.wikipedia.org/wiki/Windows_PowerShell
オブジェクト指向なコマンド環境「Powershell」を試してみた... http://d.hatena.ne.jp/nitoyon/20080806/powershell_tutorial
次世代Windowsシェル「Windows PowerShell」を試す(前編) ... http://www.atmarkit.co.jp/fdotnet/special/powershell01/p...
Windows PowerShell徹底解説:ITpro                           http://itpro.nikkeibp.co.jp/article/COLUMN/20061106/252598/
Windows PowerShell 入門(1)−基本操作編:CodeZine          http://codezine.jp/article/detail/2067?p=1
PowerShell FAQ                                              http://newpops.wankuma.com/
PowerShell Scripting                                        http://www.roy.hi-ho.ne.jp/mutaguchi/powershell/

これは完全に動作するわけではないですが、Webにアクセスする方法の初歩とのサンプルとしてはいいのではないでしょうか。

まだまだ足らない機能がありますが、この先は皆さんの手で作ってみて下さい。

Spring.NETのロギング設定

Spring.NETのロギング設定についてメモ

Spring.NETではロギングに「log4net」やEnterprise Libraryの「Logging Application Block」なんかを使えるんだけど、それらを直接使うんじゃなくて「Common.Logging」というロギングライブラリを挟んで、実際のロガーを隠蔽している。

で、その設定がちょっとややこしいので書いておく。

まずはロギングを行うクラスを定義する。ロガーにはCommon.Logging.ILogというインターフェースからアクセスできる。

Hoge.vb
Imports Common.Logging

Public Class Hoge

    Private _logger As ILog
    Public Property Logger() As ILog
        Get
            Return _logger
        End Get
        Set(ByVal value As ILog)
            _logger = value
        End Set
    End Property

    Sub Run()
        Logger.Info("Start Run method.")
        ' Do something
        Logger.Info("End   Run method.")
    End Sub

End Class

アプリケーション構成ファイルにSpring.NETの設定とCommon.Loggingの設定、ついでにオブジェクト定義*1も追加しておく。

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>

        <sectionGroup name="common">
            <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>

        <objects xmlns="http://www.springframework.net">
            <object name="logger" type="Spring.Objects.Factory.Config.LogFactoryObject, Spring.Core">
                <property name="LogName" value="sampleLogger" />
            </object>

            <object name="hoge" type="SpringLoggingSample.Hoge, SpringLoggingSample" singleton="true">
                <property name="Logger" ref="logger" />
            </object>
        </objects>
    </spring>

    <common>
        <logging>
            <factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4Net">
                <arg key="configType" value="FILE" />
                <arg key="configFile" value="log4net.config" />
            </factoryAdapter>
        </logging>
    </common>
</configuration>

今回は「log4net」を使うので、「factoryAdapter」にCommon.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4Netを指定している。
この部分をCommon.Logging.EntLib.EntLibLoggerFactoryAdapter, Common.Logging.EntLibに変えるとロガーを「Logging Application Block」に切り替える事ができる。
log4net」の設定は別ファイルに書いておくので、configFileプロパティには「log4net.config」というファイル名を指定しておく。

    <common>
        <logging>
            <factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4Net">
                <arg key="configType" value="FILE" />
                <arg key="configFile" value="log4net.config" />
            </factoryAdapter>
        </logging>
    </common>

以下が実際のロガーの設定。ファイルとコンソールに出力する。

log4net.config
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
    <!-- ファイルへのログ出力 -->
    <appender name="rollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
        <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
        <file value="Log\" />
        <appendToFile value="true" />
        <rollingStyle value="Date" />
        <datePattern value="yyyyMMdd.LOG" />
        <staticLogFileName value="false" />
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date %-5level - %message%newline" />
        </layout>
    </appender>

    <!-- コンソールへのログ出力 -->
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date %-5level - %message%newline" />
        </layout>
    </appender>

    <logger name="sampleLogger">
        <level value="DEBUG" />
        <appender-ref ref="rollingLogFileAppender" />
        <appender-ref ref="ConsoleAppender" />
    </logger>
</log4net>

で、最後にエントリーポイント。アプリケーションコンテキストを取ってきて、「hoge」オブジェクトを取りだし、Runメソッドを呼び出すだけ。

Program.vb
Imports Spring.Context

Module Program

    Sub Main()
        Dim context As IApplicationContext = ConfigurationManager.GetSection("spring/context")
        Dim obj As Hoge = context.GetObject("hoge")

        obj.Run()

        Console.ReadKey()
    End Sub

End Module

実行すると以下のように表示される。

*1:普段は別ファイルに分離するけど、面倒くさいのでくっつけておいた