OSDN Git Service

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