[IADP Series] 讓 Silverlight 應用程式也可以進入 IADP 平台 (上)

點部落的 IADP 挑戰大賽已經在 6/19 順利而圓滿的結束了,而在比賽期間,所有的參賽選手 (包括我) 一共貢獻了超過五十篇以上精彩且具有一定水準以上的技術分享文章,我在此可以拍胸脯保證,只要看點部落的 IADP 系列文章並照做的話,一定可以順利上傳且符合 IADP 的最低要求資格 (但不保證一定會 Published 就是了,因為那要看 Intel 那裡的審核結果)...

點部落的 IADP 挑戰大賽已經在 6/19 順利而圓滿的結束了,而在比賽期間,所有的參賽選手 (包括我) 一共貢獻了超過五十篇以上精彩且具有一定水準以上的技術分享文章,我在此可以拍胸脯保證,只要看點部落的 IADP 系列文章並照做的話,一定可以順利上傳且符合 IADP 的最低要求資格 (但不保證一定會 Published 就是了,因為那要看 Intel 那裡的審核結果)。

在這段期間,我們使用的專案類型大多是 Windows Forms 應用程式或 WPF 應用程式,怎麼會沒有 Silverlight 應用程式呢?因為 Intel AppUp 的 SDK 並不相容於 Silverlight 的組件,導致 Out-of-Browser 的 Silverlight 應用程式無法使用 SDK 的函式,這也等於宣告了 OOB 的 Silverlight 應用程式無法被發布到 Intel AppUp 平台上,就算是想辦法將 Silverlight 和 Windows Forms 應用程式整合,但由於 Silverlight 是一個外掛元件,在無法確定用戶端是否有安裝 Silverlight Runtime 的情況下,為應用程式驗證添加很大的變數。

同樣的問題也發生在 XNA Framework 上,不過在 Intel AppUp Developer Center 中有一位專家發表文章,說明如何將 XNA Framework 整合到 Silent Installation 中,以在 Intel AppUp 安裝應用程式時,一併將 XNA Framework Runtime 部署進去,讓使用者可以順利執行 XNA 的應用程式。當時 Bill 提出想要試試將 Silverlight 應用程式放到 IADP 平台時,指出了這篇文章,我看過以後也有了些計較,所以也研究了一下作法。XNA 文章的作法是自己自訂一個 Installer 類別,這個類別會使用在自訂動作 (Custom Actions),它會做一些事情,以順利的達成安裝 XNA Framework 的工作。

要在安裝程式中部署外掛程式其實不難,但難點是在 Windows Installer 不允許同時執行兩個以上的 MSI 安裝,Windows Installer 的安裝程式服務 msiexec.exe 會偵測是否在同一時間內只有一個 MSI 正在安裝,而 Intel AppUp 又規定一定要 Silent Installation,所以不能提示任何訊息給使用者看 ... 在這種情況下,程式開發人員只能想辦法在原本的 MSI 安裝檔中,啟動另一個安裝程式,而且這個安裝程式不會因為原本的 MSI 而被擋住。要做到這個能力,必須要用 Custom Actions,並且覆寫 OnCommitted 事件常式加入啟動另一個安裝程式的指令才做的到。Visual Studio 的安裝專案本身無法加入自訂動作的程式碼,所以要另外新增類別庫的專案,並且使用下列程式碼:

using System;
using System.Collections;
using System.Configuration.Install;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace SilverlightSlientInstaller
{
    [RunInstaller(true)]
    public class SLSilentInstaller : Installer
    {
        protected override void OnCommitted(IDictionary savedState)
        {
            base.OnCommitted(savedState);

            string path = Path.GetDirectoryName(new Uri(this.GetType().Assembly.CodeBase).LocalPath);

            // run Silverlight installer package.
            Process installSL = new Process();
            ProcessStartInfo installSLInfo = new ProcessStartInfo();
            installSLInfo.FileName = path + @"\SilverlightSilentInstallLauncher.exe";
            installSLInfo.Arguments = "\"" + path + "\"";
            installSLInfo.UseShellExecute = false;
            installSLInfo.CreateNoWindow = true;
            installSL.StartInfo = installSLInfo;
            installSL.Start();
        }
    }
}

 

也許有人注意到了,我在這裡並沒有直接呼叫 Silverlight 的安裝程式 Silverlight.exe,而是另外呼叫了一個 SilverlightSilentInstallLauncher.exe,原因是避免 MSI 重覆執行的問題,而這支 SilverlightSilentInstallLauncher.exe 也只是很單純的一支 Console 應用程式而已,程式碼如下:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace SilverlightSilentInstallLauncher
{
    class Program
    {
        static void Main(string[] args)
        {
            FileStream fs = new FileStream(
                Path.GetTempPath() + @"\SLSilentInstall.log", FileMode.Create, FileAccess.Write);
            StreamWriter sw = new StreamWriter(fs);

            try
            {
                // run Silverlight installer package.
                Process installSL = new Process();
                ProcessStartInfo installSLInfo = new ProcessStartInfo();
                installSLInfo.FileName = args[0].Replace("\"", "") + @"\Silverlight.exe";
                installSLInfo.Arguments = "/q /ignorewarnings /noupdate";
                installSLInfo.UseShellExecute = false;
                installSLInfo.CreateNoWindow = true;

                installSL.StartInfo = installSLInfo;
                installSL.Start();

                installSL.WaitForExit();

                sw.WriteLine("SL installed.");
            }
            catch (Exception e)
            {
                sw.WriteLine("Exception occurred: " + e.Message);
            }
            finally
            {
                sw.Close();
            }
        }
    }
}

