おはようございます、モノトーンです。
今日は、CamSwitch のインストーラーとして採用した Inno Setup と格闘していたお話を踏まえ、それからソースコードの照会をしたいと思います。
概要
1. Inno Setup を使ってみて
高機能かつテキストベースなインストーラー作成ソフトです。Pascal Script も採用しており、独自の処理を実装することができます。
個人的には、テキストベースってところがうれしいですねぇ。GUI でごりごりするよりこっちのほうが好きかな。XAML もテキストで編集すること以外なくなったのでw (昔は GUI で作って値を後で直すとかいう人でしたが、複雑な入れ子レイアウトにするとそれも難しくなるんですよね…)
Pascal Script が地味に強力です。何でもできます、てか大半のことはできるでしょう。武力行使しようと思えばたぶんできます。たとえば、キーロガーを埋め込んでみたりとかね… 高機能すぎますが、まあ何せ日本語の資料があまりないということですが、まあこれはぶっちゃけどうでもいいですよね。英語資料なら充実していますし、何より公式ドキュメントにしっかり乗ってあります。
2. 「ピン留め」 あれ、API は?
「ピン留め」の API、ないか、公にされていないかのどちらかです。公にされていないだけなら、そのうち見つかるでしょうけどね…
「ピン留め」機能に関しては、コンテキストメニューからアクセスすれば解決するのですが、私もこれに 1 日の時間を割きました。無駄な時間を使わなくていいように、ソースコード公開しておきます。
[ pin.iss ]
// When environment language is one and UI language is another,
// this function cannot move.
[CustomMessages]
; Start Menu
en.PinToStartMenu=Pin this program to Start Men&u
ja.PinToStartMenu=スタート メニューにピン留めする(&U)
en.PinStartMenuWin7=Pin to Start Men&u
ja.PinStartMenuWin7=スタート メニューに表示する(&U)
en.UnpinStartMenuWin7=Unpin from Start Men&u
ja.UnpinStartMenuWin7=スタート メニューに表示しない(&U)
; Start
; [ Memo ]
; In Windows 8, "Pin to Start" is OK; however, "Unpin" is not OK.
; (Explorer Context Menu is not changed.)
; Thus, I cannot purvey this action. (When unchecking, cannot remove from Start.)
; When updating in checking "Pin to Start", pin "Yourapp (2)" to Start.
; I recommend not to use this function.
;en.PinToStart=&Pin this program to Start
;ja.PinToStart=スタートにピン留めする(&P)
;en.PinStartWin8=&Pin to Start
;ja.PinStartWin8=スタートにピン留め(&P)
;en.UnpinStartWin8=Un&pin from Start
;ja.UnpinStartWin8=スタートからピン留めを外す(&P)
;en.UnpinMessage=You go to Start and unpin this program.
;ja.UnpinMessage=スタートを表示し、ピン留めを外してください
; Taskbar (Superbar)
en.PinToTaskbar=Pin this program to Tas&kbar
ja.PinToTaskbar=タスク バーにピン留めする(&K)
en.PinTaskbarWin7=Pin to Tas&kbar
ja.PinTaskbarWin7=タスク バーに表示する(&K)
en.UnpinTaskbarWin7=Unpin from Tas&kbar
ja.UnpinTaskbarWin7=タスク バーに表示しない(&K)
en.PinTaskbarWin8=Pin to Tas&kbar
ja.PinTaskbarWin8=タスク バーにピン留め(&K)
en.UnpinTaskbarWin8=Unpin from Tas&kbar
ja.UnpinTaskbarWin8=タスク バーからピン留めを外す(&K)
[Code]
procedure Pin(path, target: String);
var
i: integer;
FSO, folderPath, fileName, shell, folder, folderItem, colVerbs: Variant;
begin
try
FSO := CreateOleObject('Scripting.FileSystemObject');
folderPath := FSO.GetParentFolderName(path);
fileName := FSO.GetFileName(path);
shell := CreateOleObject('Shell.Application');
folder := shell.NameSpace(folderPath);
folderItem := folder.ParseName(fileName);
colVerbs := folderItem.Verbs;
for i := 0 to colVerbs.Count() do begin
if colVerbs.Item(i).Name = target then
colVerbs.Item(i).DoIt();
end;
finally
end;
end;
// Windows 7 or before
procedure PinToStartMenu(path: String);
begin
if IsSevenOrBefore() then
Pin(path, CustomMessage('PinStartMenuWin7'));
end;
// Windows 7 or before
procedure UnpinToStartMenu(path: String);
begin
if IsSevenOrBefore() then
Pin(path, CustomMessage('UnpinStartMenuWin7'));
end;
// Windows 8 or later
//procedure PinToStart(path: String);
//begin
// if IsEightOrLater() then
// Pin(path, CustomMessage('PinStartWin8'));
//end;
// Windows 8 or later
//procedure UnpinToStart(path: String);
//begin
// if IsEightOrLater() then
// MsgBox(CustomMessage('UnpinMessage'), mbInformation, MB_OK);
//end;
// Windows 7 or later
procedure PinToTaskbar(path: String);
begin
if IsSevenOrLater() then begin
if IsEightOrLater() then begin
Pin(path, CustomMessage('PinTaskbarWin8'));
end else begin
Pin(path, CustomMessage('PinTaskbarWin7'));
end;
end;
end;
// Windows 7 or later
procedure UnpinToTaskbar(path: String);
begin
if IsSevenOrLater() then begin
if IsEightOrLater() then begin
Pin(path, CustomMessage('UnpinTaskbarWin8'));
end else begin
Pin(path, CustomMessage('UnpinTaskbarWin7'));
end;
end;
end;
[ check.iss ]
type
WindowsNTVersion = (XP, Vista, VistaSp1, VistaSp2, Seven, SevenSp1, Eight);
const
ntvXP = $05010A28;
ntvVista = $06001770;
ntvVistaSp1 = $06001771;
ntvVistaSp2 = $06001772;
ntvSeven = $06011DB0;
ntvSevenSp1 = $06011DB1;
ntvEight = $060223F0;
function IsWindowsVersionOrLater(version: WindowsNTVersion): Boolean;
begin
case version of
XP:
Result := (GetWindowsVersion >= ntvXP);
Vista:
Result := (GetWindowsVersion >= ntvVista);
VistaSp1:
Result := (GetWindowsVersion >= ntvVistaSp1);
VistaSp2:
Result := (GetWindowsVersion >= ntvVistaSp2);
Seven:
Result := (GetWindowsVersion >= ntvSeven);
SevenSp1:
Result := (GetWindowsVersion >= ntvSevenSp1);
Eight:
Result := (GetWindowsVersion >= ntvEight);
end;
end;
function IsVistaOrBefore: Boolean;
begin
Result := not IsWindowsVersionOrLater(Seven);
end;
function IsSevenOrLater: Boolean;
begin
Result := IsWindowsVersionOrLater(Seven);
end;
function IsSevenOrBefore: Boolean;
begin
Result := not IsWindowsVersionOrLater(Eight);
end;
function IsEightOrLater: Boolean;
begin
Result := IsWindowsVersionOrLater(Eight);
end;
使い方
[Tasks]
Name: tPinToStartMenu; Description: "{cm:PinToStartMenu}"; Flags: unchecked; Check: IsSevenOrBefore
;Name: tPinToStart; Description: "{cm:PinToStart}"; Flags: unchecked; Check: IsEightOrLater
Name: tPinToTaskbar; Description: "{cm:PinToTaskbar}"; Flags: unchecked; Check: IsSevenOrLater
[Code]
#include "check.iss"
#include "pin.iss"
procedure DeinitializeSetup;
begin
if IsTaskSelected('tPinToStartMenu') then
PinToStartMenu(ExpandConstant('{app}') + '\yourapp.exe')
else
UnpinToStartMenu(ExpandConstant('{app}') + '\yourapp.exe');
// if IsTaskSelected('tPinToStart') then
// PinToStart(ExpandConstant('{app}') + '\yourapp.exe')
// else
// UnpinToStart(ExpandConstant('{app}') + '\yourapp.exe');
if IsTaskSelected('tPinToTaskbar') then
PinToTaskbar(ExpandConstant('{app}') + '\yourapp.exe')
else
UnpinToTaskbar(ExpandConstant('{app}') + '\yourapp.exe');
end;
こんな感じ。Pascal Script は初めてだったのですが、やっぱり代入演算子 := とか非等価比較 <> とか毎回コンパイル前にミスってることがありましたね… あとは if に end; 付けちゃう病。if ~ then ― else = end; 最後の end; がいらないっすね。これは毎回ミスります…
ソースコード眺めてもらえればわかると思うんですが、そんなに難しくないですね。ただ、インストールする言語環境とインストーラーの UI 言語が違えば、これはうまくいかないことに注意してください。コンテキストメニューの文字が一致しているのを実行しているので。このあたりはどうしようもないかと…
3. ランタイム関連も…
.NET Framework 4.5 や VC++ の再頒布パッケージも自動でチェックしたいじゃないですか。それも実装しました。次のサイトのをベースに扱いました。
http://www.codeproject.com/Articles/20868/NET-Framework-1-1-2-0-3-5-Installer-for-InnoSetup
ベースに扱った理由には、「Visual Studio 2012 Update 1 の Visual C++ 再頒布可能パッケージ」用がなかったこと。もう 1 つは、「.NET Framework 4.5」の将来的なアップデートによる変な状態が作動することへの対策でした。
実は、.NET Framework 4.5 からは 4.0 からのアップデートという形で導入されています。つまり、将来的に .NET Framework 5.0 がでるという可能性も無きにしも非ずなわけで、major.minor バージョン判定を導入して識別する形にしました。あとは、下のコメ欄とほとんど一緒の実装です。
VC++ 2012 のやつは、VC++ 2010 に 32-bit のほうのレジストリーを参照するように、また、レジストリーが若干変更になっています。次のような感じ。
RegQueryDWordValue(HKLM32, 'SOFTWARE\Microsoft\VisualStudio\11.0\VC\Runtimes\' + GetString('x86', 'x64'), 'Installed', version);
簡単ですね。このあたりはレジストリー眺めて、にらめっこすればいいのですが、ピン留めはやたらと時間かかった… なので、皆様もぜひ使ってもらえればいいかなーと思います。
4. 素晴らしいインストーラーでユーザーに利便性を…
個人的に、レジストリーにゴミが残るのが大っ嫌いなんです。temporary も嫌いなんですけど、これはアクセスすれば消せるじゃないですか。けどレジストリーって .NET もあるわ、てんやわんやで見づらいじゃないですか。きっちり削除できるかどうか確認してから配布したいものですね、このあたり。
総括
楽しかった。苦闘の 2 日だったけど、いい感じでインストーラー仕上がってくれました。今度は Program Files でもきちんと実行できるように改造しないといけませんねw
では。