OSDN Git Service

アセンブリバージョンを 1.3.7 に変更
[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="basedir">探すフォルダー</param>\r
430                 /// <param name="type">インストーラーの種類</param>\r
431                 /// <returns>探し出されたインストーラーファイルのフルパス。存在しないならばnull</returns>\r
432                 private static string seekInstallerFile(string basedir, InstallerType type)\r
433                 {\r
434                         if (! Directory.Exists(basedir)) {\r
435                                 return null;\r
436                         }\r
437                         \r
438                         System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();\r
439                         switch (type) {\r
440                                 case InstallerType.MSI_PACKAGE:\r
441                                         list.AddRange(Directory.GetFiles(basedir, "*.msi"));\r
442                                         break;\r
443                                 case InstallerType.EXEC_INSTALLER:\r
444                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
445                                         break;\r
446                                 case InstallerType.ARCHIVE:\r
447                                         list.AddRange(Directory.GetFiles(basedir, "*.zip"));\r
448                                         list.AddRange(Directory.GetFiles(basedir, "*.lzh"));\r
449                                         list.AddRange(Directory.GetFiles(basedir, "*.cab"));\r
450                                         list.AddRange(Directory.GetFiles(basedir, "*.7z"));\r
451                                         list.AddRange(Directory.GetFiles(basedir, "*.tar*"));\r
452                                         break;\r
453                                 case InstallerType.ITSELF:\r
454                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
455                                         break;\r
456                                 default:\r
457                                         return null;\r
458                         }\r
459                         \r
460                         // 存在しないファイルを削除\r
461                         list.RemoveAll(\r
462                                 delegate(string file) {\r
463                                         return ! File.Exists(file);\r
464                                 }\r
465                         );\r
466                         \r
467                         // "setup"の語が入ったファイルはインストーラとみなし、優先選択\r
468                         foreach (string path in list) {\r
469                                 if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) {\r
470                                         return path;\r
471                                 }\r
472                         }\r
473                         \r
474                         // それ以外なら一つ目を返す\r
475                         return (list.Count > 0)? list[0] : null;\r
476                 }\r
477                 \r
478                 \r
479                 /// <summary>\r
480                 /// アーカイブファイルの展開・インストールを行う\r
481                 /// </summary>\r
482                 /// <param name="archiveInstArgs">"archive-inst.exe"への引数</param>\r
483                 /// <param name="outputReceived">標準出力用リスナ(null可)</param>\r
484                 /// <param name="errorReceived">エラー出力用リスナ(null可)</param>\r
485                 /// <returns>実行プロセス</returns>\r
486                 private static Process createExtractArchiveProcess(string archiveInstArgs,\r
487                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> outputReceived,\r
488                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> errorReceived)\r
489                 {\r
490                         string archiveInstExe = Path.GetFullPath("archive-inst.exe");\r
491                         if (! File.Exists(archiveInstExe)) {\r
492                                 string errMsg = string.Format("\"{0}\" does not found!");\r
493                                 throw new ApplicationException(errMsg,\r
494                                                                new FileNotFoundException(errMsg, archiveInstExe));\r
495                         }\r
496                         \r
497                         \r
498                         \r
499                         ProcessStartInfo procInfo = new ProcessStartInfo(archiveInstExe, archiveInstArgs);\r
500                         procInfo.UseShellExecute = false;\r
501                         procInfo.CreateNoWindow = true;\r
502                         procInfo.WorkingDirectory = Environment.CurrentDirectory;\r
503                         \r
504                         return NaGet.Utils.ProcessStartWithOutputCapture(procInfo, outputReceived, errorReceived);\r
505                 }\r
506                 \r
507                 /// <summary>\r
508                 /// ダウンロードしたインストーラファイルを削除する\r
509                 /// </summary>\r
510                 public virtual void RemoveDownloadedFile()\r
511                 {\r
512                         if (Downloaded && File.Exists(installerFile)) {\r
513                                 File.Delete(installerFile);\r
514                         }\r
515                 }\r
516                 \r
517                 public override string ToString()\r
518                 {\r
519                         return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version);\r
520                 }\r
521                 \r
522                 public static string ToString(Installation[] installations)\r
523                 {\r
524                         string[] strs = new string[installations.Length];\r
525                         for (int i = 0; i < installations.Length; i++) {\r
526                                 strs[i] = installations[i].ToString();\r
527                         }\r
528                         return string.Join(" ", strs);\r
529                 }\r
530                 \r
531                 #region 便利メソッド群\r
532                 \r
533                 /// <summary>\r
534                 /// パッケージ配列をインストール処理配列に変換する便利メソッド\r
535                 /// </summary>\r
536                 /// <param name="pkgs">パッケージ配列</param>\r
537                 /// <returns>変換されたインストール処理配列</returns>\r
538                 public static Installation[] ConvertInstallations(Package[] pkgs)\r
539                 {\r
540                         Installation[] insts = new Installation[pkgs.Length];\r
541                         for (int i = 0; i < pkgs.Length; i++) {\r
542                                 insts[i] = new Installation(pkgs[i]);\r
543                         }\r
544                         return insts;\r
545                 }\r
546                 \r
547                 /// <summary>\r
548                 /// パッケージがインストール可能かを戻します。\r
549                 /// </summary>\r
550                 /// <param name="pkg">検査対象のパッケージ</param>\r
551                 /// <returns>インストール可能か否か</returns>\r
552                 public static bool IsInstallablePackage(Package pkg)\r
553                 {\r
554                         int installerIndex = getPreferInstallerIndex(pkg);\r
555                         return installerIndex >= 0;\r
556                 }\r
557                 \r
558                 #endregion\r
559         }\r
560 }\r