3 using System.Diagnostics;
\r
5 using NaGet.SubCommands;
\r
6 using NaGet.InteropServices;
\r
7 using System.Xml.Serialization;
\r
9 namespace NaGet.Packages.Install
\r
13 /// ダウンロード・インストール処理をカプセル化するクラス
\r
15 public class Installation
\r
17 private Package installedPackage;
\r
22 public Package InstalledPackage {
\r
23 get { return installedPackage; }
\r
25 installedPackage = value;
\r
27 installerIndex = getPreferInstallerIndex(value);
\r
28 installerFile = getArchiveFilePath();
\r
33 /// (保存される)ーインストーラのファイルのパス
\r
35 private string installerFile;
\r
38 /// インストールが完了されたか否かのフラグ
\r
40 private bool installed = false;
\r
43 /// サイレントインストールを行うか否か
\r
45 private bool silent = false;
\r
48 /// 起動するインストーラーのインデックス番号
\r
50 protected int installerIndex = 0;
\r
53 /// 外部アプリのエラー出力の受信ハンドラ
\r
55 public event EventHandler<NaGet.Utils.AnyDataEventArgs<string>> ErrorDataReceived;
\r
58 /// 外部アプリの標準出力の受信ハンドラ
\r
60 public event EventHandler<NaGet.Utils.AnyDataEventArgs<string>> OutputDataReceived;
\r
65 public Installation()
\r
72 /// <param name="package">インストールするパッケージ</param>
\r
73 public Installation(Package package)
\r
75 InstalledPackage = package;
\r
79 /// インストーラーファイルを取得する
\r
81 public string InstallerFile {
\r
82 get { return installerFile; }
\r
88 public string InstallerURL {
\r
89 get { return InstalledPackage.Installer[installerIndex].Url.Href; }
\r
93 #region インストール状態チェック関連
\r
98 public bool IsInstallablePackage()
\r
100 return installerIndex >= 0;
\r
106 public bool Downloaded {
\r
108 return File.Exists(installerFile) && ((File.GetAttributes(installerFile) & FileAttributes.Hidden) != FileAttributes.Hidden);
\r
113 /// インストール作業済みであるか否か。実際にインストールされたかどうかではありません。
\r
115 public bool Installed {
\r
116 get { return installed; }
\r
120 /// インストーラーの処理が成功してインストールされたプログラムが確認できるか否か。
\r
122 public bool InstallSuccessed {
\r
124 switch (InstalledPackage.Type) {
\r
125 case InstallerType.ARCHIVE:
\r
126 case InstallerType.ITSELF:
\r
127 // アーカイブインストーラーおよび配布exeそれ自身が実行ファイルのとき、
\r
128 // (AppliStationの作る)プログラムフォルダーの存在のみで確認を行う。
\r
129 return Directory.Exists(Path.Combine(NaGet.Env.ArchiveProgramFiles, InstalledPackage.Name));
\r
130 case InstallerType.EXEC_INSTALLER:
\r
131 case InstallerType.MSI_PACKAGE:
\r
132 return RegistriedUninstallers.GetInstalledPackageFor(InstalledPackage) != null;
\r
140 /// サイレントインストールを行うかのフラグ。
\r
142 public bool Silent {
\r
144 return (SupportsSilentOnly)? true :
\r
145 (IsSupportsSilent)? silent :
\r
148 set { silent = value; }
\r
152 /// サイレントインストールをサポートしているか否か
\r
154 public bool IsSupportsSilent {
\r
156 switch (InstalledPackage.Type) {
\r
157 case InstallerType.ARCHIVE:
\r
158 case InstallerType.ITSELF:
\r
159 case InstallerType.MSI_PACKAGE:
\r
161 case InstallerType.EXEC_INSTALLER:
\r
162 return ! string.IsNullOrEmpty(InstalledPackage.SilentInstallArguments);
\r
170 /// サイレントインストールだけをサポートしているか否か
\r
172 public bool SupportsSilentOnly {
\r
174 return (InstalledPackage.Type == InstallerType.ARCHIVE)
\r
175 || (InstalledPackage.Type == InstallerType.ITSELF);
\r
180 /// 選択されたパッケージは、AppliStationではなくPCへのインストールをするのか否かを返す。
\r
182 /// <remark>RunAsが必要か否かの判断にしようされる</remark>
\r
183 public bool TargetPC {
\r
185 return (InstalledPackage.Type != InstallerType.ARCHIVE)
\r
186 && (InstalledPackage.Type != InstallerType.ITSELF);
\r
195 /// <param name="downloader">ダウンローダオブジェクト</param>
\r
197 public void Download(Downloader downloader)
\r
200 string url = InstalledPackage.Installer[installerIndex].Url.Href;
\r
201 downloader.Download(url, installerFile);
\r
203 // サーバ指定のファイル名に変更する
\r
204 if (! string.IsNullOrEmpty(downloader.DownloadedFileName)) {
\r
205 string newFile = Path.Combine(Path.GetDirectoryName(installerFile), downloader.DownloadedFileName);
\r
206 File.Move(installerFile, newFile);
\r
207 installerFile = newFile;
\r
212 string targetDir = Path.GetDirectoryName(installerFile);
\r
213 NaGet.Utils.SetAccessControlRecursive(targetDir, File.GetAccessControl(Path.GetDirectoryName(targetDir)));
\r
214 } catch (Exception) {} // 失敗時は何もしない
\r
219 /// インストーラーファイルをスキャンする
\r
221 /// <remarks>ウイルスのため退避・削除されたときも例外を投げずにあたかも正常かのように動作しえます。</remarks>
\r
222 /// <exception cref="ComException">スキャンで意図せぬ動作があったとき</exception>
\r
223 /// <param name="scanner">スキャナーオブジェクト</param>
\r
224 /// <returns>スキャン結果</returns>
\r
226 public DownloadScannerResult ScanInstallerFile(DownloadScannerService scanner)
\r
228 DownloadScannerResult result;
\r
229 Exception e = null;
\r
231 result = scanner.Scan(installerFile, InstalledPackage.Installer[installerIndex].Url.Href);
\r
232 } catch (Exception ex) {
\r
233 result = DownloadScannerResult.ScannerNotFound;
\r
238 // ファイルが消されていないが例外が発生していたときは、その例外を投げる
\r
246 /// ハッシュ検証のためのハッシュの種類の数を返す
\r
248 /// <returns>ハッシュの個数</returns>
\r
249 public int GetRegisteredHashCount()
\r
251 HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;
\r
252 return (hashValues == null)? 0 : hashValues.Length;
\r
258 /// <returns>検証にパスしたらtrue、パスしなかったらfalse</returns>
\r
259 public bool VerifyHashValues()
\r
261 HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;
\r
262 if (hashValues != null) {
\r
263 foreach (HashValue hash in hashValues) {
\r
264 if (! hash.Validate(installerFile)) {
\r
273 private int invokeInstaller(string installerfile, InstallerType type)
\r
275 if (! File.Exists(installerfile)) {
\r
276 throw new FileNotFoundException(string.Format("{0} is not found, perhaps failed to download.", installerfile), installerfile);
\r
279 Process hProcess = null;
\r
282 case InstallerType.EXEC_INSTALLER:
\r
284 hProcess = Process.Start(installerfile, InstalledPackage.SilentInstallArguments);
\r
286 hProcess = Process.Start(installerfile);
\r
290 case InstallerType.MSI_PACKAGE:
\r
291 string msiexecArgs = string.Format("{0} /norestart /i \"{1}\" REBOOT=ReallySuppress",
\r
292 (Silent)? "/passive" : string.Empty,
\r
295 hProcess = Process.Start("msiexec", msiexecArgs);
\r
297 case InstallerType.ARCHIVE:
\r
298 case InstallerType.ITSELF:
\r
299 string argument = string.Format("-i \"{0}\" \"{1}\"", installerfile, this.InstalledPackage.Name);
\r
300 hProcess = createExtractArchiveProcess(argument,
\r
301 this.OutputDataReceived,
\r
302 this.ErrorDataReceived);
\r
303 // Note: ARCHIVEかITSELFの判断はarchive-instが行う
\r
306 throw new NotImplementedException("Not implemented archive installation yet");
\r
309 if (NaGet.Env.InstallProcessOnBackground) {
\r
311 hProcess.PriorityClass = ProcessPriorityClass.Idle;
\r
312 } catch (Exception) {}
\r
315 hProcess.WaitForExit();
\r
317 return hProcess.ExitCode;
\r
319 if (hProcess != null) {
\r
326 /// インストーラー等を起動してインストール作業を行う
\r
328 /// <returns>インストーラーの終了コード</returns>
\r
329 public int Install()
\r
331 string installerFile = this.installerFile;
\r
332 string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
\r
334 // アーカイブされているなら一旦展開
\r
335 if (InstalledPackage.ArchivedInstaller) {
\r
336 Directory.CreateDirectory(tempDir);
\r
338 string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir);
\r
339 using (Process hProcess = createExtractArchiveProcess(argument,
\r
340 this.OutputDataReceived,
\r
341 this.ErrorDataReceived)) {
\r
342 if (NaGet.Env.InstallProcessOnBackground) {
\r
344 hProcess.PriorityClass = ProcessPriorityClass.Idle;
\r
345 } catch (Exception) {}
\r
348 hProcess.WaitForExit();
\r
350 if (hProcess.ExitCode != 0) {
\r
351 throw new ApplicationException("Extract error " + installerFile + " to " + tempDir);
\r
355 installerFile = seekInstallerFile(tempDir, InstalledPackage.Type);
\r
356 if (installerFile == null && Directory.GetDirectories(tempDir).Length == 1) {
\r
357 installerFile = seekInstallerFile(Path.Combine(tempDir, Directory.GetDirectories(tempDir)[0]), InstalledPackage.Type);
\r
361 int exitCode = invokeInstaller(installerFile, InstalledPackage.Type);
\r
368 #region インストーラーファイル設定時の処理
\r
371 /// もっともふさわしいインストーラー番号を返す
\r
373 /// <returns></returns>
\r
374 private static int getPreferInstallerIndex(Package InstalledPackage)
\r
376 if (InstalledPackage.Type == InstallerType.CANNOT_INSTALL) { // インストール不能パッケージ
\r
383 for (int i = 0; i < InstalledPackage.Installer.Length; i++) {
\r
384 Platform platform = InstalledPackage.Installer[i].Platform;
\r
387 if (platform != null) {
\r
388 pts = (platform.IsRunnable())? 10 : 0;
\r
389 pts += (platform.IsRunnableArchOnWow64() && platform.IsRunnableOS())? 1 : 0;
\r
390 } else { // if (platform == null) {
\r
391 pts = 1; // null の場合は動作プラットホーム扱い
\r
393 if (pts > bestVal) {
\r
403 /// インストーラーの保存先パスを生成
\r
405 /// <remarks>installerIndexが求まっているものとして仮定して処理する</remarks>
\r
406 private string getArchiveFilePath()
\r
408 Package package = this.InstalledPackage;
\r
410 string folderName = string.Format("{0}({1})", package.Name, package.Version);
\r
411 string subFolderName = string.Format("installer.{0}", (installerIndex >= 0)? installerIndex.ToString("D") : "X");
\r
412 string fileName = NaGet.Utils.Url2filename(new Uri(package.Installer[0].Url.Href));
\r
414 string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, Path.Combine(folderName, subFolderName));
\r
416 if (! File.Exists(Path.Combine(folder, fileName))) {
\r
417 if (Directory.Exists(folder)) {
\r
418 if (! File.Exists(Path.Combine(folder, fileName))) {
\r
419 fileName = seekInstallerFile(folder, package.Type) ?? fileName;
\r
422 Directory.CreateDirectory(folder);
\r
426 Directory.SetAccessControl(folder, Directory.GetAccessControl(Path.GetDirectoryName(folder)));
\r
427 } catch (Exception) {} // 失敗時無視
\r
431 return Path.Combine(folder, fileName);
\r
439 /// <param name="basedir">探すフォルダー</param>
\r
440 /// <param name="type">インストーラーの種類</param>
\r
441 /// <returns>探し出されたインストーラーファイルのフルパス。存在しないならばnull</returns>
\r
442 private static string seekInstallerFile(string basedir, InstallerType type)
\r
444 if (! Directory.Exists(basedir)) {
\r
448 System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();
\r
450 case InstallerType.MSI_PACKAGE:
\r
451 list.AddRange(Directory.GetFiles(basedir, "*.msi"));
\r
453 case InstallerType.EXEC_INSTALLER:
\r
454 list.AddRange(Directory.GetFiles(basedir, "*.exe"));
\r
456 case InstallerType.ARCHIVE:
\r
457 list.AddRange(Directory.GetFiles(basedir, "*.zip"));
\r
458 list.AddRange(Directory.GetFiles(basedir, "*.lzh"));
\r
459 list.AddRange(Directory.GetFiles(basedir, "*.cab"));
\r
460 list.AddRange(Directory.GetFiles(basedir, "*.7z"));
\r
461 list.AddRange(Directory.GetFiles(basedir, "*.tar*"));
\r
463 case InstallerType.ITSELF:
\r
464 list.AddRange(Directory.GetFiles(basedir, "*.exe"));
\r
472 delegate(string file) {
\r
473 return ! File.Exists(file);
\r
477 // "setup"の語が入ったファイルはインストーラとみなし、優先選択
\r
478 foreach (string path in list) {
\r
479 if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) {
\r
485 return (list.Count > 0)? list[0] : null;
\r
490 /// アーカイブファイルの展開・インストールを行う
\r
492 /// <param name="archiveInstArgs">"archive-inst.exe"への引数</param>
\r
493 /// <param name="outputReceived">標準出力用リスナ(null可)</param>
\r
494 /// <param name="errorReceived">エラー出力用リスナ(null可)</param>
\r
495 /// <returns>実行プロセス</returns>
\r
496 private static Process createExtractArchiveProcess(string archiveInstArgs,
\r
497 EventHandler<NaGet.Utils.AnyDataEventArgs<string>> outputReceived,
\r
498 EventHandler<NaGet.Utils.AnyDataEventArgs<string>> errorReceived)
\r
500 string archiveInstExe = Path.GetFullPath("archive-inst.exe");
\r
501 if (! File.Exists(archiveInstExe)) {
\r
502 string errMsg = string.Format("\"{0}\" does not found!");
\r
503 throw new ApplicationException(errMsg,
\r
504 new FileNotFoundException(errMsg, archiveInstExe));
\r
509 ProcessStartInfo procInfo = new ProcessStartInfo(archiveInstExe, archiveInstArgs);
\r
510 procInfo.UseShellExecute = false;
\r
511 procInfo.CreateNoWindow = true;
\r
512 procInfo.WorkingDirectory = Environment.CurrentDirectory;
\r
514 return NaGet.Utils.ProcessStartWithOutputCapture(procInfo, outputReceived, errorReceived);
\r
518 /// ダウンロードしたインストーラファイルを削除する
\r
520 public virtual void RemoveDownloadedFile()
\r
522 if (Downloaded && File.Exists(installerFile)) {
\r
523 File.Delete(installerFile);
\r
527 public override string ToString()
\r
529 return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version);
\r
532 public static string ToString(Installation[] installations)
\r
534 string[] strs = new string[installations.Length];
\r
535 for (int i = 0; i < installations.Length; i++) {
\r
536 strs[i] = installations[i].ToString();
\r
538 return string.Join(" ", strs);
\r
544 /// パッケージ配列をインストール処理配列に変換する便利メソッド
\r
546 /// <param name="pkgs">パッケージ配列</param>
\r
547 /// <returns>変換されたインストール処理配列</returns>
\r
548 public static Installation[] ConvertInstallations(Package[] pkgs)
\r
550 Installation[] insts = new Installation[pkgs.Length];
\r
551 for (int i = 0; i < pkgs.Length; i++) {
\r
552 insts[i] = new Installation(pkgs[i]);
\r
558 /// パッケージがインストール可能かを戻します。
\r
560 /// <param name="pkg">検査対象のパッケージ</param>
\r
561 /// <returns>インストール可能か否か</returns>
\r
562 public static bool IsInstallablePackage(Package pkg)
\r
564 int installerIndex = getPreferInstallerIndex(pkg);
\r
565 return installerIndex >= 0;
\r