using System; using System.IO; using System.Diagnostics; using NaGet.Net; using NaGet.SubCommands; namespace NaGet.Packages.Install { /// /// ダウンロード・インストール処理をカプセル化するクラス /// public class Installation { /// /// インストールするパッケージ /// public Package InstalledPackage; /// /// (保存される)インストーラのファイルのパス /// public string InstallerFile; /// /// インストールが完了されたか否かのフラグ /// private bool installed = false; /// /// 起動するインストーラのインデックス番号 /// protected int installerIndex = 0; /// /// 外部アプリのエラー出力の受信ハンドラ /// public event EventHandler> ErrorDataReceived; /// /// 外部アプリの標準出力の受信ハンドラ /// public event EventHandler> OutputDataReceived; /// /// コンストラクタ /// /// インストールするパッケージ public Installation(Package package) { InstalledPackage = package; InstallerFile = getArchiveFilePath(); installerIndex = GetPreferInstallerIndex(package); } /// /// コンストラクタ /// /// インストールするパッケージ /// (保存される)インストーラのファイルのパス protected Installation(Package package, string installerfile) { InstalledPackage = package; InstallerFile = installerfile; installerIndex = GetPreferInstallerIndex(package); } /// /// インストール可能か否か /// public bool IsInstallablePackage() { return installerIndex >= 0; } /// /// すでにインストールされているパッケージを取得する /// /// /// インストールドリスト /// /// /// インストールされているパッケージの情報。インストールされたパッケージが見つからないならばnullを返す /// public InstalledPackage GetInstalledPackage(PackageList installedPkgs) { return installedPkgs.GetPackageForName(InstalledPackage.Name); } /// /// ダウンロードを行う。 /// /// ダウンローダオブジェクト 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; } } } /// /// ハッシュ検証のためのハッシュの種類の数を返す /// /// ハッシュの個数 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: hProcess = Process.Start(installerfile); hProcess.WaitForExit(); break; case InstallerType.MSI_PACKAGE: hProcess = Process.Start("msiexec", string.Format("/i \"{0}\"", installerfile)); hProcess.WaitForExit(); break; case InstallerType.ARCHIVE: // TODO ハックな実装? if (File.Exists("archive-inst.exe")) { string argument = string.Format("-i \"{0}\" \"{1}\"", installerfile, this.InstalledPackage.Name); ProcessStartInfo procInfo = new ProcessStartInfo(Path.GetFullPath("archive-inst.exe"), argument); procInfo.UseShellExecute = false; procInfo.CreateNoWindow = true; procInfo.WorkingDirectory = Environment.CurrentDirectory; hProcess = NaGet.Utils.ProcessStartWithOutputCapture(procInfo, this.OutputDataReceived, this.ErrorDataReceived); hProcess.WaitForExit(); break; } throw new NotImplementedException("Not implemented archive installation yet"); //break; default: throw new NotImplementedException("Not implemented archive installation yet"); } 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); // 64bit対策で展開も別プロセスで // TODO エラーとかの出力を取得できたりとかできるように if (File.Exists("archive-inst.exe")) { string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir); ProcessStartInfo info = new ProcessStartInfo(Path.GetFullPath("archive-inst.exe"), argument); info.UseShellExecute = false; info.WorkingDirectory = Environment.CurrentDirectory; using (Process hProcess = Process.Start(info)) { hProcess.WaitForExit(); if (hProcess.ExitCode != 0) { throw new ApplicationException("Extract error " + installerFile + " to " + tempDir); } } } else { throw new ApplicationException(string.Format("archive-inst.exe not found in {0}!", Environment.CurrentDirectory)); } // System.Text.StringBuilder output = new System.Text.StringBuilder(1024); // int res = NaGet.InteropServices.CommonArchiverExtracter.ExtractArchive(installerFile, tempDir, output, IntPtr.Zero); // if (res != 0) { // throw new ApplicationException("Extract error\n" + output.ToString()); // } 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; } /// /// ダウンロード済みであるか否か /// 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 (Type) { case InstallerType.ARCHIVE: // アーカイブインストーラはフォルダの確認 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 InstallerType Type { get { return InstalledPackage.Type; } } /// /// もっともふさわしいインストーラ番号を返す /// /// public 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; } /// /// インストーラの一時保存先パスを生成 /// private string getArchiveFilePath() { Package package = this.InstalledPackage; string folderName = string.Format("{0}({1})", package.Name, package.Version); string fileName = NaGet.Utils.Url2filename(package.Installer[0].Url.Href); string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, folderName); 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); } } return Path.Combine(folder, fileName); } /// /// インストーラファイルを探す /// /// 探すフォルダ /// インストーラの種類 /// 探し出されたインストーラファイルのフルパス。存在しないならば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; 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; } 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); } /// /// パッケージ配列をインストール処理配列に変換する便利メソッド /// /// パッケージ配列 /// 変換されたインストール処理配列 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; } } }