HIDARI日記(右)

そのときどき興味ある技術を中心にだらだら書いてます。内容は個人の見解であり、所属する企業を代表するものではありません。

JavaScriptだけでブラウザの「戻る」ボタンを無効化する方法

このあいだ諸事情で調べたら、意外とまとまってるとこなかったなのでちょこっと書いてみます。

  • IE8以降を対象
  • ブラウザの「戻る」ボタンで他のページに遷移しない
  • Backspaceキーでの「戻る」も許可しない

みたいな方針で調べてみました。

まあ結論から言うと

Ah, the back button. You might imagine "back" fires a JavaScript event which you could simply cancel like so:

document.onHistoryGo = function() { return false; }

No so. There simply is no such event.

javascript - Intercepting call to the back button in my AJAX application: I don't want it to do anything! - Stack Overflow

というわけで、ブラウザの戻るボタンをお手軽に無効にする方法はないようですね。

方法

1.window.onunloadイベントを利用する

まず直接的なイベントが無いなら似たようなイベントをかわりに使おう!みたいな安直な発想で。

ページから離れる際に発生するonunloadイベントをトリガーとして、次に表示されるページ(遷移先)を現在のページにしてしまう関数を実行する方法。

window.onunload = function() {
    alert('Back buttom is pushed ??');
    location.replace(document.location);
};  

onunloadは戻るボタン以外でも発生するの。Backspaceキー、URLの直接入力、履歴からの遷移でも。リンクをクリックしたときも当然発生する。その結果、このコードがあるページからは移動できなくなるという仕組み。

対象を「戻る」ボタンの動作だけに絞ることができないので、この方法はちょっと厳しそう。

以下のように、適当なタイミングでイベントハンドラを削除することで移動できるようにすることもできなくはないけど、釈然としない。

<script type="text/javascript">
    function goNextPage() {
        window.onunload = null;
    };
</script>

<a href="./Page3.html" onclick=" goNextPage(); ">Go Next</a>

