OSDN Git Service

na-get-lib,チケット #36130 に関連して、更新パッケージから非対応パッケージを除外するようにした
[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.SubCommands;\r
5 using NaGet.InteropServices;\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                         set { installerFile = value; }\r
83                 }\r
84                 \r
85                 /// <summary>\r
86                 /// インストーラーURLを取得する\r
87                 /// </summary>\r
88                 public string InstallerURL {\r
89                         get { return InstalledPackage.Installer[installerIndex].Url.Href; }\r
90                 }\r
91                 \r
92                 \r
93                 #region インストール状態チェック関連\r
94                 \r
95                 /// <summary>\r
96                 /// インストール可能か否か\r
97                 /// </summary>\r
98                 public bool IsInstallablePackage()\r
99                 {\r
100                         return installerIndex >= 0;\r
101                 }\r
102                 \r
103                 /// <summary>\r
104                 /// ダウンロード済みであるか否か\r
105                 /// </summary>\r
106                 public bool Downloaded {\r
107                         get {\r
108                                 return File.Exists(installerFile) && ((File.GetAttributes(installerFile) & FileAttributes.Hidden) != FileAttributes.Hidden);\r
109                         }\r
110                 }\r
111                 \r
112                 /// <summary>\r
113                 /// インストール作業済みであるか否か。実際にインストールされたかどうかではありません。\r
114                 /// </summary>\r
115                 public bool Installed {\r
116                         get { return installed; }\r
117                 }\r
118                 \r
119                 /// <summary>\r
120                 /// インストーラーの処理が成功してインストールされたプログラムが確認できるか否か。\r
121                 /// </summary>\r
122                 public bool InstallSuccessed {\r
123                         get {\r
124                                 switch (InstalledPackage.Type) {\r
125                                         case InstallerType.ARCHIVE:\r
126                                         case InstallerType.ITSELF:\r
127                                                 // アーカイブインストーラーおよび配布exeそれ自身が実行ファイルのとき、\r
128                                                 // (AppliStationの作る)プログラムフォルダーの存在のみで確認を行う。\r
129                                                 return Directory.Exists(Path.Combine(NaGet.Env.ArchiveProgramFiles, InstalledPackage.Name));\r
130                                         case InstallerType.EXEC_INSTALLER:\r
131                                         case InstallerType.MSI_PACKAGE:\r
132                                                 return RegistriedUninstallers.GetInstalledPackageFor(InstalledPackage) != null;\r
133                                         default:\r
134                                                 return false;\r
135                                 }\r
136                         }\r
137                 }\r
138                 \r
139                 /// <summary>\r
140                 /// サイレントインストールを行うかのフラグ。\r
141                 /// </summary>\r
142                 public bool Silent {\r
143                         get {\r
144                                 return (SupportsSilentOnly)? true :\r
145                                         (IsSupportsSilent)? silent :\r
146                                         false;\r
147                         }\r
148                         set { silent = value; }\r
149                 }\r
150                 \r
151                 /// <summary>\r
152                 /// サイレントインストールをサポートしているか否か\r
153                 /// </summary>\r
154                 public bool IsSupportsSilent {\r
155                         get {\r
156                                 switch (InstalledPackage.Type) {\r
157                                         case InstallerType.ARCHIVE:\r
158                                         case InstallerType.ITSELF:\r
159                                         case InstallerType.MSI_PACKAGE:\r
160                                                 return true;\r
161                                         case InstallerType.EXEC_INSTALLER:\r
162                                                 return ! string.IsNullOrEmpty(InstalledPackage.SilentInstallArguments);\r
163                                         default:\r
164                                                 return false;\r
165                                 }\r
166                         }\r
167                 }\r
168                 \r
169                 /// <summary>\r
170                 /// サイレントインストールだけをサポートしているか否か\r
171                 /// </summary>\r
172                 public bool SupportsSilentOnly {\r
173                         get {\r
174                                 return (InstalledPackage.Type == InstallerType.ARCHIVE)\r
175                                         || (InstalledPackage.Type == InstallerType.ITSELF);\r
176                         }\r
177                 }\r
178                 \r
179                 /// <summary>\r
180                 /// 選択されたパッケージは、AppliStationではなくPCへのインストールをするのか否かを返す。\r
181                 /// </summary>\r
182                 /// <remark>RunAsが必要か否かの判断にしようされる</remark>\r
183                 public bool TargetPC {\r
184                         get {\r
185                                 return (InstalledPackage.Type != InstallerType.ARCHIVE)\r
186                                         && (InstalledPackage.Type != InstallerType.ITSELF);\r
187                         }\r
188                 }\r
189                 \r
190                 #endregion\r
191                 \r
192                 /// <summary>\r
193                 /// ハッシュ検証のためのハッシュの種類の数を返す\r
194                 /// </summary>\r
195                 /// <returns>ハッシュの個数</returns>\r
196                 public int GetRegisteredHashCount()\r
197                 {\r
198                         HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;\r
199                         return (hashValues == null)? 0 : hashValues.Length;\r
200                 }\r
201                 \r
202                 /// <summary>\r
203                 /// ハッシュ検証を行う\r
204                 /// </summary>\r
205                 /// <returns>検証にパスしたらtrue、パスしなかったらfalse</returns>\r
206                 public bool VerifyHashValues()\r
207                 {\r
208                         HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;\r
209                         if (hashValues != null) {\r
210                                 foreach (HashValue hash in hashValues) {\r
211                                         if (! hash.Validate(installerFile)) {\r
212                                                 return false;\r
213                                         }\r
214                                 }\r
215                         }\r
216                         \r
217                         return true;\r
218                 }\r
219                 \r
220                 private int invokeInstaller(string installerfile, InstallerType type)\r
221                 {\r
222                         if (! File.Exists(installerfile)) {\r
223                                 throw new FileNotFoundException(string.Format("{0} is not found, perhaps failed to download.", installerfile), installerfile);\r
224                         }\r
225                         \r
226                         Process hProcess = null;\r
227                         try {\r
228                                 switch (type) {\r
229                                 case InstallerType.EXEC_INSTALLER:\r
230                                         if (Silent) {\r
231                                                 hProcess = Process.Start(installerfile, InstalledPackage.SilentInstallArguments);\r
232                                         } else {\r
233                                                 hProcess = Process.Start(installerfile);\r
234                                         }\r
235                                         \r
236                                         break;\r
237                                 case InstallerType.MSI_PACKAGE:\r
238                                         string msiexecArgs = string.Format("{0} /norestart /i \"{1}\" REBOOT=ReallySuppress",\r
239                                                                     (Silent)? "/passive" : string.Empty,\r
240                                                                     installerfile);\r
241                                         \r
242                                         hProcess = Process.Start("msiexec", msiexecArgs);\r
243                                         break;\r
244                                 case InstallerType.ARCHIVE:\r
245                                 case InstallerType.ITSELF:\r
246                                         string argument = string.Format("-i \"{0}\" \"{1}\"", installerfile, this.InstalledPackage.Name);\r
247                                         hProcess = createExtractArchiveProcess(argument,\r
248                                                                                this.OutputDataReceived,\r
249                                                                                this.ErrorDataReceived);\r
250                                         // Note: ARCHIVEかITSELFの判断はarchive-instが行う\r
251                                         break;\r
252                                 default:\r
253                                         throw new NotImplementedException("Not implemented archive installation yet");\r
254                                 }\r
255                                 \r
256                                 if (NaGet.Env.InstallProcessOnBackground) {\r
257                                         try {\r
258                                                 hProcess.PriorityClass = ProcessPriorityClass.Idle;\r
259                                         } catch (Exception) {}\r
260                                 }\r
261                                 \r
262                                 hProcess.WaitForExit();\r
263                                 \r
264                                 return hProcess.ExitCode;\r
265                         } finally {\r
266                                 if (hProcess != null) {\r
267                                         hProcess.Close();\r
268                                 }\r
269                         }\r
270                 }\r
271                 \r
272                 /// <summary>\r
273                 /// インストーラー等を起動してインストール作業を行う\r
274                 /// </summary>\r
275                 /// <returns>インストーラーの終了コード</returns>\r
276                 public int Install()\r
277                 {\r
278                         string installerFile = this.installerFile;\r
279                         string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());\r
280                         \r
281                         // アーカイブされているなら一旦展開\r
282                         if (InstalledPackage.ArchivedInstaller) {\r
283                                 Directory.CreateDirectory(tempDir);\r
284                                 \r
285                                 string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir);\r
286                                 using (Process hProcess = createExtractArchiveProcess(argument,\r
287                                                                                       this.OutputDataReceived,\r
288                                                                                       this.ErrorDataReceived)) {\r
289                                         if (NaGet.Env.InstallProcessOnBackground) {\r
290                                                 try {\r
291                                                         hProcess.PriorityClass = ProcessPriorityClass.Idle;\r
292                                                 } catch (Exception) {}\r
293                                         }\r
294                                         \r
295                                         hProcess.WaitForExit();\r
296                                 \r
297                                         if (hProcess.ExitCode != 0) {\r
298                                                 throw new ApplicationException("Extract error " + installerFile + " to " + tempDir);\r
299                                         }\r
300                                 }\r
301                                 \r
302                                 installerFile = seekInstallerFile(tempDir, InstalledPackage.Type);\r
303                                 if (installerFile == null && Directory.GetDirectories(tempDir).Length == 1) {\r
304                                         installerFile = seekInstallerFile(Path.Combine(tempDir, Directory.GetDirectories(tempDir)[0]), InstalledPackage.Type);\r
305                                 }\r
306                         }\r
307                         \r
308                         int exitCode = invokeInstaller(installerFile, InstalledPackage.Type);\r
309                         \r
310                         installed = true;\r
311                         \r
312                         return exitCode;\r
313                 }\r
314                 \r
315                 /// <summary>\r
316                 /// もっともふさわしいインストーラー番号を返す\r
317                 /// </summary>\r
318                 /// <returns></returns>\r
319                 protected internal static int GetPreferInstallerIndex(Package installedPackage)\r
320                 {\r
321                         if (installedPackage.Type == InstallerType.CANNOT_INSTALL) { // インストール不能パッケージ\r
322                                 return -1;\r
323                         }\r
324                         \r
325                         int best = -1;\r
326                         int bestVal = 0;\r
327                         \r
328                         for (int i = 0; i < installedPackage.Installer.Length; i++) {\r
329                                 Platform platform = installedPackage.Installer[i].Platform;\r
330                                 \r
331                                 int pts = 0;\r
332                                 if (platform != null) {\r
333                                         pts = (platform.IsRunnable())? 10 : 0;\r
334                                         pts += (platform.IsRunnableArchOnWow64() && platform.IsRunnableOS())? 1 : 0;\r
335                                 } else { // if (platform == null) {\r
336                                         pts = 1; // null の場合は動作プラットホーム扱い\r
337                                 }\r
338                                 if (pts > bestVal) {\r
339                                         bestVal = pts;\r
340                                         best = i;\r
341                                 }\r
342                         }\r
343                         \r
344                         return best;\r
345                 }\r
346                 \r
347                 #region インストーラーファイル設定時の処理\r
348                 \r
349                 /// <summary>\r
350                 /// インストーラーの保存先パスを生成\r
351                 /// </summary>\r
352                 /// <remarks>installerIndexが求まっているものとして仮定して処理する</remarks>\r
353                 private string getArchiveFilePath()\r
354                 {\r
355                         Package package = this.InstalledPackage;\r
356                         \r
357                         string folderName = string.Format("{0}({1})", package.Name, package.Version);\r
358                         string subFolderName = string.Format("installer.{0}", (installerIndex >= 0)? installerIndex.ToString("D") : "X");\r
359                         string fileName = NaGet.Utils.Url2filename(new Uri(package.Installer[0].Url.Href));\r
360                         \r
361                         string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, Path.Combine(folderName, subFolderName));\r
362                         \r
363                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
364                                 if (Directory.Exists(folder)) {\r
365                                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
366                                                 fileName = seekInstallerFile(folder, package.Type) ?? fileName;\r
367                                         }\r
368                                 } else {\r
369                                         Directory.CreateDirectory(folder);\r
370                                         \r
371                                         // 権限を親フォルダーに合わせる\r
372                                         try {\r
373                                                 Directory.SetAccessControl(folder, Directory.GetAccessControl(Path.GetDirectoryName(folder)));\r
374                                         } catch (Exception) {} // 失敗時無視\r
375                                 }\r
376                         }\r
377                         \r
378                         return Path.Combine(folder, fileName);\r
379                 }\r
380                 \r
381                 #endregion\r
382                                 \r
383                 /// <summary>\r
384                 /// インストーラーファイルを探す\r
385                 /// </summary>\r
386                 /// <param name="basedir">探すフォルダー</param>\r
387                 /// <param name="type">インストーラーの種類</param>\r
388                 /// <returns>探し出されたインストーラーファイルのフルパス。存在しないならばnull</returns>\r
389                 private static string seekInstallerFile(string basedir, InstallerType type)\r
390                 {\r
391                         if (! Directory.Exists(basedir)) {\r
392                                 return null;\r
393                         }\r
394                         \r
395                         System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();\r
396                         switch (type) {\r
397                                 case InstallerType.MSI_PACKAGE:\r
398                                         list.AddRange(Directory.GetFiles(basedir, "*.msi"));\r
399                                         break;\r
400                                 case InstallerType.EXEC_INSTALLER:\r
401                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
402                                         break;\r
403                                 case InstallerType.ARCHIVE:\r
404                                         list.AddRange(Directory.GetFiles(basedir, "*.zip"));\r
405                                         list.AddRange(Directory.GetFiles(basedir, "*.lzh"));\r
406                                         list.AddRange(Directory.GetFiles(basedir, "*.cab"));\r
407                                         list.AddRange(Directory.GetFiles(basedir, "*.7z"));\r
408                                         list.AddRange(Directory.GetFiles(basedir, "*.tar*"));\r
409                                         break;\r
410                                 case InstallerType.ITSELF:\r
411                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
412                                         break;\r
413                                 default:\r
414                                         return null;\r
415                         }\r
416                         \r
417                         // 存在しないファイルを削除\r
418                         list.RemoveAll(\r
419                                 delegate(string file) {\r
420                                         return ! File.Exists(file);\r
421                                 }\r
422                         );\r
423                         \r
424                         // "setup"の語が入ったファイルはインストーラとみなし、優先選択\r
425                         foreach (string path in list) {\r
426                                 if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) {\r
427                                         return path;\r
428                                 }\r
429                         }\r
430                         \r
431                         // それ以外なら一つ目を返す\r
432                         return (list.Count > 0)? list[0] : null;\r
433                 }\r
434                 \r
435                 \r
436                 /// <summary>\r
437                 /// アーカイブファイルの展開・インストールを行う\r
438                 /// </summary>\r
439                 /// <param name="archiveInstArgs">"archive-inst.exe"への引数</param>\r
440                 /// <param name="outputReceived">標準出力用リスナ(null可)</param>\r
441                 /// <param name="errorReceived">エラー出力用リスナ(null可)</param>\r
442                 /// <returns>実行プロセス</returns>\r
443                 private static Process createExtractArchiveProcess(string archiveInstArgs,\r
444                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> outputReceived,\r
445                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> errorReceived)\r
446                 {\r
447                         string archiveInstExe = Path.GetFullPath("archive-inst.exe");\r
448                         if (! File.Exists(archiveInstExe)) {\r
449                                 string errMsg = string.Format("\"{0}\" does not found!");\r
450                                 throw new ApplicationException(errMsg,\r
451                                                                new FileNotFoundException(errMsg, archiveInstExe));\r
452                         }\r
453                         \r
454                         \r
455                         \r
456                         ProcessStartInfo procInfo = new ProcessStartInfo(archiveInstExe, archiveInstArgs);\r
457                         procInfo.UseShellExecute = false;\r
458                         procInfo.CreateNoWindow = true;\r
459                         procInfo.WorkingDirectory = Environment.CurrentDirectory;\r
460                         \r
461                         return NaGet.Utils.ProcessStartWithOutputCapture(procInfo, outputReceived, errorReceived);\r
462                 }\r
463                 \r
464                 /// <summary>\r
465                 /// ダウンロードしたインストーラファイルを削除する\r
466                 /// </summary>\r
467                 public virtual void RemoveDownloadedFile()\r
468                 {\r
469                         if (Downloaded && File.Exists(installerFile)) {\r
470                                 File.Delete(installerFile);\r
471                         }\r
472                 }\r
473                 \r
474                 public override string ToString()\r
475                 {\r
476                         return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version);\r
477                 }\r
478                 \r
479                 public static string ToString(Installation[] installations)\r
480                 {\r
481                         string[] strs = new string[installations.Length];\r
482                         for (int i = 0; i < installations.Length; i++) {\r
483                                 strs[i] = installations[i].ToString();\r
484                         }\r
485                         return string.Join(" ", strs);\r
486                 }\r
487                 \r
488                 #region 便利メソッド群\r
489                 \r
490                 /// <summary>\r
491                 /// パッケージ配列をインストール処理配列に変換する便利メソッド\r
492                 /// </summary>\r
493                 /// <param name="pkgs">パッケージ配列</param>\r
494                 /// <returns>変換されたインストール処理配列</returns>\r
495                 public static Installation[] ConvertInstallations(Package[] pkgs)\r
496                 {\r
497                         Installation[] insts = new Installation[pkgs.Length];\r
498                         for (int i = 0; i < pkgs.Length; i++) {\r
499                                 insts[i] = new Installation(pkgs[i]);\r
500                         }\r
501                         return insts;\r
502                 }\r
503                 \r
504                 /// <summary>\r
505                 /// パッケージがインストール可能かを戻します。\r
506                 /// </summary>\r
507                 /// <param name="pkg">検査対象のパッケージ</param>\r
508                 /// <returns>インストール可能か否か</returns>\r
509                 public static bool IsInstallablePackage(Package pkg)\r
510                 {\r
511                         int installerIndex = GetPreferInstallerIndex(pkg);\r
512                         return installerIndex >= 0;\r
513                 }\r
514                 \r
515                 #endregion\r
516         }\r
517 }\r