2015年11月2日月曜日

VisualStudio の Install ShieldLimited Edition 2013 でアップデートインストール

Visual Studio 2013 でインストーラを作成する方法の一つに、Install Shield Limited Edition (以下、ISLE と略します)があります。 まだ使っていないという方の入り口としては、マイクロソフトさんのこのページあたりからたどるといいでしょうね。

で、インストールプロジェクトをこさえて、インストーラをビルドするまでの方法については、あちこちにまとめられたページが公開されていますので、わざわざ重複して紹介するまでもないでしょう。
と、いうことで入門から初回ビルドまでの情報についてはここでは記載しません。

インストーラをリリースした後でソフトウェアをバージョンアップしたインストーラを作成すると、そのままでは、以前のバージョンをコントロールパネルからアンインストールして新しいバージョンのインストーラを起動しなければならなくなってしまいます。
(じっさい、困ってしまいましたよw)

そこで、自動的にアップデートインストールできるような ISLE の設定方法をまとめることにしました。

1.ソフトウェアのバージョン情報
アプリケーションプロジェクトの[Properties]タブの[アセンブリ情報(I)...]ボタンを押すと、プロダクトのバージョン情報が表示されます。
アプリケーションのアセンブリ情報画面
この「アセンブリバージョン」「ファイルバージョン」にアプリケーションのバージョン情報を定義します。
バージョンアップの際は、この情報をより大きい数値に変えることになります。
バージョン情報 1.0.0.0 だったアプリケーションを 1.1.0.0 にバージョンアップする場合を例にとってみます。
バージョンを 1.0.0.0 から 1.1.0.0 にアップ


2.セットアッププロジェクトの構成
ここで、セットアッププロジェクトのバージョンもアップします。

現在のバージョン情報である、[Product Version]の部分を 1.00.0000 から 1.10.0000 に変更します。
このとき、その直後の [Product Code]も変更する必要があります。
[Product Code]の部分をクリックすると、右側に { } ボタンが現れますから、ここをクリックしてGUIDを再度生成します。

ここまでの設定でインストーラを構築した場合、すでに 1.00.0000 のバージョンがインストールされたコンピュータでインストーラを起動すると、プロダクトコードが異なるために、別のアプリケーションとしてインストールされてしまいます。つまり、同じアプリケーションが重複してインストールされてしまうことになります。
インストーラがそれ以前の過去のバージョンを自動的に削除してから新しいプロダクトをインストールできるようにするには、[Product Code]の次の行に記述されている[Upgrade Code]が関係してくることになります。

あ。ちょっと待って!
[Upgrade Code]の部分もクリックすると右側に { } ボタンが出現して新しいGUIDに更新できるようになっていますが、ここを変更してはいけません。

アップグレードインストールが正しく動作するようにするには、左側のツリーメニューにある、[Upgrade Paths]の部分を編集します。

Upgrade Paths の画面はこのようになっています。
ツリーの Upgrade Paths を右クリックして、「New Upgrade Path...」メニューを選択します。


ファイル選択ダイアログが表示されますが、「キャンセル」ボタンで閉じると、新しいアップグレードエントリが作成されています。


アップグレードの新しいノードには「Upgrade」のような名前を付けます。
プロパティの Max Version 部分には、新しく設定されたバージョン 1.10.0000 が表示されています。
その下の Include Max Version は、アップグレードの際に以前のバージョンを削除する最大バージョン情報を含めるかどうかを示しています。
ここは「No」を設定するほうがいいのかな、と思っています。


これで、アップグレードインストーラの完成です。

プロジェクトの [Upgrade Code] のGUIDを変更してしまった場合は、アップグレードノードを追加して、古いバージョンの Upgrade Code をセットアップ時の削除対象に追加する必要があります。
このとき、以前ビルドしたプロジェクトファイルのバックアップがないと、以前リリースした Upgrade Code を入手できなくなりますので、注意してください。

ISLEにはずいぶんと泣かされましたw
その他、苦労した部分についてもいずれ書けたらいいなと思ってるところです。

ではでは。


2015年10月6日火曜日

【C#】Outlook addin で RTF形式メールへのファイル添付処理(その後)

