モノトーンの伝説日記

OBS Studio と Blackmagic Design が大好き。

Inno Setup でピン留めを実現する

 おはようございます、モノトーンです。

 今日は、CamSwitch のインストーラーとして採用した Inno Setup と格闘していたお話を踏まえ、それからソースコードの照会をしたいと思います。

概要

  1. Inno Setup を使ってみて
  2. 「ピン留め」 あれ、API は?
  3. ランタイム関連も…
  4. 素晴らしいインストーラーでユーザーに利便性を

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

 では。