OSDN Git Service

na-get-lib,キャッシュフォルダの構成を複数インストーラーファイル対応に変更(キャッシュフォルダを異なるアーキテクチャやOSで共有したときにハッシュ値不適合となっ...
[applistation/AppliStation.git] / na-get-lib / NaGet.Packages.Install / Installation.cs
1 using System;\r
2 using System.IO;\r
3 using System.Diagnostics;\r
4 using NaGet.Net;\r
5 using NaGet.SubCommands;\r
6 using System.Xml.Serialization;\r
7 \r
8 namespace NaGet.Packages.Install\r
9 {\r
10 \r
11     /// <summary>\r
12     /// ダウンロード・インストール処理をカプセル化するクラス\r
13     /// </summary>\r
14     public class Installation\r
15         {\r
16                 private Package installedPackage;\r
17                 \r
18                 /// <summary>\r
19                 /// インストールするパッケージ\r
20                 /// </summary>\r
21                 public Package InstalledPackage {\r
22                         get { return installedPackage; }\r
23                         set {\r
24                                 installedPackage = value;\r
25                                 \r
26                                 installerIndex = getPreferInstallerIndex(value);\r
27                                 installerFile = getArchiveFilePath();\r
28                         }\r
29                 }\r
30 \r
31                 /// <summary>\r
32                 /// (保存される)ーインストーラのファイルのパス\r
33                 /// </summary>\r
34                 private string installerFile;\r
35 \r
36                 /// <summary>\r
37                 /// インストールが完了されたか否かのフラグ\r
38                 /// </summary>\r
39                 private bool installed = false;\r
40                 \r
41                 /// <summary>\r
42                 /// サイレントインストールを行うか否か\r
43                 /// </summary>\r
44                 private bool silent = false;\r
45           \r
46                 /// <summary>\r
47                 /// 起動するインストーラーのインデックス番号\r
48                 /// </summary>\r
49                 protected int installerIndex = 0;\r
50                 \r
51                 /// <summary>\r
52                 /// 外部アプリのエラー出力の受信ハンドラ\r
53                 /// </summary>\r
54                 public event EventHandler<NaGet.Utils.AnyDataEventArgs<string>> ErrorDataReceived;\r
55                 \r
56                 /// <summary>\r
57                 /// 外部アプリの標準出力の受信ハンドラ\r
58                 /// </summary>\r
59                 public event EventHandler<NaGet.Utils.AnyDataEventArgs<string>> OutputDataReceived;\r
60                 \r
61                 /// <summary>\r
62                 /// コンストラクタ\r
63                 /// </summary>\r
64                 public Installation()\r
65                 {\r
66                 }\r
67                 \r
68                 /// <summary>\r
69                 /// コンストラクタ\r
70                 /// </summary>\r
71                 /// <param name="package">インストールするパッケージ</param>\r
72                 public Installation(Package package)\r
73                 {\r
74                         InstalledPackage = package;\r
75                 }\r
76                 \r
77                 /// <summary>\r
78                 /// インストーラーファイルを取得する\r
79                 /// </summary>\r
80                 public string InstallerFile {\r
81                         get { return installerFile; }\r
82                 }\r
83                 \r
84                 \r
85                 #region インストール状態チェック関連\r
86                 \r
87                 /// <summary>\r
88                 /// インストール可能か否か\r
89                 /// </summary>\r
90                 public bool IsInstallablePackage()\r
91                 {\r
92                         return installerIndex >= 0;\r
93                 }\r
94                 \r
95                 /// <summary>\r
96                 /// ダウンロード済みであるか否か\r
97                 /// </summary>\r
98                 public bool Downloaded {\r
99                         get {\r
100                                 return File.Exists(installerFile) && ((File.GetAttributes(installerFile) & FileAttributes.Hidden) != FileAttributes.Hidden);\r
101                         }\r
102                 }\r
103                 \r
104                 /// <summary>\r
105                 /// インストール作業済みであるか否か。実際にインストールされたかどうかではありません。\r
106                 /// </summary>\r
107                 public bool Installed {\r
108                         get { return installed; }\r
109                 }\r
110                 \r
111                 /// <summary>\r
112                 /// インストーラーの処理が成功してインストールされたプログラムが確認できるか否か。\r
113                 /// </summary>\r
114                 public bool InstallSuccessed {\r
115                         get {\r
116                                 switch (InstalledPackage.Type) {\r
117                                         case InstallerType.ARCHIVE:\r
118                                         case InstallerType.ITSELF:\r
119                                                 // アーカイブインストーラーおよび配布exeそれ自身が実行ファイルのとき、\r
120                                                 // (AppliStationの作る)プログラムフォルダーの存在のみで確認を行う。\r
121                                                 return Directory.Exists(Path.Combine(NaGet.Env.ArchiveProgramFiles, InstalledPackage.Name));\r
122                                         case InstallerType.EXEC_INSTALLER:\r
123                                         case InstallerType.MSI_PACKAGE:\r
124                                                 return RegistriedUninstallers.GetInstalledPackageFor(InstalledPackage) != null;\r
125                                         default:\r
126                                                 return false;\r
127                                 }\r
128                         }\r
129                 }\r
130                 \r
131                 /// <summary>\r
132                 /// サイレントインストールを行うかのフラグ。\r
133                 /// </summary>\r
134                 public bool Silent {\r
135                         get {\r
136                                 return (SupportsSilentOnly)? true :\r
137                                         (IsSupportsSilent)? silent :\r
138                                         false;\r
139                         }\r
140                         set { silent = value; }\r
141                 }\r
142                 \r
143                 /// <summary>\r
144                 /// サイレントインストールをサポートしているか否か\r
145                 /// </summary>\r
146                 public bool IsSupportsSilent {\r
147                         get {\r
148                                 switch (InstalledPackage.Type) {\r
149                                         case InstallerType.ARCHIVE:\r
150                                         case InstallerType.ITSELF:\r
151                                         case InstallerType.MSI_PACKAGE:\r
152                                                 return true;\r
153                                         case InstallerType.EXEC_INSTALLER:\r
154                                                 return ! string.IsNullOrEmpty(InstalledPackage.SilentInstallArguments);\r
155                                         default:\r
156                                                 return false;\r
157                                 }\r
158                         }\r
159                 }\r
160                 \r
161                 /// <summary>\r
162                 /// サイレントインストールだけをサポートしているか否か\r
163                 /// </summary>\r
164                 public bool SupportsSilentOnly {\r
165                         get {\r
166                                 return (InstalledPackage.Type == InstallerType.ARCHIVE)\r
167                                         || (InstalledPackage.Type == InstallerType.ITSELF);\r
168                         }\r
169                 }\r
170                 \r
171                 /// <summary>\r
172                 /// 選択されたパッケージは、AppliStationではなくPCへのインストールをするのか否かを返す。\r
173                 /// </summary>\r
174                 /// <remark>RunAsが必要か否かの判断にしようされる</remark>\r
175                 public bool TargetPC {\r
176                         get {\r
177                                 return (InstalledPackage.Type != InstallerType.ARCHIVE)\r
178                                         && (InstalledPackage.Type != InstallerType.ITSELF);\r
179                         }\r
180                 }\r
181                 \r
182                 #endregion\r
183                 \r
184                 /// <summary>\r
185                 /// ダウンロードを行う。\r
186                 /// </summary>\r
187                 /// <param name="downloader">ダウンローダオブジェクト</param>\r
188                 public void Download(Downloader downloader)\r
189                 {\r
190                         if (! Installed) {\r
191                                 string url = InstalledPackage.Installer[installerIndex].Url.Href;\r
192                                 downloader.Download(url, installerFile);\r
193                                 \r
194                                 // サーバ指定のファイル名に変更する\r
195                                 if (! string.IsNullOrEmpty(downloader.DownloadedFileName)) {\r
196                                         string newFile = Path.Combine(Path.GetDirectoryName(installerFile), downloader.DownloadedFileName);\r
197                                         File.Move(installerFile, newFile);\r
198                                         installerFile = newFile;\r
199                                 }\r
200                                 \r
201                                 // 権限を親フォルダーに落とす\r
202                                 try {\r
203                                         string targetDir = Path.GetDirectoryName(installerFile);\r
204                                         NaGet.Utils.SetAccessControlRecursive(targetDir, File.GetAccessControl(Path.GetDirectoryName(targetDir)));\r
205                                 } catch (Exception) {} // 失敗時は何もしない\r
206                         }\r
207                 }\r
208                 \r
209                 /// <summary>\r
210                 /// インストーラーファイルをスキャンする\r
211                 /// </summary>\r
212                 /// <remarks>ウイルスのため退避・削除されたときも例外を投げずにあたかも正常かのように動作しえます。</remarks>\r
213                 /// <exception cref="ComException">スキャンで意図せぬ動作があったとき</exception>\r
214                 /// <param name="scanner">スキャナーオブジェクト</param>\r
215                 /// <returns>スキャン結果</returns>\r
216                 public DownloadScannerResult ScanInstallerFile(DownloadScanner scanner)\r
217                 {\r
218                         DownloadScannerResult result;\r
219                         Exception e = null;\r
220                         try {\r
221                                 result = scanner.Scan(installerFile, InstalledPackage.Installer[installerIndex].Url.Href);\r
222                         } catch (Exception ex) {\r
223                                 result = DownloadScannerResult.ScannerNotFound;\r
224                                 e = ex;\r
225                         }\r
226                         \r
227                         if ( e != null ) {\r
228                                 // ファイルが消されていないが例外が発生していたときは、その例外を投げる\r
229                                 throw e;\r
230                         } else {\r
231                                 return result;\r
232                         }\r
233                 }\r
234                 \r
235                 /// <summary>\r
236                 /// ハッシュ検証のためのハッシュの種類の数を返す\r
237                 /// </summary>\r
238                 /// <returns>ハッシュの個数</returns>\r
239                 public int GetRegisteredHashCount()\r
240                 {\r
241                         HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;\r
242                         return (hashValues == null)? 0 : hashValues.Length;\r
243                 }\r
244                 \r
245                 /// <summary>\r
246                 /// ハッシュ検証を行う\r
247                 /// </summary>\r
248                 /// <returns>検証にパスしたらtrue、パスしなかったらfalse</returns>\r
249                 public bool VerifyHashValues()\r
250                 {\r
251                         HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;\r
252                         if (hashValues != null) {\r
253                                 foreach (HashValue hash in hashValues) {\r
254                                         if (! hash.Validate(installerFile)) {\r
255                                                 return false;\r
256                                         }\r
257                                 }\r
258                         }\r
259                         \r
260                         return true;\r
261                 }\r
262                 \r
263                 private int invokeInstaller(string installerfile, InstallerType type)\r
264                 {\r
265                         if (! File.Exists(installerfile)) {\r
266                                 throw new FileNotFoundException(string.Format("{0} is not found, perhaps failed to download.", installerfile), installerfile);\r
267                         }\r
268                         \r
269                         Process hProcess = null;\r
270                         try {\r
271                                 switch (type) {\r
272                                 case InstallerType.EXEC_INSTALLER:\r
273                                         if (Silent) {\r
274                                                 hProcess = Process.Start(installerfile, InstalledPackage.SilentInstallArguments);\r
275                                         } else {\r
276                                                 hProcess = Process.Start(installerfile);\r
277                                         }\r
278                                         \r
279                                         break;\r
280                                 case InstallerType.MSI_PACKAGE:\r
281                                         string msiexecArgs = string.Format("{0} /norestart /i \"{1}\" REBOOT=ReallySuppress",\r
282                                                                     (Silent)? "/passive" : string.Empty,\r
283                                                                     installerfile);\r
284                                         \r
285                                         hProcess = Process.Start("msiexec", msiexecArgs);\r
286                                         break;\r
287                                 case InstallerType.ARCHIVE:\r
288                                 case InstallerType.ITSELF:\r
289                                         string argument = string.Format("-i \"{0}\" \"{1}\"", installerfile, this.InstalledPackage.Name);\r
290                                         hProcess = createExtractArchiveProcess(argument,\r
291                                                                                this.OutputDataReceived,\r
292                                                                                this.ErrorDataReceived);\r
293                                         // Note: ARCHIVEかITSELFの判断はarchive-instが行う\r
294                                         break;\r
295                                 default:\r
296                                         throw new NotImplementedException("Not implemented archive installation yet");\r
297                                 }\r
298                                 \r
299                                 if (NaGet.Env.InstallProcessOnBackground) {\r
300                                         try {\r
301                                                 hProcess.PriorityClass = ProcessPriorityClass.Idle;\r
302                                         } catch (Exception) {}\r
303                                 }\r
304                                 \r
305                                 hProcess.WaitForExit();\r
306                                 \r
307                                 return hProcess.ExitCode;\r
308                         } finally {\r
309                                 if (hProcess != null) {\r
310                                         hProcess.Close();\r
311                                 }\r
312                         }\r
313                 }\r
314                 \r
315                 /// <summary>\r
316                 /// インストーラー等を起動してインストール作業を行う\r
317                 /// </summary>\r
318                 /// <returns>インストーラーの終了コード</returns>\r
319                 public int Install()\r
320                 {\r
321                         string installerFile = this.installerFile;\r
322                         string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());\r
323                         \r
324                         // アーカイブされているなら一旦展開\r
325                         if (InstalledPackage.ArchivedInstaller) {\r
326                                 Directory.CreateDirectory(tempDir);\r
327                                 \r
328                                 string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir);\r
329                                 using (Process hProcess = createExtractArchiveProcess(argument,\r
330                                                                                       this.OutputDataReceived,\r
331                                                                                       this.ErrorDataReceived)) {\r
332                                         if (NaGet.Env.InstallProcessOnBackground) {\r
333                                                 try {\r
334                                                         hProcess.PriorityClass = ProcessPriorityClass.Idle;\r
335                                                 } catch (Exception) {}\r
336                                         }\r
337                                         \r
338                                         hProcess.WaitForExit();\r
339                                 \r
340                                         if (hProcess.ExitCode != 0) {\r
341                                                 throw new ApplicationException("Extract error " + installerFile + " to " + tempDir);\r
342                                         }\r
343                                 }\r
344                                 \r
345                                 installerFile = seekInstallerFile(tempDir, InstalledPackage.Type);\r
346                                 if (installerFile == null && Directory.GetDirectories(tempDir).Length == 1) {\r
347                                         installerFile = seekInstallerFile(Path.Combine(tempDir, Directory.GetDirectories(tempDir)[0]), InstalledPackage.Type);\r
348                                 }\r
349                         }\r
350                         \r
351                         int exitCode = invokeInstaller(installerFile, InstalledPackage.Type);\r
352                         \r
353                         installed = true;\r
354                         \r
355                         return exitCode;\r
356                 }\r
357                 \r
358                 #region インストーラーファイル設定時の処理\r
359                 \r
360                 /// <summary>\r
361                 /// もっともふさわしいインストーラー番号を返す\r
362                 /// </summary>\r
363                 /// <returns></returns>\r
364                 private static int getPreferInstallerIndex(Package InstalledPackage)\r
365                 {\r
366                         if (InstalledPackage.Type == InstallerType.CANNOT_INSTALL) { // インストール不能パッケージ\r
367                                 return -1;\r
368                         }\r
369                         \r
370                         int best = -1;\r
371                         int bestVal = 0;\r
372                         \r
373                         for (int i = 0; i < InstalledPackage.Installer.Length; i++) {\r
374                                 Platform platform = InstalledPackage.Installer[i].Platform;\r
375                                 \r
376                                 int pts = 0;\r
377                                 if (platform != null) {\r
378                                         pts = (platform.IsRunnable())? 10 : 0;\r
379                                         pts += (platform.IsRunnableArchOnWow64() && platform.IsRunnableOS())? 1 : 0;\r
380                                 } else { // if (platform == null) {\r
381                                         pts = 1; // null の場合は動作プラットホーム扱い\r
382                                 }\r
383                                 if (pts > bestVal) {\r
384                                         bestVal = pts;\r
385                                         best = i;\r
386                                 }\r
387                         }\r
388                         \r
389                         return best;\r
390                 }\r
391                 \r
392                 /// <summary>\r
393                 /// インストーラーの保存先パスを生成\r
394                 /// </summary>\r
395                 /// <remarks>installerIndexが求まっているものとして仮定して処理する</remarks>\r
396                 private string getArchiveFilePath()\r
397                 {\r
398                         Package package = this.InstalledPackage;\r
399                         \r
400                         string folderName = string.Format("{0}({1})", package.Name, package.Version);\r
401                         string subFolderName = string.Format("installer.{0}", (installerIndex >= 0)? installerIndex.ToString("D") : "X");\r
402                         string fileName = NaGet.Utils.Url2filename(new Uri(package.Installer[0].Url.Href));\r
403                         \r
404                         string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, Path.Combine(folderName, subFolderName));\r
405                         \r
406                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
407                                 if (Directory.Exists(folder)) {\r
408                                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
409                                                 fileName = seekInstallerFile(folder, package.Type) ?? fileName;\r
410                                         }\r
411                                 } else {\r
412                                         Directory.CreateDirectory(folder);\r
413                                         \r
414                                         // 権限を親フォルダーに合わせる\r
415                                         try {\r
416                                                 Directory.SetAccessControl(folder, Directory.GetAccessControl(Path.GetDirectoryName(folder)));\r
417                                         } catch (Exception) {} // 失敗時無視\r
418                                 }\r
419                         }\r
420                         \r
421                         return Path.Combine(folder, fileName);\r
422                 }\r
423                 \r
424                 #endregion\r
425                 \r
426                 /// <summary>\r
427                 /// すでにインストールされているパッケージを取得する\r
428                 /// </summary>\r
429                 /// <param name="installedPkgs">\r
430                 /// インストールドリスト\r
431                 /// </param>\r
432                 /// <returns>\r
433                 /// インストールされているパッケージの情報。インストールされたパッケージが見つからないならばnullを返す  \r
434                 /// </returns>\r
435                 public InstalledPackage GetInstalledPackage(PackageList<InstalledPackage> installedPkgs)\r
436                 {\r
437                         return installedPkgs.GetPackageForName(InstalledPackage.Name);\r
438                 }\r
439                 \r
440                 /// <summary>\r
441                 /// インストーラーファイルを探す\r
442                 /// </summary>\r
443                 /// <param name="basedir">探すフォルダー</param>\r
444                 /// <param name="type">インストーラーの種類</param>\r
445                 /// <returns>探し出されたインストーラーファイルのフルパス。存在しないならばnull</returns>\r
446                 private static string seekInstallerFile(string basedir, InstallerType type)\r
447                 {\r
448                         if (! Directory.Exists(basedir)) {\r
449                                 return null;\r
450                         }\r
451                         \r
452                         System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();\r
453                         switch (type) {\r
454                                 case InstallerType.MSI_PACKAGE:\r
455                                         list.AddRange(Directory.GetFiles(basedir, "*.msi"));\r
456                                         break;\r
457                                 case InstallerType.EXEC_INSTALLER:\r
458                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
459                                         break;\r
460                                 case InstallerType.ARCHIVE:\r
461                                         list.AddRange(Directory.GetFiles(basedir, "*.zip"));\r
462                                         list.AddRange(Directory.GetFiles(basedir, "*.lzh"));\r
463                                         list.AddRange(Directory.GetFiles(basedir, "*.cab"));\r
464                                         list.AddRange(Directory.GetFiles(basedir, "*.7z"));\r
465                                         list.AddRange(Directory.GetFiles(basedir, "*.tar*"));\r
466                                         break;\r
467                                 case InstallerType.ITSELF:\r
468                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
469                                         break;\r
470                                 default:\r
471                                         return null;\r
472                         }\r
473                         \r
474                         // 存在しないファイルを削除\r
475                         list.RemoveAll(\r
476                                 delegate(string file) {\r
477                                         return ! File.Exists(file);\r
478                                 }\r
479                         );\r
480                         \r
481                         // "setup"の語が入ったファイルはインストーラとみなし、優先選択\r
482                         foreach (string path in list) {\r
483                                 if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) {\r
484                                         return path;\r
485                                 }\r
486                         }\r
487                         \r
488                         // それ以外なら一つ目を返す\r
489                         return (list.Count > 0)? list[0] : null;\r
490                 }\r
491                 \r
492                 \r
493                 /// <summary>\r
494                 /// アーカイブファイルの展開・インストールを行う\r
495                 /// </summary>\r
496                 /// <param name="archiveInstArgs">"archive-inst.exe"への引数</param>\r
497                 /// <param name="outputReceived">標準出力用リスナ(null可)</param>\r
498                 /// <param name="errorReceived">エラー出力用リスナ(null可)</param>\r
499                 /// <returns>実行プロセス</returns>\r
500                 private static Process createExtractArchiveProcess(string archiveInstArgs,\r
501                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> outputReceived,\r
502                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> errorReceived)\r
503                 {\r
504                         string archiveInstExe = Path.GetFullPath("archive-inst.exe");\r
505                         if (! File.Exists(archiveInstExe)) {\r
506                                 string errMsg = string.Format("\"{0}\" does not found!");\r
507                                 throw new ApplicationException(errMsg,\r
508                                                                new FileNotFoundException(errMsg, archiveInstExe));\r
509                         }\r
510                         \r
511                         \r
512                         \r
513                         ProcessStartInfo procInfo = new ProcessStartInfo(archiveInstExe, archiveInstArgs);\r
514                         procInfo.UseShellExecute = false;\r
515                         procInfo.CreateNoWindow = true;\r
516                         procInfo.WorkingDirectory = Environment.CurrentDirectory;\r
517                         \r
518                         return NaGet.Utils.ProcessStartWithOutputCapture(procInfo, outputReceived, errorReceived);\r
519                 }\r
520                 \r
521                 /// <summary>\r
522                 /// ダウンロードしたインストーラファイルを削除する\r
523                 /// </summary>\r
524                 public virtual void RemoveDownloadedFile()\r
525                 {\r
526                         if (Downloaded && File.Exists(installerFile)) {\r
527                                 File.Delete(installerFile);\r
528                         }\r
529                 }\r
530                 \r
531                 public override string ToString()\r
532                 {\r
533                         return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version);\r
534                 }\r
535                 \r
536                 public static string ToString(Installation[] installations)\r
537                 {\r
538                         string[] strs = new string[installations.Length];\r
539                         for (int i = 0; i < installations.Length; i++) {\r
540                                 strs[i] = installations[i].ToString();\r
541                         }\r
542                         return string.Join(" ", strs);\r
543                 }\r
544                 \r
545                 #region 便利メソッド群\r
546                 \r
547                 /// <summary>\r
548                 /// パッケージ配列をインストール処理配列に変換する便利メソッド\r
549                 /// </summary>\r
550                 /// <param name="pkgs">パッケージ配列</param>\r
551                 /// <returns>変換されたインストール処理配列</returns>\r
552                 public static Installation[] ConvertInstallations(Package[] pkgs)\r
553                 {\r
554                         Installation[] insts = new Installation[pkgs.Length];\r
555                         for (int i = 0; i < pkgs.Length; i++) {\r
556                                 insts[i] = new Installation(pkgs[i]);\r
557                         }\r
558                         return insts;\r
559                 }\r
560                 \r
561                 /// <summary>\r
562                 /// パッケージがインストール可能かを戻します。\r
563                 /// </summary>\r
564                 /// <param name="pkg">検査対象のパッケージ</param>\r
565                 /// <returns>インストール可能か否か</returns>\r
566                 public static bool IsInstallablePackage(Package pkg)\r
567                 {\r
568                         int installerIndex = getPreferInstallerIndex(pkg);\r
569                         return installerIndex >= 0;\r
570                 }\r
571                 \r
572                 #endregion\r
573         }\r
574 }\r