OSDN Git Service

na-get-lib,パフォーマンスチューニングなど(動作変更なし)
[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                                 installerFile = getArchiveFilePath();\r
27                                 installerIndex = GetPreferInstallerIndex(value);\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                 /// <exception cref="FileNotFoundException">スキャン後にインストーラファイルが存在しないとき</exception>\r
215                 /// <param name="scanner">スキャナーオブジェクト</param>\r
216                 public void ScanInstallerFile(DownloadScanner scanner)\r
217                 {\r
218                         Exception e = null;\r
219                         try {\r
220                                 scanner.Scan(installerFile, InstalledPackage.Installer[installerIndex].Url.Href);\r
221                         } catch (System.Runtime.InteropServices.COMException ex) {\r
222                                 e = ex;\r
223                         }\r
224                         \r
225                         if (! File.Exists(installerFile)) {\r
226                                 // ファイルが消されているならばFileNotFoundExceptionを出す\r
227                                 throw new FileNotFoundException(string.Empty, installerFile, e);\r
228                         } else if ( e != null ) {\r
229                                 // ファイルが消されていないが例外が発生していたときは、その例外を投げる\r
230                                 throw e;\r
231                         }\r
232                 }\r
233                 \r
234                 /// <summary>\r
235                 /// ハッシュ検証のためのハッシュの種類の数を返す\r
236                 /// </summary>\r
237                 /// <returns>ハッシュの個数</returns>\r
238                 public int GetRegisteredHashCount()\r
239                 {\r
240                         HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;\r
241                         return (hashValues == null)? 0 : hashValues.Length;\r
242                 }\r
243                 \r
244                 /// <summary>\r
245                 /// ハッシュ検証を行う\r
246                 /// </summary>\r
247                 /// <returns>検証にパスしたらtrue、パスしなかったらfalse</returns>\r
248                 public bool VerifyHashValues()\r
249                 {\r
250                         HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;\r
251                         if (hashValues != null) {\r
252                                 foreach (HashValue hash in hashValues) {\r
253                                         if (! hash.Validate(installerFile)) {\r
254                                                 return false;\r
255                                         }\r
256                                 }\r
257                         }\r
258                         \r
259                         return true;\r
260                 }\r
261                 \r
262                 private int invokeInstaller(string installerfile, InstallerType type)\r
263                 {\r
264                         if (! File.Exists(installerfile)) {\r
265                                 throw new FileNotFoundException(string.Format("{0} is not found, perhaps failed to download.", installerfile), installerfile);\r
266                         }\r
267                         \r
268                         Process hProcess = null;\r
269                         try {\r
270                                 switch (type) {\r
271                                 case InstallerType.EXEC_INSTALLER:\r
272                                         if (Silent) {\r
273                                                 hProcess = Process.Start(installerfile, InstalledPackage.SilentInstallArguments);\r
274                                         } else {\r
275                                                 hProcess = Process.Start(installerfile);\r
276                                         }\r
277                                         \r
278                                         break;\r
279                                 case InstallerType.MSI_PACKAGE:\r
280                                         string msiexecArgs = string.Format("{0} /norestart /i \"{1}\" REBOOT=ReallySuppress",\r
281                                                                     (Silent)? "/passive" : string.Empty,\r
282                                                                     installerfile);\r
283                                         \r
284                                         hProcess = Process.Start("msiexec", msiexecArgs);\r
285                                         break;\r
286                                 case InstallerType.ARCHIVE:\r
287                                 case InstallerType.ITSELF:\r
288                                         string argument = string.Format("-i \"{0}\" \"{1}\"", installerfile, this.InstalledPackage.Name);\r
289                                         hProcess = createExtractArchiveProcess(argument,\r
290                                                                                this.OutputDataReceived,\r
291                                                                                this.ErrorDataReceived);\r
292                                         // Note: ARCHIVEかITSELFの判断はarchive-instが行う\r
293                                         break;\r
294                                 default:\r
295                                         throw new NotImplementedException("Not implemented archive installation yet");\r
296                                 }\r
297                                 \r
298                                 if (NaGet.Env.InstallProcessOnBackground) {\r
299                                         try {\r
300                                                 hProcess.PriorityClass = ProcessPriorityClass.Idle;\r
301                                         } catch (Exception) {}\r
302                                 }\r
303                                 \r
304                                 hProcess.WaitForExit();\r
305                                 \r
306                                 return hProcess.ExitCode;\r
307                         } finally {\r
308                                 if (hProcess != null) {\r
309                                         hProcess.Close();\r
310                                 }\r
311                         }\r
312                 }\r
313                 \r
314                 /// <summary>\r
315                 /// インストーラ等を起動してインストール作業を行う\r
316                 /// </summary>\r
317                 /// <returns>インストーラの終了コード</returns>\r
318                 public int Install()\r
319                 {\r
320                         string installerFile = this.installerFile;\r
321                         string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());\r
322                         \r
323                         // アーカイブされているなら一旦展開\r
324                         if (InstalledPackage.ArchivedInstaller) {\r
325                                 Directory.CreateDirectory(tempDir);\r
326                                 \r
327                                 string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir);\r
328                                 using (Process hProcess = createExtractArchiveProcess(argument,\r
329                                                                                       this.OutputDataReceived,\r
330                                                                                       this.ErrorDataReceived)) {\r
331                                         if (NaGet.Env.InstallProcessOnBackground) {\r
332                                                 try {\r
333                                                         hProcess.PriorityClass = ProcessPriorityClass.Idle;\r
334                                                 } catch (Exception) {}\r
335                                         }\r
336                                         \r
337                                         hProcess.WaitForExit();\r
338                                 \r
339                                         if (hProcess.ExitCode != 0) {\r
340                                                 throw new ApplicationException("Extract error " + installerFile + " to " + tempDir);\r
341                                         }\r
342                                 }\r
343                                 \r
344                                 installerFile = seekInstallerFile(tempDir, InstalledPackage.Type);\r
345                                 if (installerFile == null && Directory.GetDirectories(tempDir).Length == 1) {\r
346                                         installerFile = seekInstallerFile(Path.Combine(tempDir, Directory.GetDirectories(tempDir)[0]), InstalledPackage.Type);\r
347                                 }\r
348                         }\r
349                         \r
350                         int exitCode = invokeInstaller(installerFile, InstalledPackage.Type);\r
351                         \r
352                         installed = true;\r
353                         \r
354                         return exitCode;\r
355                 }\r
356                 \r
357                 /// <summary>\r
358                 /// もっともふさわしいインストーラ番号を返す\r
359                 /// </summary>\r
360                 /// <returns></returns>\r
361                 public static int GetPreferInstallerIndex(Package InstalledPackage)\r
362                 {\r
363                         if (InstalledPackage.Type == InstallerType.CANNOT_INSTALL) { // インストール不能パッケージ\r
364                                 return -1;\r
365                         }\r
366                         \r
367                         int best = -1;\r
368                         int bestVal = 0;\r
369                         \r
370                         for (int i = 0; i < InstalledPackage.Installer.Length; i++) {\r
371                                 Platform platform = InstalledPackage.Installer[i].Platform;\r
372                                 \r
373                                 int pts = 0;\r
374                                 if (platform != null) {\r
375                                         pts = (platform.IsRunnable())? 10 : 0;\r
376                                         pts += (platform.IsRunnableArchOnWow64() && platform.IsRunnableOS())? 1 : 0;\r
377                                 } else { // if (platform == null) {\r
378                                         pts = 1; // null の場合は動作プラットホーム扱い\r
379                                 }\r
380                                 if (pts > bestVal) {\r
381                                         bestVal = pts;\r
382                                         best = i;\r
383                                 }\r
384                         }\r
385                         \r
386                         return best;\r
387                 }\r
388                 \r
389                 /// <summary>\r
390                 /// インストーラの保存先パスを生成\r
391                 /// </summary>\r
392                 private string getArchiveFilePath()\r
393                 {\r
394                         Package package = this.InstalledPackage;\r
395                         \r
396                         string folderName = string.Format("{0}({1})", package.Name, package.Version);\r
397                         string fileName = NaGet.Utils.Url2filename(new Uri(package.Installer[0].Url.Href));\r
398                         \r
399                         string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, folderName);\r
400                         \r
401                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
402                                 if (Directory.Exists(folder)) {\r
403                                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
404                                                 fileName = seekInstallerFile(folder, package.Type) ?? fileName;\r
405                                         }\r
406                                 } else {\r
407                                         Directory.CreateDirectory(folder);\r
408                                         \r
409                                         // 権限を親フォルダに合わせる\r
410                                         try {\r
411                                                 Directory.SetAccessControl(folder, Directory.GetAccessControl(Path.GetDirectoryName(folder)));\r
412                                         } catch (Exception) {} // 失敗時無視\r
413                                 }\r
414                         }\r
415                         \r
416                         return Path.Combine(folder, fileName);\r
417                 }\r
418                 \r
419                 /// <summary>\r
420                 /// すでにインストールされているパッケージを取得する\r
421                 /// </summary>\r
422                 /// <param name="installedPkgs">\r
423                 /// インストールドリスト\r
424                 /// </param>\r
425                 /// <returns>\r
426                 /// インストールされているパッケージの情報。インストールされたパッケージが見つからないならばnullを返す  \r
427                 /// </returns>\r
428                 public InstalledPackage GetInstalledPackage(PackageList<InstalledPackage> installedPkgs)\r
429                 {\r
430                         return installedPkgs.GetPackageForName(InstalledPackage.Name);\r
431                 }\r
432                 \r
433                 /// <summary>\r
434                 /// インストーラファイルを探す\r
435                 /// </summary>\r
436                 /// <param name="basedir">探すフォルダ</param>\r
437                 /// <param name="type">インストーラの種類</param>\r
438                 /// <returns>探し出されたインストーラファイルのフルパス。存在しないならばnull</returns>\r
439                 private static string seekInstallerFile(string basedir, InstallerType type)\r
440                 {\r
441                         if (! Directory.Exists(basedir)) {\r
442                                 return null;\r
443                         }\r
444                         \r
445                         System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();\r
446                         switch (type) {\r
447                                 case InstallerType.MSI_PACKAGE:\r
448                                         list.AddRange(Directory.GetFiles(basedir, "*.msi"));\r
449                                         break;\r
450                                 case InstallerType.EXEC_INSTALLER:\r
451                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
452                                         break;\r
453                                 case InstallerType.ARCHIVE:\r
454                                         list.AddRange(Directory.GetFiles(basedir, "*.zip"));\r
455                                         list.AddRange(Directory.GetFiles(basedir, "*.lzh"));\r
456                                         list.AddRange(Directory.GetFiles(basedir, "*.cab"));\r
457                                         list.AddRange(Directory.GetFiles(basedir, "*.7z"));\r
458                                         list.AddRange(Directory.GetFiles(basedir, "*.tar*"));\r
459                                         break;\r
460                                 case InstallerType.ITSELF:\r
461                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
462                                         break;\r
463                                 default:\r
464                                         return null;\r
465                         }\r
466                         \r
467                         // 存在しないファイルを削除\r
468                         list.RemoveAll(\r
469                                 delegate(string file) {\r
470                                         return ! File.Exists(file);\r
471                                 }\r
472                         );\r
473                         \r
474                         // "setup"の語が入ったファイルはインストーラとみなし、優先選択\r
475                         foreach (string path in list) {\r
476                                 if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) {\r
477                                         return path;\r
478                                 }\r
479                         }\r
480                         \r
481                         // それ以外なら一つ目を返す\r
482                         return (list.Count > 0)? list[0] : null;\r
483                 }\r
484                 \r
485                 \r
486                 /// <summary>\r
487                 /// アーカイブファイルの展開・インストールを行う\r
488                 /// </summary>\r
489                 /// <param name="archiveInstArgs">"archive-inst.exe"への引数</param>\r
490                 /// <param name="outputReceived">標準出力用リスナ(null可)</param>\r
491                 /// <param name="errorReceived">エラー出力用リスナ(null可)</param>\r
492                 /// <returns>実行プロセス</returns>\r
493                 private static Process createExtractArchiveProcess(string archiveInstArgs,\r
494                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> outputReceived,\r
495                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> errorReceived)\r
496                 {\r
497                         string archiveInstExe = Path.GetFullPath("archive-inst.exe");\r
498                         if (! File.Exists(archiveInstExe)) {\r
499                                 string errMsg = string.Format("\"{0}\" does not found!");\r
500                                 throw new ApplicationException(errMsg,\r
501                                                                new FileNotFoundException(errMsg, archiveInstExe));\r
502                         }\r
503                         \r
504                         \r
505                         \r
506                         ProcessStartInfo procInfo = new ProcessStartInfo(archiveInstExe, archiveInstArgs);\r
507                         procInfo.UseShellExecute = false;\r
508                         procInfo.CreateNoWindow = true;\r
509                         procInfo.WorkingDirectory = Environment.CurrentDirectory;\r
510                         \r
511                         return NaGet.Utils.ProcessStartWithOutputCapture(procInfo, outputReceived, errorReceived);\r
512                 }\r
513                 \r
514                 /// <summary>\r
515                 /// ダウンロードしたインストーラファイルを削除する\r
516                 /// </summary>\r
517                 public virtual void RemoveDownloadedFile()\r
518                 {\r
519                         if (Downloaded && File.Exists(installerFile)) {\r
520                                 File.Delete(installerFile);\r
521                         }\r
522                 }\r
523                 \r
524                 public override string ToString()\r
525                 {\r
526                         return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version);\r
527                 }\r
528                 \r
529                 public static string ToString(Installation[] installations)\r
530                 {\r
531                         string[] strs = new string[installations.Length];\r
532                         for (int i = 0; i < installations.Length; i++) {\r
533                                 strs[i] = installations[i].ToString();\r
534                         }\r
535                         return string.Join(" ", strs);\r
536                 }\r
537                 \r
538                 /// <summary>\r
539                 /// パッケージ配列をインストール処理配列に変換する便利メソッド\r
540                 /// </summary>\r
541                 /// <param name="pkgs">パッケージ配列</param>\r
542                 /// <returns>変換されたインストール処理配列</returns>\r
543                 public static Installation[] ConvertInstallations(Package[] pkgs)\r
544                 {\r
545                         Installation[] insts = new Installation[pkgs.Length];\r
546                         for (int i = 0; i < pkgs.Length; i++) {\r
547                                 insts[i] = new Installation(pkgs[i]);\r
548                         }\r
549                         return insts;\r
550                 }\r
551         }\r
552 }\r