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());
                }
            }
        }
    }
}

2010年9月9日木曜日

Process.WaitForExit()は本当の意味で無期限待機しない

MSDNを見ると、Process.WaitForExitメソッドの引数無しバージョンの説明は次のようになっています。 
関連付けられたプロセスが終了するまで無期限に待機するように Process コンポーネントに指示します。
しかし、実際の動作はProcess.WaitForExit(Timeout.Infinite)と同じですので注意しましょう。
要するに、0x7FFFFFFFミリ秒後(25日後くらい)に、プロセスが終了していなくてもリターンします。(詳細はこちらを参照)

確かに、プログラム的には無期限ともいえますが、説明が紛らわしいですね。
戻り値がvoidなのもいやらしい。

サーバーでプロセスの異常終了を監視してメール通知するプログラムを作ったのですが、
再起動メールが来て焦ってログ確認したら問題無し。原因はこれでした。

2010年3月10日水曜日

Windows7でWebDAVサーバーを構築する方法

Windows7に搭載されているIIS7.5で、テスト用のWebDAVサーバーを構築しながら手順をまとめてみました。

構築するのは、基本認証に対応した書き込み可能なテスト用WebDAVサーバーです。ここでは手順は紹介しませんが、公開する場合はSSLも検討した方が良いでしょう。(基本認証はパスワードが平文で流れるため)
  1. WebDAV機能のインストール
  2. まずは、IISにWebDAVと基本認証機能をいれます。下図のように、「WebDAV発行」と「基本認証」を選択して追加します。(IISをデフォルトでインストールした状態では入っていません)
    注) Windows Vista / Windows Server 2008に搭載されているIIS7.0で行う場合、標準機能には含まれていませんので、ここからWebDAV拡張を入れることで対応できます。

  3. WebDAVユーザーを作成する
  4. WebDAVサーバーにアクセスするときに使うユーザーを作成しておきます。コントロールパネルのユーザーアカウントから、パスワードを設定した標準ユーザーを作成してください。ここではwebdavというユーザーを作りました。

  5. WebDAVを有効にする
  6. インストール直後、WebDAV機能は無効になっています。コントロールパネルの管理ツールの中からIISマネージャを起動します。左側のツリーを展開して"Default Web Site"を選択すると、右側に「WebDAVオーサリング規則」というアイコンがあると思いますので、ダブルクリックして開きます。
    右側に、「WebDAVの有効化」というボタンがあると思いますので押します。

  7. WebDAVルートの作成
  8. 次に"Default Web Site"の右クリックメニューから「仮想ディレクトリの追加」を選択します。
    今回は、"WebDAV"という名前で作成してみます。
    ちなみに、物理パスにはネットワークフォルダを指定することも出来ます。

  9. NTFSのアクセス権を設定する
  10. 標準ユーザー(Usersグループのメンバー)は読み取りアクセス権しか持っていませんので、追加したwebdavというユーザーが書き込みアクセスできるようにします。左側のツリーで、先ほど作成したWebDAVという仮想ディレクトリを選択し、右クリックメニューから「アクセス許可の編集」を選びます。
    「セキュリティ」タブを選択して、「編集」ボタンを押します。
    「追加」ボタンから、webdavユーザーを追加し、フルコントロールをチェックします。

  11. WebDAVのアクセス権を設定する
  12. 最後にWebDAVのアクセス権の設定を行います。新しく作った仮想ディレクトリ"WebDAV"を選択して、「WebDAVオーサリング規則」をダブルクリックします。そして、右上の「オーサリング規則の追加」ボタンを押します。
    「指定されたユーザー」を選択し、2.で作ったユーザー名webdavを入力します。アクセス許可には、「読み込み」と「書き込み」にチェックを入れます。
    これで設定完了です!

  13. アクセスしてみる
  14. うまく設定できたかを確認するには、WebDAVクライアントソフトを使うのが便利です。たとえばCarotDAVなどをつかって接続確認してみましょう。

IIS7.0で管理コンソールが大幅に変わったり、WebDAV拡張モジュールが標準で入っていなかったりとIISを敬遠していた方もいるかと思いますが、この機会に試してみてはどうでしょうか。

2010年2月17日水曜日

単一msiで多言語に対応したインストーラを作成してみる

InstallShieldとかを使わないと、インストーラの多言語対応は出来ないと思い込んでいた。というか世間的にそう思われてる?ちょっと検索しただけだと、無理というものが目に付く。Visual Studioで言語ごとにしかインストーラ作れないのがなあ。(これも思い込みかもしれないが)

少し本気で探してみると、下記のサイトが見つかった。アンドキュメントな方法ながらできるらしい。実際にこのとおりやってみたら、日、英、独に対応したmsiができたので、簡単にまとめてみる。
Multi-Language MSI Packages without Setup.exe Launcher

・Windows Installerの仕組み的にはどうなっているのか
ベースとなるmsiがあり、言語トランスフォーム(*.mst)を実行時にプロパティとして指定することで動作を変更できる仕組みがあるらしい。具体的には、下記のように指定できる。

msiexec.exe /i setup.msi TRANSFORMS=1041.mst

