Porting your Silverlight apps to IADP

Jeff Chu and I studied how to porting Silverlight application to IADP last several weeks. After we read “HOW TO: Installing the XNA framework from your MSI installer using Visual Studio 2008 and XnaInstaller”, we thought that if XNA Apps can be submitted successful, Silverlight apps should be, too.

Just a few days ago, I submitted a Silverlight and it has been validated. So, we prove that this is indeed feasible. Now, let’s go through the procedures to know how to do it.

Jeff Chu and I studied how to porting Silverlight application to IADP last several weeks. After we read “HOW TO: Installing the XNA framework from your MSI installer using Visual Studio 2008 and XnaInstaller”, we thought that if XNA Apps can be submitted successful, Silverlight apps should be, too.

 

Just a few days ago, I submitted a Silverlight app and it has been validated. So, we prove that this is indeed feasible. Now, let’s go through the procedures to know how to do it.

 

For this purpose, we should deliver some goals.

 

1. Hosting Silverlight app in Windows Form Application/WPF Application with WebBrowser Control.

2. Protect the Silverlight app.

3. Silent install Silverlight runtime.

 

Hosting Silverlight

 

Why we need to host Silverlight control in Windows Form App/WPF App? The first reason is AppUp apps need include Intel AppUp SDK to check the authorization. But you can’t use Intel AppUp SDK for .NET in Silverlight environment.

 

The second reason is that AppUp Client can’t launch Silverlight app directly, even though it’s an OOB app. So we should build up an exe file to be launched by AppUp Client.

 

In this sample, I will show how to host Silverlight in Windows Form Application.

 

(1-1) Build up a simple Silverlight app project. Only needs a button on the page

image

 

(1-2) Click the button, and write down code as below:

 

<C#>
private void button1_Click(object sender, RoutedEventArgs e)
       {  MessageBox.Show("Hello Silverlight"); }

<VB>
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
      MessageBox.Show("Hello Silverlight")
End Sub

 

(2-1) Build up a Windows Form Application (SLHostCS/SLHostVB), put a WebBrowser Control on the form and set the WebBrowser control’s dock property to Dockstyle.Fill.

 

(2-2) Add Intel AppUp SDK to refrence, and using (Imports) com.intel.adp namespace.

 

(2-3) Add ComVisible attribute

 

<C#>
[System.Runtime .InteropServices .ComVisible (true)]

<VB>
<System.Runtime.InteropServices.ComVisible(True)>

 

(2-4) Add a html page to this Windows Forms Application named “MainPage.Htm”

html

 

(2-5) Define AdpApolication object and appIdArray in Form1.cs\Form1.vb

