2011年1月31日月曜日

URL Rewrite 2.0 でURLやサーバー変数を書き換える

今回はIIS7で、URLやサーバー変数を書き換える方法のメモです。
アプリケーション側の都合で構成を変えたいが、公開URLは変更したくない場合などに有効です。
Apacheのmod_rewriteと同じようなことができます。

導入は、Microsoft Web Platform Installer 3.0を使うのが簡単です。
ますは、こちらからインストールしてください。


[製品] - [サーバー] の中から、[URL Rewrite 2.0] を見つけて追加ボタンを押します。


その後、下部の[インストール]ボタンを押し、続けて同意ボタンを押し終わるのを待ちます。
場合によっては依存関係にあるモジュールも自動でインストールされます。

※ここでは詳しく説明しませんが、リバースプロキシを構築したい場合は
   ARR (Application Request Routing) モジュールも必要です。


インストールが完了したら、IISマネージャーを起動します。
Default Web Siteを選択すると、「URL 書き換え」という機能が追加されているのが分かります。
ダブルクリックするか、右上の[機能を開く]を選択して設定画面を出します。
(全てのサイトにURL書き換えルールを適用したくない場合は、目的の仮想ディレクトリに移動して、
URL書き換え設定画面を開いてください)


右上の[規則の追加]を選択します。


「空の規則」を選択してOKを押します。


この画面で書き換えルールを設定します。


[名前]
作成するルールに好きな名前を付けます

[URLの一致]
正規表現 or ワイルドカード or 完全一致した場合だけ書き換えることができます。
正規表現での指定がオススメです。括弧で括っておくと、書き換えルールで変数として使えます。

たとえば、「(.*)/(.*)/.*」とした場合、{R:n}を書き換えルールで使うことができます。

{R:0} これはURL全体を指します
{R:1} これは一番目の括弧でマッチした文字列になります
{R:2} これは二番目の括弧でマッチした文字列になります


サーバー変数だけで判断したい場合は、すべて(.*)にしておいて、[条件]で指定します。

[条件]
IISのサーバー変数に一致した場合のみ書き換えを行いたい時は、ここで条件指定します。
また、サーバー変数を書き換えルールに使いたい場合もここで指定します。
書き換えルールでの指定は{C:n}になります。

[サーバー変数]
サーバー変数の書き換えを行いたい場合はここで指定します。
事前に書き換えマップに書き換えたいサーバー変数を登録しておく必要がありますので
注意してください。(URL Rewrite設定画面Topの右側メニュー[書き換えマップの表示]から行います)

[アクション]
URLの書き換えルールを設定します。
前述の変数を利用して、http://{R:1}/{C:1}/index.htmlなどと動的な書き換えもできます。
サーバー変数の書き換えだけが目的の場合はアクションは「なし」にしておきます。

ルールの設定が完了したら、右上の適用ボタンを押して保存してください。
すぐにルールが反映されるはずです。(IISの再起動は不要)


【参考】
設定方法の詳細は、下記のTechNetのページが参考になると思います。

URL 書き換えモジュールの送信ルールの作成

HTTP 要求ヘッダーおよび IIS サーバー変数の設定 (サーバー変数を書き換えたい場合)

URL 書き換え Version 2 と Application Request Routing を使用したリバース プロキシ


