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                                                 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                                 hProcess.WaitForExit();\r
286                                 \r
287                                 return hProcess.ExitCode;\r
288                         } finally {\r
289                                 if (hProcess != null) {\r
290                                         hProcess.Close();\r
291                                 }\r
292                         }\r
293                 }\r
294                 \r
295                 /// <summary>\r
296                 /// インストーラ等を起動してインストール作業を行う\r
297                 /// </summary>\r
298                 /// <returns>インストーラの終了コード</returns>\r
299                 public int Install()\r
300                 {\r
301                         string installerFile = this.installerFile;\r
302                         string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());\r
303                         \r
304                         // アーカイブされているなら一旦展開\r
305                         if (InstalledPackage.ArchivedInstaller) {\r
306                                 Directory.CreateDirectory(tempDir);\r
307                                 \r
308                                 string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir);\r
309                                 using (Process hProcess = createExtractArchiveProcess(argument,\r
310                                                                                       this.OutputDataReceived,\r
311                                                                                       this.ErrorDataReceived)) {\r
312                                         hProcess.WaitForExit();\r
313                                 \r
314                                         if (hProcess.ExitCode != 0) {\r
315                                                 throw new ApplicationException("Extract error " + installerFile + " to " + tempDir);\r
316                                         }\r
317                                 }\r
318                                 \r
319                                 installerFile = seekInstallerFile(tempDir, InstalledPackage.Type);\r
320                                 if (installerFile == null && Directory.GetDirectories(tempDir).Length == 1) {\r
321                                         installerFile = seekInstallerFile(Path.Combine(tempDir, Directory.GetDirectories(tempDir)[0]), InstalledPackage.Type);\r
322                                 }\r
323                         }\r
324                         \r
325                         int exitCode = invokeInstaller(installerFile, InstalledPackage.Type);\r
326                         \r
327                         installed = true;\r
328                         \r
329                         return exitCode;\r
330                 }\r
331                 \r
332                 /// <summary>\r
333                 /// もっともふさわしいインストーラ番号を返す\r
334                 /// </summary>\r
335                 /// <returns></returns>\r
336                 public static int GetPreferInstallerIndex(Package InstalledPackage)\r
337                 {\r
338                         if (InstalledPackage.Type == InstallerType.CANNOT_INSTALL) { // インストール不能パッケージ\r
339                                 return -1;\r
340                         }\r
341                         \r
342                         int best = -1;\r
343                         int bestVal = 0;\r
344                         \r
345                         for (int i = 0; i < InstalledPackage.Installer.Length; i++) {\r
346                                 Platform platform = InstalledPackage.Installer[i].Platform;\r
347                                 \r
348                                 int pts = 0;\r
349                                 if (platform != null) {\r
350                                         pts = (platform.IsRunnable())? 10 : 0;\r
351                                         pts += (platform.IsRunnableArchOnWow64() && platform.IsRunnableOS())? 1 : 0;\r
352                                 } else { // if (platform == null) {\r
353                                         pts = 1; // null の場合は動作プラットホーム扱い\r
354                                 }\r
355                                 if (pts > bestVal) {\r
356                                         bestVal = pts;\r
357                                         best = i;\r
358                                 }\r
359                         }\r
360                         \r
361                         return best;\r
362                 }\r
363                 \r
364                 /// <summary>\r
365                 /// インストーラの保存先パスを生成\r
366                 /// </summary>\r
367                 private string getArchiveFilePath()\r
368                 {\r
369                         Package package = this.InstalledPackage;\r
370                         \r
371                         string folderName = string.Format("{0}({1})", package.Name, package.Version);\r
372                         string fileName = NaGet.Utils.Url2filename(package.Installer[0].Url.Href);\r
373                         \r
374                         string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, folderName);\r
375                         \r
376                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
377                                 if (Directory.Exists(folder)) {\r
378                                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
379                                                 fileName = seekInstallerFile(folder, package.Type) ?? fileName;\r
380                                         }\r
381                                 } else {\r
382                                         Directory.CreateDirectory(folder);\r
383                                         \r
384                                         // 権限を親フォルダに合わせる\r
385                                         try {\r
386                                                 Directory.SetAccessControl(folder, Directory.GetAccessControl(Path.GetDirectoryName(folder)));\r
387                                         } catch (Exception) {} // 失敗時無視\r
388                                 }\r
389                         }\r
390                         \r
391                         return Path.Combine(folder, fileName);\r
392                 }\r
393                 \r
394                 /// <summary>\r
395                 /// すでにインストールされているパッケージを取得する\r
396                 /// </summary>\r
397                 /// <param name="installedPkgs">\r
398                 /// インストールドリスト\r
399                 /// </param>\r
400                 /// <returns>\r
401                 /// インストールされているパッケージの情報。インストールされたパッケージが見つからないならばnullを返す  \r
402                 /// </returns>\r
403                 public InstalledPackage GetInstalledPackage(PackageList<InstalledPackage> installedPkgs)\r
404                 {\r
405                         return installedPkgs.GetPackageForName(InstalledPackage.Name);\r
406                 }\r
407                 \r
408                 /// <summary>\r
409                 /// インストーラファイルを探す\r
410                 /// </summary>\r
411                 /// <param name="basedir">探すフォルダ</param>\r
412                 /// <param name="type">インストーラの種類</param>\r
413                 /// <returns>探し出されたインストーラファイルのフルパス。存在しないならばnull</returns>\r
414                 private static string seekInstallerFile(string basedir, InstallerType type)\r
415                 {\r
416                         if (! Directory.Exists(basedir)) {\r
417                                 return null;\r
418                         }\r
419                         \r
420                         System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();\r
421                         switch (type) {\r
422                                 case InstallerType.MSI_PACKAGE:\r
423                                         list.AddRange(Directory.GetFiles(basedir, "*.msi"));\r
424                                         break;\r
425                                 case InstallerType.EXEC_INSTALLER:\r
426                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
427                                         break;\r
428                                 case InstallerType.ARCHIVE:\r
429                                         list.AddRange(Directory.GetFiles(basedir, "*.zip"));\r
430                                         list.AddRange(Directory.GetFiles(basedir, "*.lzh"));\r
431                                         list.AddRange(Directory.GetFiles(basedir, "*.cab"));\r
432                                         list.AddRange(Directory.GetFiles(basedir, "*.7z"));\r
433                                         list.AddRange(Directory.GetFiles(basedir, "*.tar*"));\r
434                                         break;\r
435                                 default:\r
436                                         return null;\r
437                         }\r
438                         \r
439                         // 存在しないファイルを削除\r
440                         list.RemoveAll(\r
441                                 delegate(string file) {\r
442                                         return ! File.Exists(file);\r
443                                 }\r
444                         );\r
445                         \r
446                         // "setup"の語が入ったファイルはインストーラとみなし、優先選択\r
447                         foreach (string path in list) {\r
448                                 if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) {\r
449                                         return path;\r
450                                 }\r
451                         }\r
452                         \r
453                         // それ以外なら一つ目を返す\r
454                         return (list.Count > 0)? list[0] : null;\r
455                 }\r
456                 \r
457                 \r
458                 /// <summary>\r
459                 /// アーカイブファイルの展開・インストールを行う\r
460                 /// </summary>\r
461                 /// <param name="archiveInstArgs">"archive-inst.exe"への引数</param>\r
462                 /// <param name="outputReceived">標準出力用リスナ(null可)</param>\r
463                 /// <param name="errorReceived">エラー出力用リスナ(null可)</param>\r
464                 /// <returns>実行プロセス</returns>\r
465                 private Process createExtractArchiveProcess(string archiveInstArgs,\r
466                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> outputReceived,\r
467                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> errorReceived)\r
468                 {\r
469                         string archiveInstExe = Path.GetFullPath("archive-inst.exe");\r
470                         if (! File.Exists(archiveInstExe)) {\r
471                                 string errMsg = string.Format("\"{0}\" does not found!");\r
472                                 throw new ApplicationException(errMsg,\r
473                                                                new FileNotFoundException(errMsg, archiveInstExe));\r
474                         }\r
475                         \r
476                         \r
477                         \r
478                         ProcessStartInfo procInfo = new ProcessStartInfo(archiveInstExe, archiveInstArgs);\r
479                         procInfo.UseShellExecute = false;\r
480                         procInfo.CreateNoWindow = true;\r
481                         procInfo.WorkingDirectory = Environment.CurrentDirectory;\r
482                         \r
483                         return NaGet.Utils.ProcessStartWithOutputCapture(procInfo, outputReceived, errorReceived);\r
484                 }\r
485                 \r
486                 /// <summary>\r
487                 /// ダウンロードしたインストーラファイルを削除する\r
488                 /// </summary>\r
489                 public virtual void RemoveDownloadedFile()\r
490                 {\r
491                         if (Downloaded && File.Exists(installerFile)) {\r
492                                 File.Delete(installerFile);\r
493                         }\r
494                 }\r
495                 \r
496                 public override string ToString()\r
497                 {\r
498                         return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version);\r
499                 }\r
500                 \r
501                 public static string ToString(Installation[] installations)\r
502                 {\r
503                         string[] strs = new string[installations.Length];\r
504                         for (int i = 0; i < installations.Length; i++) {\r
505                                 strs[i] = installations[i].ToString();\r
506                         }\r
507                         return string.Join(" ", strs);\r
508                 }\r
509                 \r
510                 /// <summary>\r
511                 /// パッケージ配列をインストール処理配列に変換する便利メソッド\r
512                 /// </summary>\r
513                 /// <param name="pkgs">パッケージ配列</param>\r
514                 /// <returns>変換されたインストール処理配列</returns>\r
515                 public static Installation[] ConvertInstallations(Package[] pkgs)\r
516                 {\r
517                         Installation[] insts = new Installation[pkgs.Length];\r
518                         for (int i = 0; i < pkgs.Length; i++) {\r
519                                 insts[i] = new Installation(pkgs[i]);\r
520                         }\r
521                         return insts;\r
522                 }\r
523         }\r
524 }\r