<C#>
    public partial class Form1 : Form
    {
        Private AdpApplication app;
        private Object[] appIdArray = new Object[] { 0x11111111, 0x11111111, 0x11111111, 0x11111111 };
        //Note: replace "0x11111111, 0x11111111, 0x11111111, 0x11111111" with the actual application ID

<VB>
Public Class Form1
    Dim app As AdpApplication
    Dim appIdArray() As Object = New Object(3) {&H11111111&, &H11111111&, &H11111111&, &H11111111&}
    ''Note: replace "&H11111111, &H11111111, &H11111111, &H11111111" with the actual application ID

 

(2-6)Write a method to call MainPage.htm

<C#>
private void RunXap()
        {
            webBrowser1.ObjectForScripting = this;
            webBrowser1.AllowWebBrowserDrop = false;
            webBrowser1.IsWebBrowserContextMenuEnabled = false;
            webBrowser1.WebBrowserShortcutsEnabled = false;
            String uripath = Path.GetDirectoryName(new Uri(this.GetType().Assembly.CodeBase).LocalPath) + "\\MainPage.htm";
            Uri xapUri = new Uri(uripath);
            webBrowser1.Navigate(xapUri);
            webBrowser1.AllowNavigation = false;
        }

<VB>
Private Sub RunXap()
        WebBrowser1.ObjectForScripting = Me
        WebBrowser1.AllowWebBrowserDrop = False
        WebBrowser1.IsWebBrowserContextMenuEnabled = False
        WebBrowser1.WebBrowserShortcutsEnabled = False
        Dim uripath As String = Path.GetDirectoryName(New Uri(Me.GetType().Assembly.CodeBase).LocalPath) & "\MainPage.htm"
        Dim xapUri As New Uri(uripath)
        WebBrowser1.Navigate(xapUri)
        WebBrowser1.AllowNavigation = False
End Sub

 

(2-7) In Form Load event Method, add code for IADP and RunXap method.

<C#>
private void Form1_Load(object sender, EventArgs e)
        {   
            try
            {
                AdpApplicationId appId = new AdpApplicationId(Convert.ToUInt32(appIdArray[0]), Convert.ToUInt32(appIdArray[1]), Convert.ToUInt32(appIdArray[2]), Convert.ToUInt32(appIdArray[3]));
                app = new AdpApplication(appId);
                RunXap();
            }
            catch (AdpException ex)
            {
                if (ex is AdpErrorException)
                {
                   System.Environment.Exit(1);
                }
                else if (ex is AdpWarningException)
                {
                   MessageBox.Show(ex.Message, "Warning");
                }
            }
        }

<VB>
  Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Try
            Dim appId As New AdpApplicationId(Convert.ToUInt32(appIdArray(0)), Convert.ToUInt32(appIdArray(1)), Convert.ToUInt32(appIdArray(2)), Convert.ToUInt32(appIdArray(3)))
            app = New AdpApplication(appId)
            RunXap()
        Catch ex As AdpException
            If TypeOf ex Is AdpErrorException Then
                System.Environment.Exit(1)
            ElseIf TypeOf ex Is AdpWarningException Then
                MsgBox(ex.Message)
            End If
        End Try
    End Sub

 

(2-8) Copy Silverlight Xap file SLSampleCS.Xap\SLSampleVB.Xap and MainPage.htm to Windows Forms Application output folder, and run exe file to test whether the Silverlight control will show or not.

image

 

 

Protect the Silverlight app

    Now we know how to host Silverlight in Windows Forms Application, but there still an issue. We need to avoid customers can run out Silverlight application directly, so we need some way to do it. simple way for this purpose is using QueryString, of course you can do it by WCF or something else.

 

(3-1) Let’s back to Silverlight Project and add a new page named “UnAuthorized”. It’s a simple page, only one TextBlock on it.

image

 

(3-2) Then build a class named “Authorize”

<C#>
public class Authorize
    {
        public static Object[] _appIdArray
        {
            get
            {
                return new Object[] { 0x11111111, 0x11111111, 0x11111111, 0x11111111};
                //Note: replace "0x11111111, 0x11111111, 0x11111111, 0x11111111" with the actual application ID
            }
        }
    }

<VB>
Public Class Authorize
    Public Shared ReadOnly Property _appIdArray() As Object()
        Get
            Return New Object(3) {&H11111111&, &H11111111&, &H11111111&, &H11111111&}
            ''Note: replace "&H11111111, &H11111111, &H11111111, &H11111111" with the actual application ID
        End Get
    End Property
End Class

 

(3-3) Modify Application.Startup event method in App.xaml.cs/App.xaml.vb

<C#>
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            UIElement targetPage;
            targetPage = new UnAuthorized();
            try
            {
                String keyid = String.Format("{0}-{1}-{2}-{3}", Authorize._appIdArray);
                String keyName = "AuthorizeCode";
                foreach (KeyValuePair<String, String> keyPair in HtmlPage.Document.QueryString)
                {
                    if (keyPair.Key == keyName && keyPair.Value == keyid)
                    {
                        targetPage = new MainPage();
                        break;
                    }
                }

            }
            catch (Exception ex)
            { throw ex; }
            finally
            { this.RootVisual = targetPage; }
        }

<VB>
  Private Sub Application_Startup(ByVal o As Object, ByVal e As StartupEventArgs) Handles Me.Startup
        Dim targetPage As UIElement
        targetPage = New UnAuthorized()
        Try
            Dim keyid As String = String.Format("{0}-{1}-{2}-{3}", Authorize._appIdArray)
            Dim keyName As String = "AuthorizeCode"
            For Each keyPair As KeyValuePair(Of String, String) In HtmlPage.Document.QueryString
                If keyPair.Key = keyName AndAlso keyPair.Value = keyid Then
                    targetPage = New MainPage()
                    Exit For
                End If
            Next
        Catch ex As Exception
            Throw ex
        Finally
            Me.RootVisual = targetPage
        End Try
    End Sub

 

(3-4) Compile the new Silverlight project and copy the new xap file to Windows Forms Application output folder, and run exe file to see what will be happened. It shows “UnAuthorized” page, that is what we want.

image

 

(3-5) Back to Windows Forms Application project, we need to modify RunXap method.