英語のsetup.msiに、日本語のトランスフォーム(1041.mst)を適用して表示言語を日本語に変えることが出来る。但し、mstファイルはmsiをベースに作成しておく必要がある。(各種ツールを利用)
InstallShieldなどのツールで多言語対応のSetup.exeを作成できるが、裏ではこんな動きをしているんだなと、初めて知った。

・実際に試してみる
前述のサイトの紹介されている方法は、実行時に指定するmstファイルをmsiに組み込んでしまうというもののようだ。地域と言語の設定が、mstファイルを組み込んだ言語になっている場合、自動でその言語で表示できるようになる。
  1. まず、mstファイルの組み込みに必要なスクリプトファイルを入手するため、Windows Installer SDKをインストールする。(これはPlatform SDKの一部として含まれている)
  2. 次に、デフォルト言語を英語にしてビルドしたmsiと、日本語とドイツ語のmstを作成する。動作検証をするのが主目的なので、手っ取り早くInstallShieldにて作成。
  3. [Windows Installer SDKのインストールフォルダ]\Samples\SysMgmt\Msi\Scripts内にある、WiSubStg.vbs及びWiLangId.vbsを、msiと同じフォルダにコピー。
  4. ここでスクリプトを実行。
  5. WiSubStg.vbs setup.msi 1041.mst 1041
    WiSubStg.vbs setup.msi 1031.mst 1031
    WiLangId.vbs setup.msi Package 1033,1041,1031
これで完了。地域と言語の設定をドイツ語にしてsetup.msiを起動すると、ちゃんとドイツ語で表示された。アンドキュメントな方法なので、心配な点はあるが、1つのmsiで多言語UIをサポートするインストーラが作成できることは確認できた。

2010年1月31日日曜日

Windows Azureの日本語の料金表まとめ

いよいよ明日から課金開始ですね。
ちょっと前までは、料金表が英語だったような気がするのですが、先ほどこちらを確認したら日本語がありました。

現在提供されているのは4つのプラン。
料金も日本円表示になっているので、ドルよりイメージしやすいかもしれません。
  • Windows Azure Platform 導入特別プラン (料金表)
  • Windows Azure Platform 標準プラン (料金表)
  • Windows Azure Platform 拡張プラン (料金表)
  • Windows Azure Platform 従量課金プラン (料金表)
あと、前回のエントリーで書いた疑問点はこちらを見て無事解決しました。

(1)デプロイしているとsuspend状態であってもCPU課金が発生
    やはり、これはデプロイした時点で仮想マシンが確保される為でしょうね。
    特別プランの25hでテストしようと考えている場合は、テスト後に必ずdeleteしないと直ぐに無料分が尽きます・・・。

(2)Production環境とStaging環境に課金で差は無い
    ProductionとStagingの両方で動かすと、CPUはx2必要になるみたいです。
    これもURL以外に機能的な差はありませんので、当然といえば当然ですね。

2010/2/19追記
CPU課金は1ヶ月の合計デプロイ時間で決まるわけではない模様。1時間のうち1分でもデプロイしていると1時間分課金が発生するということらしい。これは、なんか納得いかないなー。

2010年1月7日木曜日

Windows AzureのCPUの課金で心配な部分

2月から課金が開始されるWindows Azureだけど、本番環境に登録しようか迷い中。

Introductory Specialというプランがあって、6/31まではいくらか無料分があるらしい。
  • 25 hours of a small compute instance
  • 500 MB of storage
  • 10,000 storage transactions
1月末でCTP環境が使えなくなるし、いろいろと試すために本番環境に登録したいのだが、個人でやるとなると心配な部分が多い。ストレージは十分そうだけど、CPUが足りるかどうか。

で、ぶっちゃけCPU時間が25Hで足りるかというと、ここを見ると以下のような記述がある。

  • Compute time, measured in service hours: Windows Azure compute hours are charged only for when your application is deployed. When developing and testing your application, developers will want to remove the compute instances that are not being used to minimize compute hour billing. Partial compute hours are billed as full hours.

  • つまり、デプロイされていると、Stop状態であっても課金される認識でOK?
    deleteするの忘れて寝てしまうと、25Hなんてすぐ終わっちゃいますね・・・。
    Worker Roleも使った日には半日分です。(small instance * 2になるので)

    あと、Staging環境の扱いってどうなるんですかね。探したけど、どこにも見当たらず。
    なんとなくProduction環境と同じ扱いの気がしますが。StagingとProduction両方にデプロイしていると2つ分としてカウントされるんでしょうかねー?気になるところです。

    ちょっと怖いので、しばらく開発環境で我慢しようかな。
    まあ、MSDN Premium Subscriptionを確保できれば問題ないんだけどね・・・。

    2010年1月6日水曜日

    Web Service ClientがC:\Windows\Tempに書き込みできない例外

    Web参照を含むWebサービスクライアントを、Visual Studioでビルドしたときにできる [DllName].serializers.dll をデプロイに含める。
    そうしないと、実行時にC:\Windows\Tempに動的にファイルを作成しようとして、書き込みアクセス権がないユーザの場合に例外発生。

    詳細は↓参照。ジャストミートな内容。
    http://blogs.msdn.com/andreal/archive/2009/04/18/could-not-find-file-c-windows-temp-dll.aspx