【補足】
IIS6の場合はURL Rewrite 2.0は使えませんが、
IIRF (Ionic's Isapi Rewrite Filter) というツールを使うと、ほぼ同じことができます。

2010年12月20日月曜日

Installer error 2869 の原因が分からない場合のヒント

このエラーの解析にかなり手間取ったのでメモしておきます。

インストール中に、下記のようなエラーダイアログが出て、ロールバックしてしまいました。
何回やっても駄目で、しかも特定の環境下でしか再現しない・・・。

The dialog SetupError has the error style bit set, but is not an error dialog.

う~ん、全く意味が分かりませんね!
とりあえず、直前のエントリで書いた方法で、詳細ログを取るとエラーコードは2869。
このエラーコードを頼りに調べていくと、こちらのブログを発見。

要するに、直前のカスタムアクションが失敗しているのが原因のようです。
エラーメッセージからは全く想像つきませんね・・・。
ログから直前のカスタムアクションを調べてみます。

MSI (s) (BC:10) [13:23:12:269]: Invoking remote custom action. 
DLL: C:\Windows\Installer\MSI7330.tmp, Entrypoint: ManagedInstall

これは、どうやら.NETのInstallerクラスを呼び出している所のようです。
ProjectInstallerではレジストリの書き換えとサービス登録をやっていた
のですが、レジストリの書き換えは失敗する余地がない。
消去法で、サービス登録が失敗しているのでは?と考えて調べてみると、
サービスがアンインストールされずに残ったままでした。
今回のケースでは、手動でサービスを消したら*解決しました。

* サービスは次のコマンドで削除できます(必要なサービスを誤って消さないように注意!)
sc.exe delete [サービス名]

【参考】
Windows Installer Error Messages (MSDN)
http://msdn.microsoft.com/en-us/library/aa372835(VS.85).aspx

2010年12月19日日曜日

Setup.exeで詳細ログを出力する方法

msiで詳細ログを出力できることは知っていましたが、Setup.exeでも可能なようです。
リリースする場合は、InstallShieldなどで単一のSetup.exeにまとめている場合が
多いと思いますが、何か問題が発生した場合の解析に便利です。

まずはmsiの場合の復習。

msiexec /i Installer.msi /L*v "C:\Install.log"

setup.exeの場合は次の通り。

setup.exe /v"/L*v C:\Install.log"

/vオプションに続けてダブルクォーテーションで囲んだ形で指定すればOKのようです。

2010年12月8日水曜日

Google Chrome 8でMicrosoft Forefront TMGへ接続できない問題の回避方法

Google Chrome 8にアップデート後、Microsoft Forefront TMGへのアクセスができなくなり、
いろいろと苦労したので、忘れないようにメモ。

現象としては、TMGへアクセスするとずっとロード中のままになります。
Windows XPではキャッシュとクッキーのクリアで直ったのですが、Windows 7の場合
再インストールしても復旧せず困っていました。

いろいろ検索したところ、Chromeのフォーラムに答えを発見しました。
起動オプションとして、--use-system-sslを使えば良いみたいです。
具体的には、Chromeのショートカットのプロパティを開いて、リンク先の後ろに
これを付ければよいです。



【フォーラムの該当エントリ】

2010年11月5日金曜日

Visual Studioのセットアップ プロジェクトでアドバタイズショートカットを作らない方法

Visual Studio付属のセットアップ プロジェクトで、スタートメニューにショートカットを
作成しようとすると、必ずアドバタイズショートカットになってしまいます。
これには、いろいろと弊害があり、意図しない状況で修復インストールが動いてしまったりします。

厄介なことに、Visual Studioのオプションではどうすることも出来ません。(項目が無い)
かといって、リリース毎にOrcaで設定をいじるのも面倒です。
なので、なにか良い方法が無いか考えてみました。

結論からいえば、セットアップ プロジェクトのポストビルドイベントで何とかできます。

(1) 例によって、スクリプトファイルを入手するため、Windows Installer SDKをインストールします。
    (これはPlatform SDKの中に含まれています)

(2)*.vdprojがあるプロジェクトフォルダ直下にWiRunSQL.vbsをコピーします。
    WiRunSQL.vbsは、デフォルトでは下記の場所にあるはずです。

C:\Program Files\Microsoft Platform SDK\Samples\SysMgmt\Msi\Scripts\WiRunSQL.vbs

(3)ポストビルドイベントを次のように設定します。(マクロを使っていますのでコピペでいけるはず)

cscript "$(ProjectDir)WiRunSQL.vbs" "$(BuiltOuputPath)" "INSERT INTO Property(Property,Value) VALUES('DISABLEADVTSHORTCUTS', '1')"
































【参考サイト】
[MSDN] Execute SQL Statements

[DOBON.NET] アドバタイズショートカットではなく、普通のショートカットを作成する

[DOBON.NET] MSIファイルのWindows Installerデータベースをプログラムで編集する

2010年10月20日水曜日

Chrome 拡張機能を作ってみました (Last Tab Keeper)

タブの制御を行う、簡単な拡張機能を作ってみました。

Chromeって、最後のタブを閉じると自動的に終了するのですが、ウインドウ自体は閉じて欲しくない場合ありませんか?
その場合、先に新規タブを出しておいて、開いているWebページを閉じることになると思います。
それを少し自動化できます。

最後のタブを閉じた時のイベントを拾って、新しいタブを作ればよいと気楽に考えていたのですが、
どうもそのタイミングでは新しいタブが作れない模様。
試行錯誤して、開いているWebページが残り1タブになったら、自動的に新しいタブを作ることにしました。
人によって、合う合わないあるかと思いますが、ご興味持たれた方は是非使ってみてください。

Chrome拡張機能ギャラリーに登録してありますので、下記リンクからアクセスできます。
ご意見、ご感想などありましたら、twitterかコメント欄でメッセージ頂ければ嬉しいです。

Last Tab Keeper

※名前はちょっと微妙だったかも・・・。アイコンもデフォルトのままですみません。

2010年10月15日金曜日

終了していないスレッドを調べるクラス

ワーカースレッドが終了していないため、メインウインドウは消えてもプロセスが残ってしまう場合があります。
その場合、原因のスレッドを突き止めて終わらない原因を修正しなければいけません。
しかし、開発環境では再現できないケースなどもあると思います。その場合のデバッグ用クラスを作ってみました。

基本的に、"new Thread"を"TraceableThread.CreateThread"に置換して、メインウインドウの
FormClosedイベントあたりでTimerProcTraceを呼んであげるだけです。
そうすると、指定ミリ秒毎に終了していないスレッドがある場合、作成時のスタックトレース付きでデバッグ出力に出します。
開発環境が入っていない(入れられない)場合でも、DebugViewなどで確認できます。

VS2008で作りましたが、多分.NET2.0以降なら動くと思います。

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Reflection;

namespace Diagnostics
{
    /// <summary>
    /// スレッドの追跡を行えます。
    /// 終了していないスレッドを調べるのに役立ちます。
    /// </summary>
    public static class TraceableThread
    {
        private static List<Thread> m_listThread;
        private static List<StackTrace> m_listStackTrace;
        private static Timer m_timerRemove;
        private static Timer m_timerTrace;
        private static Object m_sync;

        /// <summary>
        /// 静的コンストラクタ
        /// </summary>
        static TraceableThread()
        {
            // 初期化
            m_listThread = new List<Thread>();
            m_listStackTrace = new List<StackTrace>();
            m_sync = new Object();

            // スレッド終了監視タイマー
            m_timerRemove = new Timer(new TimerCallback(TimerProcRemove), null, 1000, 1000);
        }

        /// <summary>
        /// スレッドの終了を監視し、スレッドリストを更新します。
        /// </summary>
        private static void TimerProcRemove(Object obj)
        {
            lock (m_sync)
            {
                List<Thread> remove = new List<Thread>();

                // 終了しているスレッドを探す
                foreach (Thread trd in m_listThread)
                {
                    try
                    {
                        if (trd.Join(0) == true)
                        {
                            remove.Add(trd);
                        }
                    }
                    catch (ThreadStateException)
                    {
                        // スレッドが開始されていない場合
                        continue;
                    }
                }

                // リストから削除する
                foreach (Thread trd in remove)
                {
                    int nIndex = m_listThread.IndexOf(trd);
                    m_listThread.RemoveAt(nIndex);
                    m_listStackTrace.RemoveAt(nIndex);
                }
            }
        }

        /// <summary>
        /// new Thread(ParameterizedThreadStart start)の代わりに呼んでください
        /// </summary>
        public static Thread CreateThread(ParameterizedThreadStart start)
        {
            StackTrace trace = new StackTrace(true);
            Thread thread;

            lock (m_sync)
            {
                m_listStackTrace.Add(trace);

                thread = new Thread(start);

                m_listThread.Add(thread);
            }

            return thread;
        }

        /// <summary>
        /// new Thread(ThreadStart start)の代わりに呼んでください
        /// </summary>
        public static Thread CreateThread(ThreadStart start)
        {
            StackTrace trace = new StackTrace(true);
            Thread thread;

            lock (m_sync)
            {
                m_listStackTrace.Add(trace);

                thread = new Thread(start);

                m_listThread.Add(thread);
            }

            return thread;
        }

        /// <summary>
        /// new Thread(ParameterizedThreadStart start, int maxStackSize)の代わりに呼んでください
        /// </summary>
        public static Thread CreateThread(ParameterizedThreadStart start, int maxStackSize)
        {
            StackTrace trace = new StackTrace(true);
            Thread thread;

            lock (m_sync)
            {
                m_listStackTrace.Add(trace);

                thread = new Thread(start, maxStackSize);

                m_listThread.Add(thread);
            }

            return thread;
        }

        /// <summary>
        /// new Thread(ThreadStart start, int maxStackSize)の代わりに呼んでください
        /// </summary>
        public static Thread CreateThread(ThreadStart start, int maxStackSize)
        {
            StackTrace trace = new StackTrace(true);
            Thread thread;

            lock (m_sync)
            {
                m_listStackTrace.Add(trace);

                thread = new Thread(start, maxStackSize);

                m_listThread.Add(thread);
            }

            return thread;
        }

        /// <summary>
        /// スレッドの終了追跡を開始します。
        /// メインウインドウのFormClosedイベントで呼び出すことで、
        /// 終了していないスレッドを検出することができます。
        /// </summary>
        public static void TraceThreadTermination(int interval)
        {
            if (m_timerTrace == null)
            {
                m_timerTrace = new Timer(new TimerCallback(TimerProcTrace), null, interval, interval);
            }
        }

        /// <summary>
        /// 終了していないスレッドをTraceに出力します。
        /// </summary>
        private static void TimerProcTrace(object obj)
        {
            lock (m_sync)
            {
                for (int i = 0; i < m_listThread.Count; i++)
                {
                    Trace.WriteLine("");
                    Trace.WriteLine(DateTime.Now.ToLongTimeString());
                    Trace.WriteLine("■終了していないスレッドを検出しました!");
                    Trace.WriteLine("Name = " + m_listThread[i].Name + ", ManagedThreadId = " + m_listThread[i].ManagedThreadId.ToString());
                    Trace.WriteLine("スレッド作成時のスタックトレースは次の通りです。");
                    Trace.WriteLine(m_listStackTrace[i].ToString());
                }
            }
        }
    }
}