<C#>
        private void RunXap()
        {
            webBrowser1.ObjectForScripting = this;
            webBrowser1.AllowWebBrowserDrop = false;
            webBrowser1.IsWebBrowserContextMenuEnabled = false;
            webBrowser1.WebBrowserShortcutsEnabled = false;
            String uripath = Path.GetDirectoryName(new Uri(this.GetType().Assembly.CodeBase).LocalPath) + "\\MainPage.htm";
            // Uri xapUri = new Uri(uripath);
            // Modify as Below
            String keyid = String.Format("{0}-{1}-{2}-{3}", appIdArray);
            String keyName = "AuthorizeCode";
            Uri xapUri = new Uri(String.Format("{0}?{1}={2}", uripath, keyName, keyid));
            // Modify end
            webBrowser1.Navigate(xapUri);
            webBrowser1.AllowNavigation = false;
        }

<VB>
Private Sub RunXap()
        WebBrowser1.ObjectForScripting = Me
        WebBrowser1.AllowWebBrowserDrop = False
        WebBrowser1.IsWebBrowserContextMenuEnabled = False
        WebBrowser1.WebBrowserShortcutsEnabled = False
        Dim uripath As String = Path.GetDirectoryName(New Uri(Me.GetType().Assembly.CodeBase).LocalPath) & "\MainPage.htm"
        '' Dim xapUri As New Uri(uripath)
        '' Modify as Below
        Dim keyid As String = String.Format("{0}-{1}-{2}-{3}", appIdArray)
        Dim keyName As String = "AuthorizeCode"
        Dim xapUri As New Uri(String.Format("{0}?{1}={2}", uripath, keyName, keyid))
        '' Modify end
        WebBrowser1.Navigate(xapUri)
        WebBrowser1.AllowNavigation = False
    End Sub

 

(3-6) So far we finished Silverlight hosting and protection, in next step, we will implement how to silent install Silverlight runtime.

 

Silent install Silverlight runtime

 

(4-1) Go to http://www.silverlight.net/downloads to download Silverlight runtime (file name is Silverlight.exe)

 

(4-2) Back to the Windows Forms project, add a new class named “InstallerUtility”. There are three main methods in this class.

(4-2-1) IsRuntimeInstalled method check whether Silverlight runtime has been installed or not.
(4-2-2) IsInstallerRunning method check whether Silverlight runtime is installing or not.
(4-2-3) OnRunning method is used for temporizing when user launch the application before Silverlight runtime has been installed complete.

<C#>
    class InstallerUtility
    {
        String keyName;
        String valueName;
        public InstallerUtility()
        {
            keyName = "Software\\Microsoft\\Silverlight";
            valueName = "Version";
        }

        public Boolean IsRuntimeInstalled(String targetVersion)
        {
            Boolean returnValue = false;
            RegistryKey silverlightKey = Registry.LocalMachine.OpenSubKey(keyName);
            if (silverlightKey != null)
            {
                Object installedValue = silverlightKey.GetValue(valueName);
                if (installedValue != null)
                {
                    RegistryValueKind valueKind = silverlightKey.GetValueKind(valueName);
                    if (valueKind == RegistryValueKind.String)
                    {
                        if (String.Compare(installedValue.ToString(), targetVersion) >= 0)
                        {
                            returnValue = true;
                        }
                    }
                }
            }
            return returnValue;
        }

        public Boolean IsInstallerRunning()
        {
            Boolean returnValue = false;
            Process[] processes = Process.GetProcessesByName("Silverlight");
            if (processes != null)
            {
                if (processes.Length > 0)
                {
                    returnValue = true;
                }
            }
            return returnValue;
        }

        public void OnRunning()
        {
            Application.DoEvents();
            while (IsInstallerRunning())
            {
                Thread.Sleep(500);
            }
        }
    }

<VB>
Public Class InstallerUtility
    Private keyName As String
    Private valueName As String
    Sub New()
        keyName = "Software\Microsoft\Silverlight"
        valueName = "Version"
    End Sub
    Public Function IsRuntimeInstalled(ByVal targetVersion As String) As Boolean
        Dim returnValue As Boolean = False

        Dim silverlightKey As RegistryKey = Registry.LocalMachine.OpenSubKey(keyName)
        If Not silverlightKey Is Nothing Then
            Dim installedValue As Object = silverlightKey.GetValue(valueName)
            If Not installedValue Is Nothing Then
                Dim valueKind As RegistryValueKind = silverlightKey.GetValueKind(valueName)
                If valueKind = RegistryValueKind.String Then
                    If String.Compare(installedValue.ToString(), targetVersion) >= 0 Then
                        returnValue = True
                    End If
                End If
            End If
        End If
        Return returnValue
    End Function

    Public Function IsInstallerRunning() As Boolean
        Dim returnValue As Boolean = False

        Dim processes() As Process = Process.GetProcessesByName("Silverlight")
        If Not processes Is Nothing Then
            If processes.Length > 0 Then
                returnValue = True
            End If
        End If
        Return returnValue
    End Function

    Public Sub OnRunning()
        Application.DoEvents()
        While IsInstallerRunning() = True
            Thread.Sleep(500)
        End While
    End Sub