RTF形式メールに添付するとOutlookが落ちる問題の続き。

問題は、添付されたファイルをZIP書庫に圧縮する処理を入れようとして見つけたものです。
いろいろやって回避できそうな方法を見つけたので解説しておきます。


メールへの添付ファイルをZIP書庫に変更するには、次のイベントを処理します。

・ファイルが添付される直前に呼び出されるイベント
Outlook.ItemEvents_10_BeforeAttachmentAddEventHandler
void beforeAttachmentAdd(Outlook.Attachment attachment, ref bool Cancel)
パラメータは、添付されるファイルの情報と、問い合わせなどでの中断可能なフラグ参照。

添付されたファイルをZIP書庫に圧縮して添付する処理を考える場合、次のような手順で実現します。
void beforeAttachmentAdd(Outlook.Attachment attachment, ref bool Cancel)
{
    //一時ファイルに添付ファイルを保存
    string work_file = 一時ファイル名;
    attachment.SaveAsFile(work_file);

    //一時ファイルをZIP圧縮
    string zip_file = ZIP書庫名;
    ZIP書庫を作成する処理();

    //添付ファイルを入れ替える
    Outlook.Attachments attachments = attachment.Parent.Attachments;
    attachments.Remove(attachment.Index); //※添付ファイルを削除
    attachments.Add(zip_file);

    //後処理
    File.Delete(zip_file);
    File.Delete(work_file);
}

先の報告で Outlookが落ちるケースでは、「※添付ファイルを削除」の部分 (Attachments.Remove)で内部エラーが発生してうまくいきませんでした。

RTF形式メールの場合は、次のように処理するとうまくいきます。(うまくいくように見えます)
void beforeAttachmentAdd(Outlook.Attachment attachment, ref bool Cancel)
{
    //ZIP書庫の作成手順は上記と同じ

    bool isRtf = (attachment.Parent.BodyFormat ==  Outlook.OlBodyFormat.olFormatRichText);
    if(!isRtf)
    {
        //テキストメール、HTMLメールの場合は上記と同じ処理
    }
    else
    {
        Cancel = true;  //添付されたファイルをキャンセルして添付させないようにする
        Outlook.Attachments attachments = attachment.Parent.Attachments;
        attachments.Add(zip_file);
    }
    //後処理は上記と同じ
}

ファイルが添付される前処理段階でZIP書庫を添付し、イベントで伝えられた添付を Cancel することで、Outlook が落ちるという致命傷を回避して添付することができました・・・

ところがですねぇ。
環境によって、うまくいく場合とうまくいかない場合が出てしまうんですね。

■添付された(はずの)ファイルがメールに表示されない。
これは困りものです。実際には表示されないケースとしては2つのパターンがあります。

1.メールにはちゃんと添付されているんだけど表示がされていない場合
 この場合は、メールの形式をいったんテキスト形式にした後、RTF形式にすると表示されます。
2.Cancel = true; フラグで一緒に添付されたファイルもキャンセルされてしまう場合
  添付ファイル削除イベントハンドラに情報が渡されることで確認できます。

■複数のファイルを添付しようとすると、イベントが呼び出されない。
ZIP書庫の作成時にモーダル画面を表示させて問い合わせなどを行うと、複数ファイルの添付イベントで2回目以降のイベントが発生しない場合があります。

これらの問題を回避するには・・・

次回(いつだよw)に書くことにします。

※この記事での注意事項
(1) attachment.Parent.BodyFormat, attachment.Parent.Attachments の取得は attachment.Parent を Outlook.MailItem などにキャストしたあとで取得します。

(2) attachments.Remove(attachment.Index); の部分を attachment.Delete(); で処理するように記載されているサンプルをよく見かけますが、例外が出る場合がありました
(詳しくわかりませんでした・・・)

(3)最後の問題点(イベントが呼び出されない場合がある)については、その後も解決してません。

2015年8月31日月曜日

【C#】 仮想ListView で頻繁に RetrieveVirtualItem が呼び出される

C# の Windows フォーム で ListView を仮想モードで動作させると、リストビュー上にマウスカーソルがあるだけで、頻繁に RetrieveVirtualItem が呼び出される、という現象がでます。