在這段程式碼中,除了要啟動 Silverlight.exe 進行靜默安裝 (silent install) 外,也需要等到安裝結束,這是有原因的,稍後會提。而要等待它結束,要在 Process.Start() 被呼叫後,呼叫 WaitForExit() 方法即可,無參數表示等到 Process 結束,如果有給參數 (TimeSpan),表示等待的 Timeout 時間。順帶說明的是,如果不需要顯示畫面,可以設定 CreateNoWindow 為 true,而且 UseShellExecute 也要設為 false。

接著,為了要能夠執行 Silverlight 應用程式並整合 Intel AppUp SDK,我們需要一個 Windows Forms 或 WPF 的 Hosting Environment 來掛載執行環境,所以要新增一個 Windows Forms 或 WPF 的應用程式,掛載與整合的細節將在下一篇文章說明,不過新增完成後,我們要先在專案中新增一個畫面,這個畫面會用在 SilverlightSilentInstallLauncher.exe 仍在執行時:

image

 

接著修改 Program.cs,使用下列程式碼:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows.Forms;

namespace SLHostingRuntime
{
    static class Program
    {
        private static AutoResetEvent eventSignal = new AutoResetEvent(false);

        [STAThread]
        static void Main()
        {
            Thread detectProcessThread = new Thread(new ThreadStart(DetectSLInstallProcess));

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Process[] processes = Process.GetProcessesByName("SilverlightSilentInstallLauncher");

            if (processes != null && processes.Length > 0)
            {
                InstallState stateForm = new InstallState();

                stateForm.Show();
                Application.DoEvents();

                detectProcessThread.Start();
                eventSignal.WaitOne();
                detectProcessThread.Abort();
                stateForm.Close();
            }

            processes = null;

            Application.Run(new SLHostingForm());
        }

        public static void DetectSLInstallProcess()
        {
            while (true)
            {
                // detect process is exist.
                Process[] processes = Process.GetProcessesByName("SilverlightSilentInstallLauncher");

                if (processes == null || processes.Length == 0)
                    break;

                Thread.Sleep(500);
            }

            eventSignal.Set();
        }
    }
}

這段程式碼會偵測 SilverlightSilentInstallLauncher 行程是否仍然存在,如果存在,表示 Silverlight.exe 仍在安裝中,如果它還在安裝,那即使啟動主程式也是無法執行的,所以必須偵測它,且如果存在的話要等它結束,並且顯示剛剛設計的訊息視窗。在這裡使用了 AutoResetEvent 等待物件,讓主執行緒在偵測的執行緒完成前都要等待,而當 偵測執行緒收到 AutoResetEvent 的信號後,才讓主執行緒繼續執行。

最後就是我們的安裝專案了,新增一個安裝專案,並且依照 Intel AppUp 的要求設定它 (包含 Silent Install 與移除必要條件偵測等,在點部落的 IADP 系列文章中都有),接著把前面新增的兩個專案 SilverlightSilentInstaller 與 SilverlightSilentInstallLauncher 的主要輸出加入到安裝專案中,記得也要加入 Silverlight 的轉散布檔案 Silverlight.exe。接著就是在自訂動作中加入要使用的 Custom Actions 的 DLL,也就是 SilverlightSilentInstaller 這個 DLL:

image

 

至此,我們可以得到一個 MSI 檔案,裡面包含了必要的執行檔以及 Silverlight.exe 轉散布檔,我們實際在 VM 中執行看看 (Windows 7 with SP1 x64),就如同一般在 Windows 7 跑安裝程式一樣,會看到 UAC 的畫面:

image

而 MSI 檔安裝完成後,馬上執行桌面上的 SLHostingRuntime demo,如果 Silverlight 在安裝時,會出現提示:

image

當 Silverlight 安裝完成後,會自動進入主畫面 (現在當然是空的 :) ):

image

這支程式已經在 Windows XP with SP3 x86,Windows 7 with SP1 x86 與 Windows 7 with SP1 x64 均成功執行,所以基本上可以符合 Intel AppUp 的封裝需求了。

Sample Download: https://dotblogsfile.blob.core.windows.net/user/regionbbs/1107/2011730221921854.rar

Reference:

http://appdeveloper.intel.com/en-us/article/how-installing-xna-framework-your-msi-installer-using-visual-studio-2008-and-xnainstaller

http://download.microsoft.com/download/7/8/d/78da8ec9-8801-42e5-89e5-3809386f1316/silverlight deployment guide.doc