using System; using System.IO; using System.Diagnostics; using NaGet.Net; using NaGet.SubCommands; using System.Xml.Serialization; namespace NaGet.Packages.Install { /// /// ダウンロード・インストール処理をカプセル化するクラス /// public class Installation { private Package installedPackage; /// /// インストールするパッケージ /// public Package InstalledPackage { get { return installedPackage; } set { installedPackage = value; installerIndex = getPreferInstallerIndex(value); installerFile = getArchiveFilePath(); } } /// /// (保存される)ーインストーラのファイルのパス /// private string installerFile; /// /// インストールが完了されたか否かのフラグ /// private bool installed = false; /// /// サイレントインストールを行うか否か /// private bool silent = false; /// /// 起動するインストーラーのインデックス番号 /// protected int installerIndex = 0; /// /// 外部アプリのエラー出力の受信ハンドラ /// public event EventHandler> ErrorDataReceived; /// /// 外部アプリの標準出力の受信ハンドラ /// public event EventHandler> OutputDataReceived; /// /// コンストラクタ /// public Installation() { } /// /// コンストラクタ /// /// インストールするパッケージ public Installation(Package package) { InstalledPackage = package; } /// /// インストーラーファイルを取得する /// public string InstallerFile { get { return installerFile; } } #region インストール状態チェック関連 /// /// インストール可能か否か /// public bool IsInstallablePackage() { return installerIndex >= 0; } /// /// ダウンロード済みであるか否か /// public bool Downloaded { get { return File.Exists(installerFile) && ((File.GetAttributes(installerFile) & FileAttributes.Hidden) != FileAttributes.Hidden); } } /// /// インストール作業済みであるか否か。実際にインストールされたかどうかではありません。 /// public bool Installed { get { return installed; } } /// /// インストーラーの処理が成功してインストールされたプログラムが確認できるか否か。 /// public bool InstallSuccessed { get { switch (InstalledPackage.Type) { case InstallerType.ARCHIVE: case InstallerType.ITSELF: // アーカイブインストーラーおよび配布exeそれ自身が実行ファイルのとき、 // (AppliStationの作る)プログラムフォルダーの存在のみで確認を行う。 return Directory.Exists(Path.Combine(NaGet.Env.ArchiveProgramFiles, InstalledPackage.Name)); case InstallerType.EXEC_INSTALLER: case InstallerType.MSI_PACKAGE: return RegistriedUninstallers.GetInstalledPackageFor(InstalledPackage) != null; default: return false; } } } /// /// サイレントインストールを行うかのフラグ。 /// public bool Silent { get { return (SupportsSilentOnly)? true : (IsSupportsSilent)? silent : false; } set { silent = value; } } /// /// サイレントインストールをサポートしているか否か /// public bool IsSupportsSilent { get { switch (InstalledPackage.Type) { case InstallerType.ARCHIVE: case InstallerType.ITSELF: case InstallerType.MSI_PACKAGE: return true; case InstallerType.EXEC_INSTALLER: return ! string.IsNullOrEmpty(InstalledPackage.SilentInstallArguments); default: return false; } } } /// /// サイレントインストールだけをサポートしているか否か /// public bool SupportsSilentOnly { get { return (InstalledPackage.Type == InstallerType.ARCHIVE) || (InstalledPackage.Type == InstallerType.ITSELF); } } /// /// 選択されたパッケージは、AppliStationではなくPCへのインストールをするのか否かを返す。 /// /// RunAsが必要か否かの判断にしようされる public bool TargetPC { get { return (InstalledPackage.Type != InstallerType.ARCHIVE) && (InstalledPackage.Type != InstallerType.ITSELF); } } #endregion /// /// ダウンロードを行う。 /// /// ダウンローダオブジェクト public void Download(Downloader downloader) { if (! Installed) { string url = InstalledPackage.Installer[installerIndex].Url.Href; downloader.Download(url, installerFile); // サーバ指定のファイル名に変更する if (! string.IsNullOrEmpty(downloader.DownloadedFileName)) { string newFile = Path.Combine(Path.GetDirectoryName(installerFile), downloader.DownloadedFileName); File.Move(installerFile, newFile); installerFile = newFile; } // 権限を親フォルダーに落とす try { string targetDir = Path.GetDirectoryName(installerFile); NaGet.Utils.SetAccessControlRecursive(targetDir, File.GetAccessControl(Path.GetDirectoryName(targetDir))); } catch (Exception) {} // 失敗時は何もしない } } /// /// インストーラーファイルをスキャンする /// /// ウイルスのため退避・削除されたときも例外を投げずにあたかも正常かのように動作しえます。 /// スキャンで意図せぬ動作があったとき /// スキャナーオブジェクト /// スキャン結果 public DownloadScannerResult ScanInstallerFile(DownloadScanner scanner) { DownloadScannerResult result; Exception e = null; try { result = scanner.Scan(installerFile, InstalledPackage.Installer[installerIndex].Url.Href); } catch (Exception ex) { result = DownloadScannerResult.ScannerNotFound; e = ex; } if ( e != null ) { // ファイルが消されていないが例外が発生していたときは、その例外を投げる throw e; } else { return result; } } /// /// ハッシュ検証のためのハッシュの種類の数を返す /// /// ハッシュの個数 public int GetRegisteredHashCount() { HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash; return (hashValues == null)? 0 : hashValues.Length; } /// /// ハッシュ検証を行う /// /// 検証にパスしたらtrue、パスしなかったらfalse public bool VerifyHashValues() { HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash; if (hashValues != null) { foreach (HashValue hash in hashValues) { if (! hash.Validate(installerFile)) { return false; } } } return true; } private int invokeInstaller(string installerfile, InstallerType type) { if (! File.Exists(installerfile)) { throw new FileNotFoundException(string.Format("{0} is not found, perhaps failed to download.", installerfile), installerfile); } Process hProcess = null; try { switch (type) { case InstallerType.EXEC_INSTALLER: if (Silent) { hProcess = Process.Start(installerfile, InstalledPackage.SilentInstallArguments); } else { hProcess = Process.Start(installerfile); } break; case InstallerType.MSI_PACKAGE: string msiexecArgs = string.Format("{0} /norestart /i \"{1}\" REBOOT=ReallySuppress", (Silent)? "/passive" : string.Empty, installerfile); hProcess = Process.Start("msiexec", msiexecArgs); break; case InstallerType.ARCHIVE: case InstallerType.ITSELF: string argument = string.Format("-i \"{0}\" \"{1}\"", installerfile, this.InstalledPackage.Name); hProcess = createExtractArchiveProcess(argument, this.OutputDataReceived, this.ErrorDataReceived); // Note: ARCHIVEかITSELFの判断はarchive-instが行う break; default: throw new NotImplementedException("Not implemented archive installation yet"); } if (NaGet.Env.InstallProcessOnBackground) { try { hProcess.PriorityClass = ProcessPriorityClass.Idle; } catch (Exception) {} } hProcess.WaitForExit(); return hProcess.ExitCode; } finally { if (hProcess != null) { hProcess.Close(); } } } /// /// インストーラー等を起動してインストール作業を行う /// /// インストーラーの終了コード public int Install() { string installerFile = this.installerFile; string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); // アーカイブされているなら一旦展開 if (InstalledPackage.ArchivedInstaller) { Directory.CreateDirectory(tempDir); string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir); using (Process hProcess = createExtractArchiveProcess(argument, this.OutputDataReceived, this.ErrorDataReceived)) { if (NaGet.Env.InstallProcessOnBackground) { try { hProcess.PriorityClass = ProcessPriorityClass.Idle; } catch (Exception) {} } hProcess.WaitForExit(); if (hProcess.ExitCode != 0) { throw new ApplicationException("Extract error " + installerFile + " to " + tempDir); } } installerFile = seekInstallerFile(tempDir, InstalledPackage.Type); if (installerFile == null && Directory.GetDirectories(tempDir).Length == 1) { installerFile = seekInstallerFile(Path.Combine(tempDir, Directory.GetDirectories(tempDir)[0]), InstalledPackage.Type); } } int exitCode = invokeInstaller(installerFile, InstalledPackage.Type); installed = true; return exitCode; } #region インストーラーファイル設定時の処理 /// /// もっともふさわしいインストーラー番号を返す /// /// private static int getPreferInstallerIndex(Package InstalledPackage) { if (InstalledPackage.Type == InstallerType.CANNOT_INSTALL) { // インストール不能パッケージ return -1; } int best = -1; int bestVal = 0; for (int i = 0; i < InstalledPackage.Installer.Length; i++) { Platform platform = InstalledPackage.Installer[i].Platform; int pts = 0; if (platform != null) { pts = (platform.IsRunnable())? 10 : 0; pts += (platform.IsRunnableArchOnWow64() && platform.IsRunnableOS())? 1 : 0; } else { // if (platform == null) { pts = 1; // null の場合は動作プラットホーム扱い } if (pts > bestVal) { bestVal = pts; best = i; } } return best; } /// /// インストーラーの保存先パスを生成 /// /// installerIndexが求まっているものとして仮定して処理する private string getArchiveFilePath() { Package package = this.InstalledPackage; string folderName = string.Format("{0}({1})", package.Name, package.Version); string subFolderName = string.Format("installer.{0}", (installerIndex >= 0)? installerIndex.ToString("D") : "X"); string fileName = NaGet.Utils.Url2filename(new Uri(package.Installer[0].Url.Href)); string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, Path.Combine(folderName, subFolderName)); if (! File.Exists(Path.Combine(folder, fileName))) { if (Directory.Exists(folder)) { if (! File.Exists(Path.Combine(folder, fileName))) { fileName = seekInstallerFile(folder, package.Type) ?? fileName; } } else { Directory.CreateDirectory(folder); // 権限を親フォルダーに合わせる try { Directory.SetAccessControl(folder, Directory.GetAccessControl(Path.GetDirectoryName(folder))); } catch (Exception) {} // 失敗時無視 } } return Path.Combine(folder, fileName); } #endregion /// /// インストーラーファイルを探す /// /// 探すフォルダー /// インストーラーの種類 /// 探し出されたインストーラーファイルのフルパス。存在しないならばnull private static string seekInstallerFile(string basedir, InstallerType type) { if (! Directory.Exists(basedir)) { return null; } System.Collections.Generic.List list = new System.Collections.Generic.List(); switch (type) { case InstallerType.MSI_PACKAGE: list.AddRange(Directory.GetFiles(basedir, "*.msi")); break; case InstallerType.EXEC_INSTALLER: list.AddRange(Directory.GetFiles(basedir, "*.exe")); break; case InstallerType.ARCHIVE: list.AddRange(Directory.GetFiles(basedir, "*.zip")); list.AddRange(Directory.GetFiles(basedir, "*.lzh")); list.AddRange(Directory.GetFiles(basedir, "*.cab")); list.AddRange(Directory.GetFiles(basedir, "*.7z")); list.AddRange(Directory.GetFiles(basedir, "*.tar*")); break; case InstallerType.ITSELF: list.AddRange(Directory.GetFiles(basedir, "*.exe")); break; default: return null; } // 存在しないファイルを削除 list.RemoveAll( delegate(string file) { return ! File.Exists(file); } ); // "setup"の語が入ったファイルはインストーラとみなし、優先選択 foreach (string path in list) { if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) { return path; } } // それ以外なら一つ目を返す return (list.Count > 0)? list[0] : null; } /// /// アーカイブファイルの展開・インストールを行う /// /// "archive-inst.exe"への引数 /// 標準出力用リスナ(null可) /// エラー出力用リスナ(null可) /// 実行プロセス private static Process createExtractArchiveProcess(string archiveInstArgs, EventHandler> outputReceived, EventHandler> errorReceived) { string archiveInstExe = Path.GetFullPath("archive-inst.exe"); if (! File.Exists(archiveInstExe)) { string errMsg = string.Format("\"{0}\" does not found!"); throw new ApplicationException(errMsg, new FileNotFoundException(errMsg, archiveInstExe)); } ProcessStartInfo procInfo = new ProcessStartInfo(archiveInstExe, archiveInstArgs); procInfo.UseShellExecute = false; procInfo.CreateNoWindow = true; procInfo.WorkingDirectory = Environment.CurrentDirectory; return NaGet.Utils.ProcessStartWithOutputCapture(procInfo, outputReceived, errorReceived); } /// /// ダウンロードしたインストーラファイルを削除する /// public virtual void RemoveDownloadedFile() { if (Downloaded && File.Exists(installerFile)) { File.Delete(installerFile); } } public override string ToString() { return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version); } public static string ToString(Installation[] installations) { string[] strs = new string[installations.Length]; for (int i = 0; i < installations.Length; i++) { strs[i] = installations[i].ToString(); } return string.Join(" ", strs); } #region 便利メソッド群 /// /// パッケージ配列をインストール処理配列に変換する便利メソッド /// /// パッケージ配列 /// 変換されたインストール処理配列 public static Installation[] ConvertInstallations(Package[] pkgs) { Installation[] insts = new Installation[pkgs.Length]; for (int i = 0; i < pkgs.Length; i++) { insts[i] = new Installation(pkgs[i]); } return insts; } /// /// パッケージがインストール可能かを戻します。 /// /// 検査対象のパッケージ /// インストール可能か否か public static bool IsInstallablePackage(Package pkg) { int installerIndex = getPreferInstallerIndex(pkg); return installerIndex >= 0; } #endregion } }