End Class

 

(4-3) Add a new class by “Installer Class” Template and named “Launcher”

image

 

(4-3) Add code to Launcher Class as below, let the Silverlight.exe will be executed after your application has been installed complete. The Argument /q will silent install Silverlight runtime.

<C#>
protected override void OnCommitted(IDictionary savedState)
        {
            InstallerUtility utility = new InstallerUtility();
            if (!utility.IsInstallerRunning())
            {
                if (!utility.IsRuntimeInstalled("4.0.60531.0"))
                {
                    Process installProcess = new Process();
                    installProcess.StartInfo.FileName = Path.GetDirectoryName(new Uri(this.GetType().Assembly.CodeBase).LocalPath) + "\\runtimesource\\silverlight.exe";
                    installProcess.StartInfo.Arguments = "/q";
                    installProcess.Start();
                }
            }
            base.OnCommitted(savedState);
        }

<VB>
Protected Overrides Sub OnCommitted(ByVal savedState As System.Collections.IDictionary)
        Dim utility As New InstallerUtility
        If utility.IsInstallerRunning() = False Then
            If utility.IsRuntimeInstalled("4.0.60531.0") = False Then
                Dim installProcess As New Process
                installProcess.StartInfo.FileName = Path.GetDirectoryName(New Uri(Me.GetType().Assembly.CodeBase).LocalPath) & "\runtimesource\silverlight.exe"
                installProcess.StartInfo.Arguments = "/q"
                installProcess.Start()
            End If
        End If
        MyBase.OnCommitted(savedState)
End Sub

 

(4-4) Add a new form named “RunningWindow”. The purpose of this form is to show a "Waiting for Silverlight installing" window. You will have to show this window in case the user tries to run your game while the Silverlight runtime is still being installed. Of course, you can make it fancier then a boring single label.

image

 

(4-5) Then we need to modify the code of Form1 (the startup form of application).

<C#>
// Modify as Below
    InstallerUtility utility = new InstallerUtility();
    if (utility.IsInstallerRunning())
    {
        RunningWindow running = new RunningWindow();
        running.Show();
        Application.DoEvents();
        Thread temporizing = new Thread(new ThreadStart(utility.OnRunning));
        temporizing.Start();
        temporizing.Join();
        running.Close();
        Thread.Sleep(500);
    }
    if (utility.IsRuntimeInstalled("4.0.60531.0"))
    { RunXap(); }
    else
    { MessageBox.Show("Need Silverlight runtime installed!"); }
    // Modify End

<VB>
   '' Modify as Below
   Dim utility As New InstallerUtility()
   If utility.IsInstallerRunning() = True Then
       Dim running As New RunningWindow()
       running.Show()
       Application.DoEvents()
       Dim temporizing As New Thread(AddressOf utility.OnRunning)
       temporizing.Start()
       temporizing.Join()
       running.Close()
       Thread.Sleep(500)
   End If
   If utility.IsRuntimeInstalled("4.0.60531.0") = True Then
       RunXap()
   Else
       MessageBox.Show("Need Silverlight runtime installed!")
   End If
   '' Modify end

 

(5-1) Build a setup project and add files of your application (exe, xap, htm..)

image

 

(5-2) In the "File System" editor, add “runtimesource” folder, then add “Silverlight.exe” under it.

image

image

 

(5-3) Create your app’s shortcut in “User’s Desktop” and “User’s Programs Menu”

 

(5-4) In the “User Interface” editor, remove the following dialogs

image

 

(5-5) In the "Custom Actions" editor, add "SLHostCS.exe"/"SLHostVB.exe" from the Application Folder to the "Install", "Commit", "Rollback" and "Uninstall" custom actions.

image

 

(5-6) Remember to remove “Prerequisites”

image

 

(5-7) if you use Visual Studio 2010, open the “Properties Window” in “Launch Conditions” then modify .Net Framework Version to right version.

image

image

 

Ok, we now have a Silverlight package that complies with IADP application packaging requirements.

 

Sample Code: Visual Studio 2010 + Silverlight SDK 4
IADPSilverlightDemoCS.zip
IADPSilverlightDemoVB.zip