初期表示状態では、落ち着いていてもソートやスクロールをした後だと、どうも表示範囲の再表示が完了したことをわかってないみたいで、マウスがじっとしていても何度も何度も呼び出されてしまい、性能の低いPCだと、CPUの使用率が100%になったり、どうかすると真っ白になって固まってしまいます。
(ListViewItem のリストをキャッシュしていても、です)

ネット上にListView のソースらしきものが落ちていましたので、眺めてみましたところ・・・

たとえば、MouseHover の部分を見ると、

        protected override void OnMouseHover(EventArgs e)  {
            ListViewItem item = null;
            if (this.Items.Count > 0) {
                Point pos = Cursor.Position;
                pos = PointToClientInternal(pos);
                item = GetItemAt(pos.X, pos.Y);
            }
 
という記述があり、必要かどうかを判断することなくマウス位置の要素を取り出してます。
ということで、ListView の派生クラスをこさえて、MouseHover を処理しないという暴挙wを働いてみました。

    public class SsListView : System.Windows.Forms.ListView
    {
        public SsListView()
            : base()
        {
        }
        protected override void OnMouseHover(EventArgs e)
        {
             // base.OnMouseHover(e);
        }
   }

デザイナーで読み込む方法を知らないので、フォームのコンストラクタで上記の派生クラスを張り付けた状態で動作させると、マウスが静止しているときには GetItemAt が呼ばれなくなり、CPUの占有がなくなりました。
※この方法では、リスト項目のツールチップが表示されなくなってしまいますので注意。

でも、リストビュー上でマウスを動かし続けると、相変わらず更新され続けてしまいます。
そこで、派生クラスに次のコードを追加してみました。

         const int WM_MOUSEMOVE = 0x0200;
        [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected override void WndProc(ref Message msg)
        {
            switch (msg.Msg)
            {
                case WM_MOUSEMOVE:
                    if ((Control.MouseButtons & MouseButtons.Left) != MouseButtons.Left)
                    {
                        return; // 無視させる
                    }
                    break;

            }
            base.WndProc(ref msg);
            return;
        }

ドラッグ処理中じゃなければ、無視するようにしてみたわけです。
これで、マウスをいくら動かしてもCPUを占有しなくなりましたよ。

何らかの弊害があるのかもしれませんが、とりあえずうまくいくようになったみたい・・・

本件について別の情報を入手しましたので追記しておきます。(まだ試してないw)
https://stackoverflow.com/questions/938896/flickering-in-listview-with-ownerdraw-and-virtualmode

2015年7月31日金曜日

Outlook 2010 アドイン開発で Outlook の問題を発見。

Outlook のメール形式に「リッチテキスト形式」ってのがあって、アドインで添付すると添付ファイルが表示されなかったりする問題もあるんだけど、もっと致命的なバグを見つけてしまった。

再現性の確認のために手順書いておきます。

環境:Windows 8.1 Pro 64bit + Outlook 2010 + VisualStudio 2012 Pro

手順:
1.C:\Temp\texst1.txt ファイルを用意

2.Outlook2010 アドインプロジェクトを作成
  2-1.アドインを作成
  2-2.インスペクタを登録
  2-3.BeforeAttachAdd() を記述
  2-4.メッセージボックスを表示
        [キャンセル]=添付を削除
        [いいえ]=そのまま添付
        [はい]=ファイル交換処理
  2-5.ファイルを交換(「はい」の場合)
        int index = attachment.Index
        (Parent).Attachments.Remove(index);
        (Parent).Attachments.Add("c:\Temp\test1.txt");を実施

3.Outlookを実行(デバッグ実行で)
  3-1.新規メールを作成
  3-2.リッチテキスト形式に変更
  3-3.ファイルを添付
  3-4-1-1.添付ファイルを [CTRL]-[C]でコピー
  3-4-1-2.[CTRL]-[V]で貼り付け
  または
  3-4-2-1.添付ファイルドラッグして別の位置に移動

  Outlook が落ちる。

HTML形式、テキスト形式のメールでは落ちません。
ま、再現性確認のためにメッセージボックスはいらないんだけどね。

以下、ソースの 抜粋です。(コメントなくてすんません。)

    public partial class ThisAddIn
    {
        Outlook.Inspectors Inspectors;

        private const string TempFile = @"C:\Temp\test1.txt";

        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            Inspectors = Application.Inspectors;
            Inspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
        }

        private void Inspectors_NewInspector(Outlook.Inspector inspector)
        {
            if (inspector.CurrentItem == null)
            {
                return;
            }

            Outlook.MailItem mailItem = inspector.CurrentItem as Outlook.MailItem;
            if (mailItem != null)
            {
                // add event handler for BeforeAttachAdd
                Outlook.ItemEvents_10_BeforeAttachmentAddEventHandler beforeAddHandler = new Outlook.ItemEvents_10_BeforeAttachmentAddEventHandler(OnBeforeAttachmentAdd);
                mailItem.BeforeAttachmentAdd += beforeAddHandler;
                return;
            }
        }

        private void OnBeforeAttachmentAdd(Outlook.Attachment Attachment, ref bool Cancel)
        {
            // change file
            int index = Attachment.Index;
            Outlook.MailItem mailItem = Attachment.Parent as Outlook.MailItem;
            if(mailItem != null){
                Outlook.Attachments attachments = mailItem.Attachments;
                attachments.Remove(index);

                attachments.Add(TempFile, Outlook.OlAttachmentType.olByValue);
            }
            return;
        }

    }

特定するまで、結構時間かかっちゃうんだよね。
こういう問題を発見するのって、開発しているアドイン側の問題として指摘されるわけで、クライアントさんからものすごい指摘を受けながら対策しなきゃならないんで、プレッシャーもかかるし調査にもとっても時間がかかって進捗とかほかの作業にも影響が出ます。


なんか、損な役割だなぁって思っちゃうです。

上記の件はマイクロソフトさんに報告済みですので、将来的にこの記事は意味を持たなくなります。

あと、このコードを参考にして自分のアドインを開発しようとする方がいるかもしれませんので、サンプルコードで端折っていることを書いておきます。

・インスペクタで += したハンドラは、クローズ時に -= で解除します。(ラッパクラスを作るといい)
・左辺値に代入したCOMオブジェクトは、Marshal.ReleaseComObject で参照解除します。

2015年7月14日火曜日

無料で構築できるスパムフィルタ - Becky! 編 -

いまのところメールクライアントには Becky! を使っています。

毎日毎日大量のスパムメールに辟易しておりましたが、ふと思いついてちょいと設定を変更して見ましたら、みごとにスパムを受信しなくて済むようになったので、その方法をば。

「ご自分のメールサーバで転送設定ができること」が前提となりますのでご了承ください。
以下は、POP3の例です。

手順1.gmail アカウントを用意します。
無料で15GBも利用できる gmail アカウントを用意します。
https://mail.google.com/

手順2.メールサーバで転送設定します。
自分のメールアカウントのサーバでメールの転送設定を実施して、受信メールを上記のグーグルメールに転送するようにしておきます。
(転送したメールは、他の方法で受信しなくなるのでサーバに残さない設定にします)

手順3.Becky!のメール設定で POP3 の受信(だけ)を gmail アカウントに設定します。
プロパティの設定例は、こんな感じ。



要するに、受信メールは gmail のアカウント、送信メールはメールアカウントってことね。

一日に何十回もスパムメールを範囲選択して削除してましたが、そのストレスたるや。

GMAILのスパムフィルタは強力なので、ほとんどスパムを受信することがなくなりました。

快適快適!笑

あ。そうそう。
メールクライアントからのgmail アクセスで、「安全性の低いアプリからのアクセス」とかでつながらない場合があるので、そこは(ご自分の責任の範囲で)設定を変更してくださいませ。

メール接続方法の変更で、メールが受信できなくなったり送信できなくなったりすることがあるかもしれないので、変更前の設定を必ず記録しておいてくださいね。

(変更はご自分の責任の範囲で行ってくださいませ。)

[PR]-------------------------------------------------------------
  企業メールの、より安全なセキュリティ対策 【Becky! 暗号化添付プラグイン】
     http://www.seasoft.co.jp/products/bkencryptsender.html
     http://www.vector.co.jp/soft/winnt/net/se501817.html
-----------------------------------------------------------------