というかIE11ではlocation.replace()でランタイムエラーになって動かないんだよな…(だめじゃん

参考:ブラウザの戻るボタンを無効にする方法: ある SE のつぶやき

2.history.forward()関数を使う

検索するとよく見つかる方法。遷移元のページに 以下のJSを記述しておく。

window.onunload = function() {};
history.forward();

上のコードがhead要素辺りに書いてあるページAからページBへ遷移、その後Bから「戻る」ボタンでAへ戻ると、history.foward()関数でBへ送り返されます。なので、Backspaceキー使われても平気。

ただ、履歴の一つ前がAのときはBからだけでなくどのページからも戻れないことになるので、特定のページだけ「戻る」ボタンを使用不可にしたい!みたいな状況では使えない。

逆にこのページだけには戻ってきてほしくない、っていうときは、あるいは使える?

参考:[JavaScript] ブラウザの戻るボタンを無効にする方法 | 自由が丘で働くWeb屋のブログ

3.window.location.hashを使う

URLにhash(URLの後ろにくっつく#HogeHogeみたいな文字列で、ページ内リンクなんかに使われてる)をつけることで別ページへの遷移として履歴に残して、他のページに戻れなくしてしまう方法。

window.location.hash="no-back";
window.location.hash="no-back-button";
window.onhashchange=function(){
    window.location.hash="no-back";
}

ページが読み込まれたタイミングでURLの末尾に#no-back、あるいは#no-back-buttonを追加し、同時にhashが変更されると発生するonhashchangeでhashを変更する関数を追加する。

ページに遷移してきた時点で、履歴の一つ前のページが自分自身になっているため、戻るボタンを押しても自分に戻るだけになるという寸法。

URLに余計な文字列が含まれてしまうのを嫌がる人もいますが、先に挙げた方法よりだいぶ安定している印象ですね。

参考:javascript - How to Detect Browser Back Button event - Cross Browser - Stack Overflow

4. history.go()を使う???

+JavaScript+ブラウザの戻るボタンの無効化 - Free Flying

こういう方法もあるみたいだけど、なんでhistory.go()で「戻る」が使えなくなるのかイマイチ分からなかったんで試すのをやめました。

参考:go method (Internet Explorer)

5. HTML5の History API を利用する

今回僕が当たった要件では難しいけど、HTML5が利用できるIE10以降では以下の方法を使えば、概ねそれらしく動きます。

「戻る」を禁止したいページに以下のJSを書く。

<script type="text/javascript">
    history.pushState(null, null, null);

    window.addEventListener("popstate", function() {
        history.pushState(null, null, null);
    });
</script>

ページが読み込まれたらhistory.pushState()メソッドで履歴の先頭に新しい履歴がひとつ追加される。このとき第3引数がnullなので、追加された履歴は自分自身を指していることになる。続いてpopstateイベントのハンドラとしてhistory.pushState()を実行する関数を指定する。

「戻る」ボタンが押されるとpopstateからイベントハンドラが実行され、自分自身を指す履歴(history.pushState(null, null, null))がもう一つ追加される。

ページ読み込み時に実行されるhistory.pushState()では、最初に「戻る」が実行されたときに戻る先の履歴が追加され、2回目以降の「戻る」実行時はイベントハンドラで追加される履歴が使用される、はず。

戻る先は常に自分自身になるため、結果として「戻る」ボタンを無効化できます。先に書いたlocation.hashに近いもので、実際リファレンスには

ある意味では、pushState() の呼び出しは window.location = "#foo"; と設定するのと似ています。どちらも、現在のドキュメントに関連する別の履歴エントリの生成とアクティベートを行います。

Manipulating the browser history - Web developer guide | MDN

とあるので、HTML5が使えるならhistory.pushState()、ダメならlocation.hashを使うというのが、ある意味正攻法なのかも。

まあ、各種イベントの発火条件とかに違いがあるので、よく考えて使う必要があるのは言うまでもないのですが。

参考:

おわりに

かろうじて使えるかなって言うのがlocation.hashを使う方法でしょうか。

というかそもそもですね、特にレガシーな感じのブラウザで、「戻る」ボタンの無効化くらいJavaScriptだけでできるんでしょ?ちゃちゃっとやっちゃって?という、(冒頭の引用のような)安直な考えは捨てるべきなのではとか思わずにはいられないと思ったり思わなかったり。

「Windowsでの自動化について考える会」を開催してきました #WinAutoMate

f:id:hidari-yori:20150222023741j:plain

今日は Windowsでの自動化について考える会 - connpass を開催してきました。

実は見切り発車感の強い企画ではありましたが、蓋を開けてみるとキャンセル待ちまで出るほど多くの方に興味を持っていただけたみたいで、かつ僕自信にとっても非常に勉強になることが多い有意義な勉強会になりました。

参加者、発表者、そして会場を提供していただいたMOTEX様、ありがとうございました。お疲れ様でした。

発表の概要のようなもの

本日の発表資料はすべて公開されています。

僕の発表ではこの勉強会の導入として、自動化の「手段」について駆け足で紹介。 個人的に興味のあるプロダクトの名前もこっそり混ぜつつ、僕なりの解釈とゆるく絡めて話しました。

続いて森理麟さんから。自身がこれまでに行ってきた身の回りの自動化の経験や、これからの自動化の在り方についての大胆な予想が語られました。大小の様々なつまづきポイントについての知見がとても勉強になるものでした。

三番目の石川さんの発表はシステムテスト自動化について。石川さんが作られてるテスト自動化ライブラリ Friendly を使ったテストの自動化、そしてアプリケーションドライバーの重要性を強く訴えるものでした。途中、参加者に実際にその場でFriendlyを使ったテストを書いてもらうという面白い試みもされていて見応えがありました。

Hyper-VとPowerShellによる仮想サーバーの自動構築.pptx - Microsoft PowerPoint Online

最後の発表はwakaさん。Powershell DSCとHyper-Vを組み合わせて仮想マシンの新規インストールからWebサイトの立ち上げまでを行ってしまおうというものでした。OSの新規インストールにはライセンスの問題がつきまとうため、なかなか実行できないかもしれませんが、とても珍しい知見が得られたと思います。またデモ中、PowersShell DSCでサクラエディタのインストールが行われた時の参加者の食付きがすごかったのが印象的でした。

おわりに

Twitterでつぶきやかれた内容は Windowsでの自動化について考える会 #WinAutoMate まとめ - Togetterまとめ にまとめています。よければこちらも見てみてください。

次回はいつになるかわからないけど、もう一回くらいはやりたいな。

そういえば、次あれば資料はワイド表示でつくろうと思いました(小並感

はじめてVirtual DOMを触ったよ

Room metro #29 に参加して VirtualDOM をほんの少し教わったので忘れないうちに書いておきます(とかいいつつすでに怪しいけども)。

ほとんど当日のメモそのままなので内容的にアレなところあるかも(言い訳

VirtualDOMってなにか

HTMLのDOMと全く同じものをJSで作ったもの。

本来ブラウザのものであるDOMを、JSのオブジェクトとして仮想的に構築したデータ構造。

今まではブラウザが持つDOMを直接触ってた。 するとDOM変更のタイミングで毎回ブラウザのレンダリングが走る。これはとても重い処理。 この重さを軽減するために、VirtualDOMを使ってJSで変更を行ったあと、まとめてDOMに反映する。

VirtualDOMからのDOMの更新では、更新前のVirtualDOMとの差分だけが実際のDOMに反映される。これによって無駄な更新が入らず速度面で有利になる。VirtualDOMがイケてるところ。と言われている。 ここを抑えておかないとそこらの記事はナンノコッチャになる。

話題の?Reactとは

Virtual DOMを使ったプロダクト。まだよくわからん。

今回の続きとして勉強会を計画中とか風のうわさで本人から聞いたので楽しみにしてます。

ハンズオン

Matt-Esch/virtual-dom

を使って簡単なTODOアプリを作成。 上記のライブラリは本当に生のVirtualDOMを扱うもので、通常はこれをラップしていい感じにVirtualDOMを操作してくれるライブラリを使うとのこと。

ざっくりと手順の紹介

まずは virtual-dom/dist at master · Matt-Esch/virtual-dom から virtual-dom.js をローカルに保存。

次にVisualStudioから新しくASP.NET Applicationプロジェクトを作成。

f:id:hidari-yori:20150202225007j:plain

この時、空のテンプレートを選択して余計なフォルダーや参照は追加しないようにする。

f:id:hidari-yori:20150202225031j:plain

ASP.NET Applicationプロジェクトが作成できたら、そこにHTMLファイル追加。

f:id:hidari-yori:20150202225057j:plain

さらに、先ほどダウンロードした virtual-dom.js を追加。

f:id:hidari-yori:20150202225123j:plain

ここまでが準備。この状態まで持ってこれたら、あとはひたすらコードを書いていくだけ。

そしてできたものがこちら。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="virtual-dom.js"></script>
<title>Hoge</title>
</head>
<body>
<div>
<input type="text" name="todo"/>
<button type="button" name="add">add</button>
</div>
<div class="list"></div>
<script>
   var todos = [],
   textbox = document.querySelector("[name=todo]");

   document.querySelector("[name=add]").addEventListener("click", function () {
       var todoText = textbox.value;
       todos.push(todoText);

       var newTodoVDom = createVDom(todos);
       var patch = virtualDom.diff(todoVDom, newTodoVDom);

       todoNode = virtualDom.patch(todoNode, patch);
       todoVDom = newTodoVDom;

   }, false);

   function applyTodosVDom(todos)
   {
       var newTodoVDom = createVDom(todos),
       patches = virtualDom.diff(todoVDom, newTodoVDom);

       todoNode = virtualDom.patch(todoNode, patches);
       todoVDom = newTodoVDom;
   }

   function createVDom(todolist) {
       var textVDoms = todolist.map(function(todo) {
           return virtualDom.h("div", {}, [
           virtualDom.h("button", {
               onclick: function() {
                   todos.splice(indexedDB, 1);
                   applyTodosVDom(todos);
               }
           }, "complete!"),
           virtualDom.h("span", {}, todo)
           ]);
       });

       return virtualDom.h("div", {}, textVDoms);
   }

   var todoVDom = createVDom(todos);
   var todoNode = virtualDom.create(todoVDom);
   document.querySelector(".list").appendChild(todoNode);
</script>
</body>
</html>

おわりに

以上がはじめてのVirtualDOM体験でした。

めとべやのみなさんありがとうございました。

「イシューからはじめよ」を読んだ

イシューからはじめよ―知的生産の「シンプルな本質」

イシューからはじめよ―知的生産の「シンプルな本質」

せっかく読んだので、気になった、あるいは気に入ったところのメモ。

明確化する

「これは何に答えを出すためのものなのか」というイシューを明確にしてから問題に取り組まなければ後で必ず混乱が発生し、目的意識がブレて多くのムダが発生する。

それから

「こんな感じのことを決めないとね」といった「テーマの整理」程度で止めてしまう人が多いが、これでは まったく不足している(中略)「やってみなければわからないよね」といったことは決して言わない。ここで踏ん張り切れるかどうかが、あとから大きく影響してくる

さらに発展して

「できる限り先んじて考えること、知的生産における段取りを考えること」を英語で「Think ahead of the problem」と言う。

怒られてる気がした。これやっちゃって失敗してるわーって。

「今はまだわからない」って思考停止は実行するのが簡単すぎる。諦める前に一歩立ち止まりたい。

諦めるな、僕。

方向を定める

よいイシューの表現は、「~はなぜか?」といういわゆる「WHY」ではなく、「WHERE」「WHAT」「HOW」の形をとることが多い。

問題を整理しようとするとき、僕達はよく、「なぜ」という言葉を使うけど、「なぜ」という言葉は問題の本質をぼやけさせてしまうように思う。

「答え」が存在する

どれほどカギとなる問いであっても、「答えを出せないもの」はよいイシューとは言えないのだ。「答えを出せる範囲でもっともインパクトのある問い」こそが意味のあるイシューとなる。

筆者は本書の冒頭で、 「悩む」=「答えが出ない」「考える」=「答えが出る」 という考え方を示している。それだけでなく「答えが出るものの中でも最も影響が大きいものを扱うようにしろ」と言っている。

すべての「答えが出るもの」を扱うには人間は小さすぎる。

やりすぎ

情報収集にかけた努力・手間とその結果得られる情報量にはある程度のところまでは正の相関があるが、そこを過ぎるととたんに新しい取り込みのスピードが鈍ってくる。

また

ある情報量までは急速に知恵が湧く。だが、ある量を超すと急速に生み出される知恵が減り、もっとも大切な「自分ならではの視点」がゼロに近づいていくのだ。

取り入れる情報量には注意しないと、気を抜くと自分で考えることをやめてしまったり、自分でも気づかないうちに集めた情報によって考えの幅が狭められたりしてしまう。

集めた情報に逆に洗脳されちゃうような感じは確かにある。そして

数字をこねくり回さず、手早くまとめることが大切だ。1回ごとの完成度よりも取り組む回数(回転数)を大切にする。また、90%以上の完成度を目指せば、通常は途方もなく時間がかかる。そのレベルはビジネスではもちろん、研究論文でも要求されることはまず無い。そういう視点で「受け手にとっての十分なレベル」を自分のなかで理解し、「やり過ぎない」ように意識することが大切だ。

ただでさえ、僕は時間をかけ過ぎると飽きちゃうので、モチベーションをマネジメントするためにも「一つ一つをやり過ぎ、短い時間で複数回」というのは気をつけないと…

努力というか

既存の手法についてひと通りなじむには、どのような分野であっても相当な年数がかかる。

さらに

実際のところ、どのような分野であっても、多くのプロを目指す修行のかなりの部分はこれら既存の手法、技の習得に費やされる。

周りの人たちを見ているとわかってくるのだけど、一つの分野で素晴らしいパフォーマンスとクオリティで仕事をしている人たちは、みなさん驚くほど引き出しを持っておられる。 そこに追い付きたいし、追い越したい。たぶんそこには近道なんか無いんだろうなーってよく思う。

プロとしての意識

「コンプリート・スタッフ・ワーク(Complete Staff Work)」これは「自分がスタッフとして受けた仕事を完遂せよ。いかなるときにも」という意味だ。この「コンプリートワーク」という言葉はプロフェッショナルとして仕事をする際には、常に激しく自分にのしかかってくる。

完遂の定義にもよるけど、というか完遂の定義を事前にしておかないといけないな。

まあそれとは別に、たとえクソのようなプロジェクトでも何とかして着陸させないといけないし、そういう意味ではやっぱり働く上で一番必要な心構えだと思う。

イキルサイノウ

「人から褒められること」ではなく、「生み出した結果」そのものが自分を支え、励ましてくれる。

自分で生み出したという事実だけが生きる勇気をくれるのではないか。そう考えるとちょっと光が見える気がする。

おわりに

読んでよかった一冊です。

気に入ったフレーズだけをピックアップしましたが、本文はもっと具体的な例、論文を書く際やプレゼンテーションの資料を作る際に参考になる考え方が示されていたので、初めて論文を書くことになる大学生なんかは読んでおいて損はないんじゃないかと思います。

はじめてAppVeyorで.NETベースのプログラムのCI環境を構築した

手元でJenkinsを動かしてCI環境を構築するのもいいのだけれど、一度くらいCIサービスを使ったCIを実行したいと思い立ったので実行してみました。

現状で.NET言語用のCIサービスといえば AppVeyor なので、これを使ってCI環境を構築した話です。

AppVeyorはOSSのCIなら無料で使えるため、GitHubなどを使って開発している場合は非常にありがたいです。

ちゃきちゃきやっていく

ごちゃごちゃっとサインアップしてしまいましょう。GitHubアカウントを使ってサインアップしておくと便利。

画面上部の「+NEW PROJECT」を選ぶと各種ホスティングサービスのからリポジトリを選択できるようになるのでビルドしたいものを選びます。

f:id:hidari-yori:20150118223911j:plain

ところでこのサービス選択の中にVisual Studio Onlineが入ってて、おおなるほどなってなりました(そらそうや。

f:id:hidari-yori:20150118224039j:plain

プロジェクトを追加したら、画面上部の「SETTINGS」からお好みな設定を作ります。

ビルド中のビルドの前後、テストの前後など色々なタイミングで好きな処理を挿し込めるようにPowershellコマンドプロンプトスクリプトを設定できますのでいい感じに利用しましょう。

一点だけ注意点。

Nugetパッケージの自動復元はデフォルトでは行われません。

なので About NuGet package restore - Appveyor を参考に設定を行う必要があります。

手っ取り早いのは [SETTINGS] > [Build] > [Before build script] に以下のスクリプトを追加する方法でしょうか。

nuget restore

これを忘れるとちょっとどきどきすることになります。

f:id:hidari-yori:20150118225130j:plain

バッジつけたい

[SETTINGS] > [Badges] から各種「Project status badge」が選べます。

リポジトリのREADME.mdに載せるならマークダウンなやつをコピペするのがいいかと思います。

おわりに

びっくりするぐらい簡単なので積極的に使っていきたい。特にVisual Studio Olineで…

PowershellからMSBuildを動かす

まあ別段MSBuildに限った話ではなくて、外部プロセスを起動したいときは同じ考え方ができますが。

やりたいこと

MSBuild.exe MyApp.sln /t:Rebuild /p:Configuration=Release

みたいなのをラップして

Build-VsProject -Path .\MyApp.sln -Options "/t:Rebuild /p:Configuration=Release"

で実行可能にすることを目指します。

どうやるか

パッと浮かぶのは Start-Process か Call Operator & を使う方法。

ただ Start-Process にしろ & にしろ、コンソールからちょろっと使うにはいいけど、 スクリプトで使うのは終了コードの取り方が面倒だったりして辛いかも。

なので System.Diagnostics.Process と, プロセス実行時の設定を行うための System.Diagnostics.ProcessStartInfo を自前で作る方法で行くことにします。

.NETのクラスを使うほうが安心感ありますし(謎。

書いていきましょう

System.Diagnostics.Process はこんな感じで書ける。

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = MSBuild.exe
$pinfo.Arguments = "Path\to\MyApp.sln /t:Rebuild /p:Configuration=Release"
$pinfo.RedirectStandardOutput = $true
$pinfo.RedirectStandardError = $true
$pinfo.UseShellExecute = $false

$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $pinfo
$proc.Start() | Out-Null

$proc.RedirectStandardOutputプロパティと$proc.RedirectStanderdErrorプロパティの設定はお好みで。 このプロパティをtrueにしておくと、起動したプロセスの標準出力を取得できるようになります。

この時の注意点として、RedirectStandardOutputプロパティやRedirectStanderdErrorプロパティをTrueにするときは必ずUseShellExecuteプロパティをFalseに設定する必要があります。しないと例外が飛んできます(http://msdn.microsoft.com/ja-jp/library/system.diagnostics.processstartinfo.redirectstandardoutput(v=vs.110).aspx)。

$stdOut = $proc.StandardOutput.ReadToEnd()
$errOut = $proc.StandardError.ReadToEnd()

という感じに使うことができます。

終了コード欲しいよね

実行したプロセスの終了コードは$proc.ExitCodeプロパティで取得可能なので $proc.WaitForExit()$proc.HasExitプロパティなんかと合わせて使います。

$proc.StartInfo = $pinfo
$proc.Start() | Out-Null
$proc.WaitForExit()

if($proc.ExitCode -eq 0){
    Write-Host "Build has Succeedes!!"
}
else{
    Write-Host "Build has failed..."
}

さて、これで上手くいくと思うじゃろ?

WaitForExit()の罠

そんなことはなく。

$proc.WaitForExit()にはハマリポイントがあります。

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = MSBuild.exe
$pinfo.Arguments = "Path\to\MyApp.sln /t:Rebuild /p:Configuration=Release"
$pinfo.RedirectStandardOutput = $true
$pinfo.RedirectStandardError = $true
$pinfo.UseShellExecute = $false

$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $pinfo
$proc.Start() | Out-Null
$proc.WaitForExit()

$stdOut = $proc.StandardOutput.ReadToEnd()
$errOut = $proc.StandardError.ReadToEnd()

とか素直に書いてしまうとプロセスが終了せずに固まってしまいます。 原因はMSBuildからの出力によってStandardOutputのバッファいっぱいになり、そこで処理が止まってしまうため。

これをいい感じに吸い出してやる必要があります。そのためにOutputDataReceivedイベントと$proc.BeginOutputReadLine() を使って非同期に出力を読み取ります。

手順は以下の通り。

  1. BSBuildの出力を保存する追記可能なストアとしてStringBuilderを用意
  2. OutputDataReceivedイベントで行いたい処理をスクリプトブロックに記述
  3. OutputDataReceivedのイベントハンドラを登録
  4. プロセスを実行
  5. $proc.BeginOutputReadLine()で読み取り開始
  6. プロセス終了後に読み取った出力を適当に書き出す

この流れで書いたのが以下のコードです。

$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $pinfo

# 出力の格納先としてStringBuilderを用意
$stdOutBuilder = New-Object -TypeName System.Text.StringBuilder
$stdErrBuilder = New-Object -TypeName System.Text.StringBuilder

# イベントを利用して出力を書き出す
# まずスクリプトブロックにイベントで行いたい処理を記述
# ここではStringBuilderに出力を格納する
$action = {
    if(![string]::IsNullOrEmpty($EventArgs.Data)){
        $Event.MessageData.AppendLine($EventArgs.Data)
    }
}

# イベントハンドラを登録
$stdOutEvent = Register-ObjectEvent -InputObject $proc -Action $action -EventName "OutputDataReceived" -MessageData $stdOutBuilder
$stdErrEvent = Register-ObjectEvent -InputObject $proc -Action $action -EventName "ErrorDataReceived" -MessageData $stdErrBuilder

# プロセスを開始
[void]$proc.Start()

# 非同期で出力の読み取りを開始
$proc.BeginOutputReadLine()
$proc.BeginErrorReadLine()
$proc.WaitForExit()

# 最後に読み取った出力を書き出す
$stdOutBuilder.ToString() | Out-File ".\BuildLog.txt"
$stdErrBuilder.ToString() | Out-File ".\ErrorLog.txt"

思った以上に面倒なコードになりました。

コード全体

というわけで最終的に出来上がった?コードはこんな感じでした。ちょっと高度な関数風にしてみました。

function Build-VsProject
{
    [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="Low")]
    param
    (
        [Parameter(
            Mandatory=$true,
            Position=0
        )]
        [alias("SolutionPath")]
        [string]
        $Path,

        [string]
        $Options
    )

    begin
    {
        $msb="C:\Program Files (x86)\MSBuild\12.0\Bin\MSBuild.exe"
    }

    process
    {
        if(-not(Test-Path -Path $msb)){throw "MSBuildNotFoundException"}

        Write-Host "Start Build $Path"

        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $msb
        $pinfo.Arguments = "$Path /t:Rebuild /p:Configuration=Release"
        $pinfo.RedirectStandardOutput=$true
        $pinfo.RedirectStandardError=$true
        $pinfo.UseShellExecute=$false
        $pinfo.CreateNoWindow=$true

        $proc = New-Object System.Diagnostics.Process
        $proc.StartInfo = $pinfo

        $stdOutBuilder = New-Object -TypeName System.Text.StringBuilder
        $stdErrBuilder = New-Object -TypeName System.Text.StringBuilder

        $action = {
            if(![string]::IsNullOrEmpty($EventArgs.Data)){
                $Event.MessageData.AppendLine($EventArgs.Data)
            }
        }

        $stdOutEvent = Register-ObjectEvent -InputObject $proc -Action $action -EventName "OutputDataReceived" -MessageData $stdOutBuilder
        $stdErrEvent = Register-ObjectEvent -InputObject $proc -Action $action -EventName "ErrorDataReceived" -MessageData $stdErrBuilder
        [void]$proc.Start()
        $proc.BeginOutputReadLine()
        $proc.BeginErrorReadLine()
        $proc.WaitForExit()

        Unregister-Event -SourceIdentifier $stdOutEvent.Name
        Unregister-Event -SourceIdentifier $stdErrEvent.Name

        $stdOutBuilder.ToString() | Out-File ".\BuildLog.txt"
        $stdErrBuilder.ToString() | Out-File ".\ErrorLog.txt"

        if($proc.ExitCode -eq 0){
            Write-Host "Build has Succeedes!!"
        }
        else{
            Write-Host "Build has failed..."
        }

        return $proc.ExitCode
    }

    end
    {
    }
}

おわりに

みたいなことを書こうとしてるところでぎたぱそ先生がPowerShell で System.Diagnostic.Process にて BeginOutputReadLine() を使う - tech.guitarrapc.cómを投稿しておられるのに気づいて会社でぶったまげたことをご報告致します。

さすがMVP。わかりやすい...脱帽するしかないけど、これからも強く儚く生きていこうと思います。よろしくお願いします。

またこの記事を書くにあたり発破をかけて下さった@Posauneさんと@irofさんに感謝。

WindowsでのHubotのインストール手順 Hubot@2.9.3

node.jsのインストール

node.jsからmsiを落としてきてインストールする。他の方法は知らない。nodebrewとかWindowsで使えんのかな?誰か教えて><。

npmフォルダの作成

node.jsのインストーラの問題(node.js - nodejs/windows Error: ENOENT, stat 'C:\Users\RT\AppData\Roaming\npm' - Stack Overflow)でnpmが動かなくて焦った。自分でC:\Users\<YOUR_NAME>\AppData\Roamingの下にnpmってフォルダ作らなきゃいけない。

CoffeeScriptのインストール

npm install -g coffee-script しましょう。

yeoman と generator-hubotのインストール

ググったらいっぱい出てくる npm hubot でのインストールはScriptsフォルダがなかったりしてhubot hellohubot pingも動かない。代わりにyeoman使う。

npm install -g yo generator-hubot

自分のHubotを生成する

適当にHubot用のフォルダを作ってyo。

mkdir hubot-test
cd hubot-test
yo hubot

コマンド実行中にHubotのアスキーアートが表示されておしゃれ。

external-scripts.jsonの修正

ひとまずコンソールで動かすだけなので hubot-test\external-scripts.jsonから hubot-heroku-keepalivehubot-redis-brain を削除する。これをしとかないとhubot実行時にエラーが出るので。必要になりそうなときは適当にバックアップする。

Hubotの実行

cd hubot-test
.\bin\hubot

動作確認

Hubot> hubot ping
Hubot> PONG

Scriptsに自分のスクリプトを追加してみる

自分の作ったスクリプトhubot-test\scripts に置くとHubotに認識されるので試してみた。

GoodNight.coffee

module.exports = (robot) ->
    robot.respond /GOOD NIGHT$/i, (msg) ->
        msg.send "おやすみうみう"

おわりに

第1回 Hubot×ChatOps勉強会 - connpassに参加してきた勢いでやった。反省してない。

あと、今回書いた情報もそのうち腐ってくると思うのでご注意を。