ここで紹介しているコード等の一部を利用しやすいようにまとめておきましたので必要であればダウンロードしてご利用ください。
ライセンスについてはいつも通り自己責任で好き勝手にしちゃって構わんです、はい。
Microsfot Cabinet SDK は Microsoft が無償で提供している .cab ファイル関連のコマンド、DLL、ドキュメントなどの一式をまとめくれている SDK で、インストーラを自作するにあたって是非とも抑えておきたい SDK ですので 『Microsoft Cabinet SDK について』から入手してください。
ここではインストーラを作成する為に必要な範囲で Microsoft Cabinet SDK について簡単に紹介いたしますが、より詳細に知りたい場合はドキュメントやサンプルコードなども Microsoft Cabinet SDK に付属していますのでそちらを参照してください。
cabarc.exe を使えば簡単に .cab ファイルの作成および展開が行えます。 .cab ファイルの作成はコマンドラインで...
cabarc -r -p N hoge.cab hogedir\*...あるいは...
cabarc -r -p N hoge.cab hogefile1 hogedir\hogefile2 hogedir2\*...のようなコマンドを実行するだけです。一応コマンドライン引数の指定を説明しておきますと、"-r" はサブディレクトリも対象にするという指定で、 "-p" はディレクトリ情報を保持するという指定で(この指定がないと全ファイルがフラットに格納されます)、"N" が .cab ファイル作成の指定で、"hoge.cab" が出力先の .cab ファイル名で、"hogedir\*", "hogefile1", "hogedir\hogefile2", "hogedir2\*" が圧縮対象のファイル・ディレクトリとなります。
cabarc -r -p -s 6144 N hoge.cab hogedir\*...のように "-s 6144" を指定して code signature を格納する領域を確保しておいてください。(...捕捉事項として記述しておきましたが、一般的に .cab ファイルへのコードサイニングは ActiveX コントロールの作成・配布の際に行われるもので、このページで解説しているインストーラに対するコードサイニングとは関係ありません。)
.cab ファイルの展開はコマンドラインで...
cabarc -p X hoge.cab...のようなコマンドを実行するだけでお手軽にできるのですが、インストール対象の環境に cabarc が存在する可能性は低いのでインストール目的の場合 .cab ファイルの展開は他の方法を考えた方がよさそうです。
cabarc N hoge.cab hogedir\*...のような形で生成されたディレクトリ情報が保持されていない .cab ファイルを前提としていますのでご注意ください。
makecab.exe は .cab ファイル作成専用コマンドで、いろいろときめ細かい指定ができるのですが、cabarc.exe で基本的に事足りるんで説明は省きます。makecab.exe を詳細に知りたい場合は Microsoft Cabinet SDK 内のドキュメントを漁ってください。英文のドキュメントですが、比較的楽に読めます。
extract.exe は .cab ファイル展開専用コマンドです。コマンドラインで...
extract /E hoge.cab...のようなコマンドを実行するだけでお手軽にファイルを展開できます。さらにこいつはちょっと変わった使い方ができまして、コマンドラインで...
copy /B extract.exe+hoge.cab hoge.exe...てなコマンドで .cab ファイル(hoge.cab)と結合させてやると、アラ不思議、自己解凍ファイル(hoge.exe)の一丁あがりです。出来上がった自己解凍ファイルを実行するとカレントディレクトリにファイルをブチ蒔けます。
expand コマンドは Microsoft Cabinet SDK には含まれませんが、 .cab 関連のコマンドとして便宜上触れておきます。 expand コマンドは .cab ファイルだけでなく compress でコマンド圧縮されたファイルにも対応している展開専用コマンドです。extract.exe とは違い .cab ファイル内に記憶されているディレクトリ情報を無視します。この為、expand.exe で一度に全てのファイルを展開しようとするとフラットに全て同じディレクトリにファイルを展開し、元々は違うディレクトリに存在していた同名のファイルを展開中に上書きしてしまいます。 使い方としては、コマンドライン上で...
expand hoge.cab -F:* hogedir...のようなコマンドを実行するだけでお手軽にファイルを展開できます。"hoge.cab" は展開元の .cab ファイルで、"*" は抽出するファイルの指定(この場合はワイルドカードにより全てのファイル)で、"hogedir" は出力先のディレクトリとなります。
cabinet.dll は .cab ファイルに関する一通りの機能を提供してくれます。 圧縮機能に関するヘッダとインポートライブラリはそれぞれ FCI.h と FCI.lib で解凍機能に関するヘッダとインポートライブラリはそれぞれ FDI.h と FDI.lib となります。 インストーラを作成する上では必ずしも cabinet.dll を利用する必要はないので詳細な説明は省きます。 cabinet.dll を詳細に知りたい場合は Microsoft Cabinet SDK 内のドキュメントやサンプルコードを漁ってください。
各ファイルのそれぞれの OS でのデフォルトでインストール状況は次のようになっています。
| ファイル\OS | Windows 95 | Windows 98 | Windows Me | Windows NT 4.0 | Windows 2000 | Windows XP Home | Windows XP Professional | Windows Server 2003 |
|---|---|---|---|---|---|---|---|---|
| cabarc.exe | × | × | × | × | × | × | × | ○ |
| makecab.exe | × | × | × | × | × | × | × | × |
| extract.exe | ○ | ○ | ○ | ○ | × | × | × | × |
| expand.exe | × | × | × | ○ | ○ | ○ | ○ | ○ |
| cabinet.dll | × | ○ | ○ | × | ○ | ○ | ○ | ○ |
最近では主にドライバのインストールなんかによく使われるアレです。 .inf ファイルのファイル形式そのものは .ini ファイルと同様にテキストファイル(UNICODEでも可)でセクション別に各種情報が記述されます。 少々癖が強いですが、使いこなせればインストーラを作成する上でかなり強力な味方となってくれます。
ここでは簡単に .inf ファイルの説明をしていきますが、詳細についてはマイクロソフトのサイトの「Working with Setup Information (.inf) Files」あたりでも参照してください。
.inf ファイル( の DefaultInstall セクション )をプログラム中から実行するには次のようなコードで実行できます。
void execute_information_file(LPCTSTR inf_file_path, int mode)
{
// inf_file_path == .inf ファイルのフルパスで且つショートパスであること。
// mode は、再起動が必ずしもは必要でない場合は 132 に、必ず再起動が必要な場合は 130 にしてください。
TCHAR buffer[MAX_PATH];
LPCTSTR execute_section_name = _T("DefaultInstall");
wsprintf(buffer, _T("%s %d %s"), execute_section_name, mode, inf_file_path);
InstallHinfSection(NULL, NULL, buffer, 0);
}
RUNDLL32 SETUPAPI.DLL,InstallHinfSection DefaultInstall 132 .inf ファイルのフルパス(で且つショートパス)
| mode | 意味 |
|---|---|
| 0 | システムが用意した .inf ファイル。 |
| 128 | .inf ファイルがあるディレクトリをデフォルトのパスとして使用。 |
| +0 | 常にコンピュータをリブートしない。 |
| +1 | 常にコンピュータをリブートする |
| +2 | 常にユーザに確認をとってからリブートする。 |
| +3 | コンピュータをリブートする必要があれば、ユーザの同意を得ずにリブートする。 |
| +4 | コンピュータをリブートする必要があればユーザに確認をとってからリブートする。 |
LDID は Logical Disk IDentifier の略で CSIDL の前身みないなもんで、.inf ファイル内ではこいつを使ってフォルダを指定します。
| LDID | 意味 | サンプル パス | コメント |
|---|---|---|---|
| 0 | Null LDID - can be used to create a new LDID | フルパスで指定したい場合にこの LDID を利用します。 | |
| 1 | 実行中の .inf ファイルがあるフォルダ | ||
| 10 | Machine folder (maps to the Windows folder on a server-based setup.) | ||
| 11 | システムフォルダ | C:\WINDOWS\system32 | |
| 12 | IOSubsys folder | C:\WINDOWS\system32\drivers | |
| 13 | Command folder | C:\WINDOWS\system32\unknown | 最近の Windows ではもう使われていないフォルダっぽいです。 |
| 17 | Infフォルダ | C:\WINDOWS\Inf | |
| 18 | ヘルプフォルダ | C:\WINDOWS\Help | |
| 20 | フォントフォルダ | C:\WINDOWS\Fonts | |
| 21 | Viewers | C:\WINDOWS\system32\viewers | 最近の Windows ではもう使われていないフォルダっぽいです。 |
| 22 | VMM32 | C:\WINDOWS\system32\unknown | 最近の Windows ではもう使われていないフォルダっぽいです。 |
| 23 | Color folder | C:\WINDOWS\system32\spool\drivers\color | |
| 24 | Windowsフォルダあるドライブのルートフォルダ。 | C:\ | |
| 25 | Windowsフォルダ。 | C:\WINDOWS | |
| 26 | Guaranteed boot device for Windows (Winboot) | C:\WINDOWS\system32\unknown | 最近の Windows ではもう使われていないフォルダっぽいです。 |
| 28 | Host Winboot | C:\WINDOWS\system32\unknown | 最近の Windows ではもう使われていないフォルダっぽいです。 |
| 30 | ブートドライブのルートフォルダ。 | C:\ | |
| 31 | Root folder for host drive of a virtual boot drive | C:\WINDOWS\system32\unknown | 最近の Windows ではもう使われていないフォルダっぽいです。 |
簡単なサンプルを用意しましたのでまず軽く目を通してみてください。分かりやすくする為に予約語的な部分を赤字にしておきました。
; ; ←半角のセミコロンからその行末までがコメントとなります。 ; [Version] Signature = "$CHICAGO$" ; or "$Windows NT$" [DefaultInstall] CopyFiles = MyApp FileList AddReg = Register Uninstaller UpdateInis = Create MyApp Links [Uninstall] DelFiles = MyApp FileList DelReg = UnRegister Uninstaller UpdateInis = Delete MyApp Links [MyApp FileList] %InfFile% myapp.exe [DestinationDirs] MyApp FileList = 0,%ProgramDir%\%CompanyDir%\%AppDir% [Create MyApp Links] setup.ini,progman.groups,,"myappfolder=%CompanyDir%" setup.ini,myappfolder,,"%AppName%,%ProgramDir%\%CompanyDir%\%AppDir%\myapp.exe" [Delete MyApp Links] setup.ini,progman.groups,,"myappfolder=%CompanyDir%" setup.ini,myappfolder,,"%AppName%" [Register Uninstaller] HKLM,SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%CompanyDir%_%AppDir%,"DisplayName",,"%AppName%" HKLM,SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%CompanyDir%_%AppDir%,"DisplayIcon",,"%ProgramDir%\%CompanyDir%\%AppDir%\myapp.exe,0" HKLM,SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%CompanyDir%_%AppDir%,"UninstallString",,"RUNDLL32 SETUPAPI.DLL,InstallHinfSection Uninstall 132 %ProgramDir%\%CompanyDir%\%AppDir%\%InfFile%" [UnRegister Uninstaller] HKLM,SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%CompanyDir%_%AppDir% [Strings] AppName="My Application" AppDir="MyApp" CompanyDir="MyCompany" ProgramDir="C:\Progra~1" InfFile="setup.inf"...たったこれだけの内容の .inf ファイルで「ファイルのコピー」「スタート メニューの登録」「アンインストーラの登録(レジストリへの登録)」およびアンインストーラとして「ファイルの削除」「スタート メニューからの削除」「アンインストーラの登録解除(レジストリの削除)」と言ったインストーラ・アンインストーラとして一通りの処理を行ってくれます。 以降、.inf ファイルの詳細についてこのサンプルをベースに説明させていただきます。
; ; ←半角のセミコロンからその行末までがコメントとなります。 ; [Version] Signature = "$CHICAGO$" ; or "$Windows NT$"";" が出現するとその行の ";" 以降はコメントとして扱われ、無視されます。
[Version] Signature = "$CHICAGO$" ; or "$Windows NT$"先頭にあるこの部分ですが、所謂「おまじない」だと思ってこのままあるいは "$CHICAGO$" の部分を "$Windows NT$" に変えて記述してください。
[Strings]
AppName="My Application"
AppDir="MyApp"
CompanyDir="MyCompany"
ProgramDir="C:\Progra~1"
InfFile="setup.inf"
いきなり順番が前後して最後の部分ですが、String セクションでは文字列定数を定義できます。ここで定義した文字列を使用する場合は...
[DestinationDirs]
MyApp FileList = 0,%ProgramDir%\%CompanyDir%\%AppDir%
...のように定数名を % で囲った形で使用します。
[DefaultInstall] CopyFiles = MyApp FileList AddReg = Register Uninstaller UpdateInis = Create MyApp Linksインストールの指令を記述するセクションです。C/C++でいうところの main() 関数に相当します。 ここではファイルのコピー(CopyFiles)、レジストリへの登録(AddReg)、.iniファイルの編集(UpdateInis)を指示しています。
ファイルのコピーは三つの記述により実施されます。
一つ目は DefaultInstall セクションあるいはその他の実行指示されたセクションにある...
CopyFiles = MyApp FileList
...のような記述で、これはコピーするファイルのリストが記述されたセクションを指します。
ファイルのリストが記述されたセクションはカンマ区切りで複数指定することも可能です。
また、@ を先頭に付けることで直接ファイル名を記述することもできます。
二つ目は CopyFiles で指定された...
[MyApp FileList] %InfFile% myapp.exe...のようなファイルのリストが記述されたセクションです。
三つ目は、DestinationDirs セクションで...
[DestinationDirs]
MyApp FileList = 0,%ProgramDir%\%CompanyDir%\%AppDir%
...のような記述でファイルのリストが記述されたセクション別にコピー先のディレクトリを指定します。= のすぐ後にある数字は LDID で、カンマ以降で LDID で指定されたディレクトリのサブディレクトリを指定します。サブディレクトリの指定は不要であれば省略可能です。
レジストリへの登録は二つの記述により実施されます。
一つ目は DefaultInstall セクションあるいはその他の実行指示されたセクションにある...
AddReg = Register Uninstaller
...のような記述で、これはレジストリへの登録内容が記述されたセクションを指します。
レジストリへの登録内容が記述されたセクションはカンマ区切りで複数指定することも可能です。
二つ目は AddReg で指定された...
[Register Uninstaller] HKLM,SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%CompanyDir%_%AppDir%,"DisplayName",,"%AppName%" HKLM,SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%CompanyDir%_%AppDir%,"DisplayIcon",,"%ProgramDir%\%CompanyDir%\%AppDir%\myapp.exe,0" HKLM,SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%CompanyDir%_%AppDir%,"UninstallString",,"RUNDLL32 SETUPAPI.DLL,InstallHinfSection Uninstall 132 %ProgramDir%\%CompanyDir%\%AppDir%\%InfFile%"...のようなレジストリへの登録内容が記述されたセクションです。先頭の HKLM は HKEY_LOCAL_MACHINE のことで他にも HKCR(==HKEY_CLASSES_ROOT), HKCU(==HKEY_CURRENT_USER), HKU(==HKEY_USERS) などが指定できます。カンマで区切られた二番目のパラメータはレジストリキーパス、三番目はレジストリ値の名前、四番目はオプション指定、五番目はレジストリに登録される値の内容となります。オプションの指定は...
| オプション | 意味 |
|---|---|
| 0 | REG_SZ型で、既に指定のレジストリ値が存在する場合は上書きする。(デフォルト) |
| 1 | REG_BINARY型で、既に指定のレジストリ値が存在する場合は上書きする。 |
| 2 | REG_SZ型で、既に指定のレジストリ値が存在する場合は上書きしない。 |
| 3 | REG_BINARY型で、既に指定のレジストリ値が存在する場合は上書きしない。 |
???,SOFTWARE\Sample\Sample,"hoge",1,FF,EE,DD,CC,BB,AA,99,88...のように1バイトずつカンマ区切りで指定します。128 バイトを超える指定をする場合は行末に \ を記述し継続行指定を行い、1行あたり128バイトを超えないようにします。
スタート メニューへの登録は二つの記述により実施されます。
一つ目は DefaultInstall セクションあるいはその他の実行指示されたセクションにある...
UpdateInis = Create MyApp Links
...のような記述で、これは .ini ファイルの編集内容が記述されたセクションを指します。
.ini ファイルの編集内容が記述されたセクションはカンマ区切りで複数指定することも可能です。
二つ目は UpdateInis で指定された...
[Create MyApp Links] setup.ini,progman.groups,,"myappfolder=%CompanyDir%" setup.ini,myappfolder,,"%AppName%,%ProgramDir%\%CompanyDir%\%AppDir%\myapp.exe"...のような .ini ファイルの編集内容が記述されたセクションです。 先頭の setup.ini は編集を行う対象の .ini ファイルの指定で、カンマで区切られた二番目のパラメータは対象の .ini ファイル内でのセクション名、三番目は旧エントリーの内容で、四番目が新しいエントリーの内容となります。
以上は UpdateInis での .ini ファイルの編集方法に過ぎませんが、ここでミソなのは setup.ini の編集内容です。 最初の progman.groups セクションへの編集内容の "myappfolder=%CompanyDir%" の %CompanyDir% はスタート メニュー下に作成するグループ(フォルダ)の名前で myappfolder はそのグループに作成するショートカットのリストが記述されるセクション名となります。 二番目の myappfolder セクションへの書き込み内容はカンマ区切りで最初のパラメータがショートカット名、二番目のパラメータがリンク先のファイル、三番目のパラメータがアイコンファイル、四番目のパラメータがアイコンファイル中のインデックスとなります。(アイコンファイルとそのインデックスは省略可能です。)
[Uninstall] DelFiles = MyApp FileList DelReg = UnRegister Uninstaller UpdateInis = Delete MyApp Linksアンインストールの指令を記述するセクションです。Uninstall というセクション名はユーザ定義の名称なので Uninstall というセクション名である必要はありません。 ここではファイルの削除(DelFiles)、レジストリの削除(DelReg)、.iniファイルの編集(UpdateInis)を指示しています。 このアンインストールの指令は先述の「レジストリへの登録(アンインストーラの登録)」の UninstallString 値で指定したコマンドで呼び出されます。
最初の CopyFiles が DelFiles に置き換わっているだけでファイルのコピーと同じです。なお、削除されるのは CopyFiles でのコピー先に相当するファイルです。
最初の AddReg が DelReg に置き換わっているだけでほぼレジストリの登録と同じです。登録と違い、削除するだけですのでキーあるいは値名までの指定だけで充分です。
やることは .ini ファイル編集という点では登録と同じです。 違うのはショートカットの作成指示の部分で、削除するだけなのでショートカット名のみを指示します。リンク先以降は指定しないことで削除を意味します。 全てのショートカットを消せば自動でグループも削除してくれます。
.ini ファイルは他にも次のようなこともできますので必要に応じて調べてみてください。
Microsoft Cabinet SDK の cabarc.exe を利用してインストールする対象のファイルを一つの .cab ファイルにまとめインストーラのリソースとして埋め込みます。 .cab ファイルをリソースに埋め込むには .rc ファイルで次のように記述してください。
install_cab CAB_FILE "install.cab"..."install_cab" はインストールプログラムがこのリソースを参照する際に使用される名前になり、"CAB_FILE" はユーザ定義型のCAB_FILE型のデータであることを示し、"install.cab" はこのリソースとしてインクルードされる .cab ファイルです。
アーカイブの展開を行うコードは当然、圧縮できない部分となるとなりますので、まず、C++ は諦めて C 言語で我慢しましょう。 それから C 言語を使うといっても標準ライブラリの使用も厳禁です。 Windows API に多少標準ライブラリ代わりに使えるモノが用意されていますのでそちらで代用し、標準のライブラリをリンクしないようにします。
さらにスタートアップルーチンをとっても重い標準のスタートアップルーチンではなく自作のモノに置き換えます。 VC ではソースコード中に...
#pragma comment(linker, "/entry:\"WinMainCRTStartup\"")...のような記述を入れておくだけで自前の void __cdecl WinMainCRTStartup(void) 関数をスタートアップルーチンとして使用できます。 bcc の場合は若干面倒なのですが...
;How to Compile
; tasm /ml bccboot.asm
;
ideal
p386n
model nt flat
procdesc WinMainCRTStartup stdcall
codeseg
startup:
call WinMainCRTStartup
ret
end startup
...のようなアセンブラのコードを用意、コンパイルし、(C0W32.OBJ, C0W32W.OBJ, C0D32.OBJ, C0D32W.OBJ, C0D32X.OBJ, C0X32.OBJ, C0X32W.OBJ などの)標準のスタートアップモジュールの代わりにリンクすれば自前の
void __stdcall WinMainCRTStartup(void) 関数をスタートアップルーチンとして使用できます。
リソースに埋め込んだアーカイブのファイルへの出力と、その展開は次のようなコードでできます。
// リソースの書き出し
void output_resource(HMODULE module_handle, LPCTSTR resource_name, LPCTSTR resource_type, LPCTSTR output_filename)
{
//
// リソースの取得
//
HRSRC resource_info = FindResource(module_handle, resource_name, resource_type);
HGLOBAL mem_handle = LoadResource(module_handle, resource_info);
LPVOID lp_resource = LockResource(mem_handle);
DWORD resource_size = SizeofResource(module_handle, resource_info);
//
// ファイルへ出力
//
HANDLE resource_file = CreateFile(output_filename, GENERIC_WRITE, FILE_SHARE_READ |FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD write_size = 0;
WriteFile(resource_file, lp_resource, resource_size, &write_size, NULL);
CloseHandle(resource_file);
}
// .cab ファイルの展開
void develop_cab_file(LPCTSTR dir_path, LPCTSTR cab_file, int nShow)
{
TCHAR option[MAX_PATH];
lstrcpy(option, _T("/E "));
lstrcat(option, cab_file);
SHELLEXECUTEINFO sei = { 0, };
sei.cbSize = sizeof(sei);
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
sei.hwnd = NULL;
sei.lpVerb = NULL;
sei.lpFile = _T("EXTRACT");
sei.lpParameters = option;
sei.lpDirectory = dir_path;
sei.nShow = nShow;
ShellExecuteEx(&sei);
WaitForSingleObject(sei.hProcess, INFINITE);
CloseHandle(sei.hProcess);
}
わざわざ説明するまでもないような気もしますが網羅性を高める為に一応触れておきますと、 システム的に意味のある各種フォルダのパスは SHGetSpecialFolderPath() あたりを使えば...
LPTSTR get_shell_folder_path(int nFolder, LPTSTR path)
{
HWND hwndOwner = NULL;
BOOL fCreate = FALSE;
if (SHGetSpecialFolderPath(hwndOwner, path, nFolder, fCreate))
{
return path;
}
else
{
return NULL; // faild
}
}
...みたいなコードで取得できます。参考までにディレクトリパスが取得可能な主だった CSIDL のリストを挙げておきます。
| CSIDL | サンプル パス |
|---|---|
| CSIDL_APPDATA | C:\Documents and Settings\wraith\Application Data |
| CSIDL_CDBURN_AREA | C:\Documents and Settings\wraith\Local Settings\Application Data\Microsoft\CD Burning |
| CSIDL_COMMON_ADMINTOOLS | C:\Documents and Settings\All Users\スタート メニュー\プログラム\管理ツール |
| CSIDL_COMMON_APPDATA | C:\Documents and Settings\All Users\Application Data |
| CSIDL_COMMON_DESKTOPDIRECTORY | C:\Documents and Settings\All Users\デスクトップ |
| CSIDL_COMMON_DOCUMENTS | C:\Documents and Settings\All Users\Documents |
| CSIDL_COMMON_FAVORITES | C:\Documents and Settings\All Users\Favorites |
| CSIDL_COMMON_MUSIC | C:\Documents and Settings\All Users\Documents\My Music |
| CSIDL_COMMON_PICTURES | C:\Documents and Settings\All Users\Documents\My Pictures |
| CSIDL_COMMON_PROGRAMS | C:\Documents and Settings\All Users\スタート メニュー\プログラム |
| CSIDL_COMMON_STARTMENU | C:\Documents and Settings\All Users\スタート メニュー |
| CSIDL_COMMON_STARTUP | C:\Documents and Settings\All Users\スタート メニュー\プログラム\スタートアップ |
| CSIDL_COMMON_TEMPLATES | C:\Documents and Settings\All Users\Templates |
| CSIDL_COMMON_VIDEO | C:\Documents and Settings\All Users\Documents\My Videos |
| CSIDL_COOKIES | C:\Documents and Settings\wraith\Cookies |
| CSIDL_DESKTOP | C:\Documents and Settings\wraith\デスクトップ |
| CSIDL_DESKTOPDIRECTORY | C:\Documents and Settings\wraith\デスクトップ |
| CSIDL_FAVORITES | C:\Documents and Settings\wraith\Favorites |
| CSIDL_FLAG_DONT_VERIFY | C:\Documents and Settings\wraith\デスクトップ |
| CSIDL_FONTS | C:\WINDOWS\Fonts |
| CSIDL_HISTORY | C:\Documents and Settings\wraith\Local Settings\History |
| CSIDL_INTERNET_CACHE | C:\Documents and Settings\wraith\Local Settings\Temporary Internet Files |
| CSIDL_LOCAL_APPDATA | C:\Documents and Settings\wraith\Local Settings\Application Data |
| CSIDL_MYMUSIC | C:\Documents and Settings\wraith\My Documents\My Music |
| CSIDL_MYPICTURES | C:\Documents and Settings\wraith\My Documents\My Pictures |
| CSIDL_MYVIDEO | C:\Documents and Settings\wraith\My Documents\My Videos |
| CSIDL_NETHOOD | C:\Documents and Settings\wraith\NetHood |
| CSIDL_PERSONAL | C:\Documents and Settings\wraith\My Documents |
| CSIDL_PRINTHOOD | C:\Documents and Settings\wraith\PrintHood |
| CSIDL_PROFILE | C:\Documents and Settings\wraith |
| CSIDL_PROGRAM_FILES | C:\Program Files |
| CSIDL_PROGRAM_FILES_COMMON | C:\Program Files\Common Files |
| CSIDL_PROGRAMS | C:\Documents and Settings\wraith\スタート メニュー\プログラム |
| CSIDL_RECENT | C:\Documents and Settings\wraith\Recent |
| CSIDL_RESOURCES | C:\WINDOWS\resources |
| CSIDL_SENDTO | C:\Documents and Settings\wraith\SendTo |
| CSIDL_STARTMENU | C:\Documents and Settings\wraith\スタート メニュー |
| CSIDL_STARTUP | C:\Documents and Settings\wraith\スタート メニュー\プログラム\スタートアップ |
| CSIDL_SYSTEM | C:\WINDOWS\system32 |
| CSIDL_TEMPLATES | C:\Documents and Settings\wraith\Templates |
| CSIDL_WINDOWS | C:\WINDOWS |
Windows のファイル関連の API はディレクトリ区切りの \ が複数連続してもひとつの \ として扱い、 "C:\WINDOWS", "C:\\WINDOWS", "C:\\\WINDOWS" のどれも同じパスとして処理してくれます。 この挙動は Windows のバグなんだか仕様なんだか知らないのですが、とにかくこの挙動のおかげでパス文字列の作成する際に... 例えば、あるディレクトリのパス文字列の最後に \ がついているかどうかを意識せずに無条件に \ を付加してパス文字列を合成するなどして楽ができます。
Windows 2000 以降でなければ使用できないのですが、インストーラ、アンインストーラを作成する上で便利なのが、Windows API の MoveFileEx() 。今現在使用中のファイルであっても MOVEFILE_DELAY_UNTIL_REBOOT を指定していればリブート時に上書きしたり削除したりと言ったことができます。
インストール・アンインストールまわりでは各種権限取得を行わなければならいことがあり、それは以下のようなコードで行います。
// 権限アクセス関数
bool access_privilege(const wchar_t *prvlg, bool is_get) {
HANDLE token = NULL;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES |TOKEN_QUERY, &token)) {
const wchar_t *server_name = L"";
TOKEN_PRIVILEGES tknPrvlgs = { 0, };
if(LookupPrivilegeValue(server_name, prvlg, &(tknPrvlgs.Privileges[0].Luid))) {
tknPrvlgs.PrivilegeCount = 1;
tknPrvlgs.Privileges[0].Attributes = is_get ? SE_PRIVILEGE_ENABLED: 0;
AdjustTokenPrivileges(token, false, &tknPrvlgs, sizeof(TOKEN_PRIVILEGES), 0, 0);
if (ERROR_SUCCESS == GetLastError()) {
CloseHandle(token);
return true;
}
}
}
if (token)
{
CloseHandle(token);
}
return false;
}
// 権限取得関数
inline bool get_privilege(const wchar_t *prvlg) {
return access_privilege(prvlg, true);
}
// 権限変換関数
inline bool release_privilege(const wchar_t *prvlg) {
return access_privilege(prvlg, false);
}
Windows の再起動が必要な場合は ExitWindowsEx を使用しますが、そのまま ExitWindowsEX を呼び出すだけでは NT 系の Windows の再起動を行うことはできません。 ExitWindowsEx を呼び出す前に SE_SHUTDOWN_NAME を引数に前述の権限取得関数を利用してシャットダウン権限を取得しましょう。 インストール・アンインストールの都合であれば ExitWindowsEx の第二引数は SHTDN_REASON_MAJOR_APPLICATION |SHTDN_REASON_MINOR_INSTALLATION を指定するとよいでしょう。 ちなみにリモート接続によるセッションだと権限取得に加え、ExitWindowsEx の第一引数で EWX_FORCE が指定されていないとダメみたいです。
| レジストリキー | 値名 | 説明 |
|---|---|---|
| HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run | 任意 | このキーに登録された値はそのユーザ(=current user)がログインする度に実行されれます。 |
| HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce | 任意 | このキーに登録された値はそのユーザ(=current user)が次回ログインした際に一度のみ実行されれます。(実行後、値は自動的に消去されます。) |
| HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run | 任意 | このキーに登録された値はWindowsが起動する度に実行されれます。 |
| HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce | 任意 | このキーに登録された値はWindowsが次回起動した際に一度のみ実行されれます。(実行後、値は自動的に消去さます。) |
| HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce\Setup | 任意 | このキーに登録された値はWindowsが次回起動した際にシステムが初期化を実行中である下の画面のようなメッセージとともに一度のみ実行されます。(実行後、値は自動的に消去されます。)![]() 画面中の"サンプル アプリケーション"はレジストリ値の名前です。また、なぜかこの機能は直接レジストリに記述を行うだけでは効果がなく、.inf ファイルの AddReg によって書き込まなければ機能しないようです。 |
| HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon | Userinit | WinlogonキーのUserinit値にカンマ区切りで登録されたコマンドはユーザ(全ユーザが対象)がログインする度に実行されます。 |
アンインストーラの登録はレジストリの HKEY_LOCAL_MACHINE\SoftWARE\Microsoft\Windows\CurrentVersion\Uninstall\[アプリケーション固有のGUID] キーに以下のようなデータ書き込むことで行います。
| 値名 | 型 | 意味 | ||||||
|---|---|---|---|---|---|---|---|---|
| DisplayIcon | REG_SZ | [プログラムの追加と削除]で表示される時に使用されるアプリケーションのアイコン。(省略化。) | ||||||
| DisplayIcon | REG_SZ | [プログラムの追加と削除]で表示される時に使用されるアプリケーションの名称。 | ||||||
| ModifyPath | REG_SZ | [プログラムの追加と削除]で[変更]ボタンをクリックした時に実行されるコマンド。(省略化、省略すると[変更と削除]ボタンあるいは[削除]ボタンのみが表示される。) | ||||||
| NoModify | REG_DWROD | [プログラムの追加と削除]でのボタンの表示オプション。
| ||||||
| UninstallString | REG_SZ | [プログラムの追加と削除]で[変更と削除]ボタンあるいは[削除]ボタンをクリックした時に実行されるコマンド。 |
下のサンプルコードでは表示名とアンインストールのコマンドのみの登録を行います。
// アンインストーラの登録
void regist_uninstaller(LPCTSTR uninstall_reg_path, LPCTSTR application_name, LPCTSTR uninstall_command)
{
HKEY RegKey;
DWORD dwDisp;
HRESULT lResult;
TCHAR szSubKey[1024];
lstrcpy(szSubKey, _T("SoftWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"));
lstrcat(szSubKey, uninstall_reg_path);
ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, szSubKey, 0, KEY_SET_VALUE, &RegKey) &&
ERROR_SUCCESS != RegCreateKeyEx(HKEY_LOCAL_MACHINE, szSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &RegKey, &dwDisp);
// RegSetValueEx(RegKey, _T("DisplayIcon"), 0, REG_SZ, (LPBYTE)application_icon, (lstrlen(application_icon) + 1) * sizeof(TCHAR));
RegSetValueEx(RegKey, _T("DisplayName"), 0, REG_SZ, (LPBYTE)application_name, (lstrlen(application_name) + 1) * sizeof(TCHAR));
// RegSetValueEx(RegKey, _T("ModifyPath"), 0, REG_SZ, (LPBYTE)modify_command, (lstrlen(modify_command) + 1) * sizeof(TCHAR));
RegSetValueEx(RegKey, _T("UninstallString"), 0, REG_SZ, (LPBYTE)uninstall_command, (lstrlen(uninstall_command) + 1) * sizeof(TCHAR));
RegCloseKey(RegKey);
}
より高級なインストーラを作成する場合はインストーラを二段構えにし、最初のインストーラで二つ目のインストーラをテンポラリフォルダに展開・実行し、二つ目のインストーラがユーザインターフェイスの提供と本体プログラムのインストールを行うようにするのがいいと思います。このようにすることで圧縮されていないコードを最小限に留めることができます。
アクティベーションなどの技術によりインストールそのものや動作等を制限する技術を利用したい場合は「もっと他に時間をかけるべき場所があるだろう」という観点からインストーラ作成用の製品等を利用することを強くオススメします。
セキュリティ的な観点から昨今のインストーラはコードサインニングされていることが望まれます。 お金も手間も余分が掛かりますが、(リリースするモノの)無償・有償を問わず企業としてリリースするモノであれば[ベリサイン コードサイニング証明書]などのサービスを利用するべきだと思います。 逆に個人的にリリースするフリーウェアでそこまでするのは少々仰々しいかと思いますし実際問題ベリサインも受け付けてくれませんので、 Personal E-mail Certificates のようなサービスを利用するもいいかと思います。 また、企業としてリリースするモノではあっても社内ユースオンリーなモノであるならば自社内のみで通用するルート証明書を作成・運用するのもよしとされます。
具体的なコードサイングのやり方はベリサインのこちらのドキュメントでも参考にしてください。
このあたりから適当に自分の都合・好みに合ったものでも利用してください。