Running Windows Store Application From Desktop Application

一般來說,Windows Store Application對於Windows 8來說,是一個特別的應用程式,他執行在特殊的Container(容器)中,雖然有著與舊有執行檔相同的附檔名(.exe),但事實上
Windows Program Loader是無法直接載入執行的,必須先建立容器,然後要求該容器來執行。

/黃忠成

 

  一般來說,Windows Store Application對於Windows 8來說,是一個特別的應用程式,他執行在特殊的Container(容器)中,雖然有著與舊有執行檔相同的附檔名(.exe),但事實上
Windows Program Loader是無法直接載入執行的,必須先建立容器,然後要求該容器來執行。
  這個容器有著很特殊的權限設定,在一定程度上防止Windows Store Application做出影響安全性的行為,也就是大家耳熟能詳的sandbox(沙箱)容器,最明顯的限制就是,
此容器將僅限制執行在使用者權限所及的動作,也就是任何需要管理員權限的行為都會被容器所擋下。

  想要在Desktop Application中直接啟動Windows Store Application,如果不想遵循官方文件中的Protocol Activation準則,那麼所要克服的第一道牆就是如何建立出可讓
Windows Store Application執行於其中的容器。

 

關於Protocol Activation

http://devhammer.net/protocol-activation-what-is-it-what-apps-offer-it-and-how-can-i-use-it-in-my-apps

更詳盡的範例

http://www.dotblogs.com.tw/billchung/archive/2013/11/04/126525.aspx

 

IApplicationActivationManager

 

  WinRT API提供了IApplicationActivationManager來建立這個容器,其定義原型如下:

 


IApplicationActivationManager : public IUnknown

    {

    public:

        virtual HRESULT STDMETHODCALLTYPE ActivateApplication(

            /* [in] */ __RPC__in LPCWSTR appUserModelId,

            /* [unique][in] */ __RPC__in_opt LPCWSTR arguments,

            /* [in] */ ACTIVATEOPTIONS options,

            /* [out] */ __RPC__out DWORD *processId) = 0;

       

        virtual HRESULT STDMETHODCALLTYPE ActivateForFile(

            /* [in] */ __RPC__in LPCWSTR appUserModelId,

            /* [in] */ __RPC__in_opt IShellItemArray *itemArray,

            /* [unique][in] */ __RPC__in_opt LPCWSTR verb,

            /* [out] */ __RPC__out DWORD *processId) = 0;

       

        virtual HRESULT STDMETHODCALLTYPE ActivateForProtocol(

            /* [in] */ __RPC__in LPCWSTR appUserModelId,

            /* [in] */ __RPC__in_opt IShellItemArray *itemArray,

            /* [out] */ __RPC__out DWORD *processId) = 0;

       

    };

 

轉成C#之後如下:

public enum ActivateOptions

    {

        None = 0x00000000,  // No flags set

        DesignMode = 0x00000001,  // The application is being activated for design mode, and thus will not be able to

        // to create an immersive window. Window creation must be done by design tools which

        // load the necessary components by communicating with a designer-specified service on

        // the site chain established on the activation manager.  The splash screen normally

        // shown when an application is activated will also not appear.  Most activations

        // will not use this flag.

        NoErrorUI = 0x00000002,  // Do not show an error dialog if the app fails to activate.                               

        NoSplashScreen = 0x00000004,  // Do not show the splash screen when activating the app.

    }



    [ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

    interface IApplicationActivationManager

    {

        // Activates the specified immersive application for the "Launch" contract, passing the provided arguments

        // string into the application.  Callers can obtain the process Id of the application instance fulfilling this contract.

        IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);

        IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);

        IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);

    }



    [ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]//Application Activation Manager

    class ApplicationActivationManager : IApplicationActivationManager

    {

        [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]

        public extern IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);

        [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]

        public extern IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);

        [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]

        public extern IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);

    }

 

要在Desktop Application引用這個COM物件需要動一點手腳,你必須手動修改.csproj加入以下的設定。

 


<PropertyGroup>

    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

    <ProjectGuid>{F497F4E8-C2D0-47F1-8CA6-4CF6BF1AB2E1}</ProjectGuid>

    <OutputType>WinExe</OutputType>

    <AppDesignerFolder>Properties</AppDesignerFolder>

    <RootNamespace>WpfApplication1</RootNamespace>

    <AssemblyName>WpfApplication1</AssemblyName>

    <TargetPlatformVersion>8.0</TargetPlatformVersion>

    <FileAlignment>512</FileAlignment>

    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

    <WarningLevel>4</WarningLevel>

    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>

    <TargetFrameworkProfile />

  </PropertyGroup>

 

重點在於加入<TargetPlatformVersion>8.0</TargetPlatformaVersion>,一旦修改完成後,便可在此Project中引用Windows.winmd做為Referernce,開啟Desktop Application呼叫WinRT API的大門。

 

 

AppUserModelId

 

  IApplicationActivationManager所提供的三個函式都需要一個AppUserModelId參數,在Windows Store Application中,當應用程式裝到使用者電腦中後,便會產生一個AppUserModelId,一般來說,

這個AppUserModelId是by application存在的,只要有了這個AppUserModelId,便能要求容器執行此應用程式。

  有一點需特別注意,IApplicationActivationManager需要執行在使用者權限中,這意味著如果Desktop Application是執行在系統管理員權限中時,在正常情況下任何對於IApplicationActivationManager的呼叫都會以例外訊息告終。

  AppUserModelId存放於Registry中。

圖001

 

 

 

取得安裝應用程式列表

 

  當然,一切都得要先取得AppUserModelId才行,在Win RT API中提供了PackagerManager類別,透過這個類別可以取得現行使用者所安裝的Windows Store Application列表。


PackageManager pm = new PackageManager();

var list = pm.FindPackages().ToList();

注意,這必須執行在系統管理員權限之下,回傳的是一個IEumerable<Package>物件,每個元素都是Package物件,描述著一個Windows Store Applciation的資訊。

Package物件的ID屬性值對應到Registry中HKCU:\Software\Classes\ActivatableClasses\Package的子鍵值,每一個子鍵都代表著一個Windows Store Application,有了這個地圖便可以
取得應用程式列表及每個應用程式專屬的AppUserModelId,最後就能透過IApplicationActivationManager來執行。



最後一塊拼圖


  如果你仔細閱讀上面的說明,會發現有一個衝突點,那就是IApplicationActivationManager必須執行在使用者權限,但PackageManager卻必須執行在系統管理員權限,這意味著照正常的流程

是無法撰寫一支可列舉目前已安裝的Windows Store Application,讓使用者點選後執行的應用程式。

  在正常流程下唯一的解法就是分成兩支應用程式,一支用來取得應用程式列表及AppModelUserId,另一支用來執行Windows Store Application,前者將執行於系統管理員權限下,後者則執行於使用者權限下。

  下面是AppGenerate的程式碼列表,注意,這是一個WPF Application,且必須引用Windows.winmd(這意味著你必須先修改.csproj)。

 

AppGenerate\App.xaml.cs


using System;

using System.Collections.Generic;

using System.Configuration;

using System.Data;

using System.IO;

using System.Linq;

using System.Threading.Tasks;

using System.Windows;

using Windows.Management.Deployment;



namespace AppsGenerate

{

    /// 

    /// Interaction logic for App.xaml

    /// 

    public partial class App : Application

    {

        private static void GenerateAllPackages()

        {

            PackageManager pm = new PackageManager();

            var list = pm.FindPackages().ToList();

            using (FileStream fs = new FileStream("list.dat", FileMode.Create, FileAccess.Write))

            {

                using (StreamWriter sw = new StreamWriter(fs))

                {

                    foreach (var item in list)

                    {

                        if (item.IsFramework) continue;

                        sw.WriteLine(item.Id.Name + "," + item.Id.FullName);

                    }

                }

            }

        }



        void App_Startup(object sender, StartupEventArgs e)

        {

            GenerateAllPackages();

            Environment.Exit(0);

        }

    }

}


負責執行Windows Store Application的應用程式啟動時會先執行這個AppGenerate程式,由於前者必須執行於使用者權限下,照正常流程其喚起的子程式都將執行於同樣的權限下,

不過.NET Framework有個例外,就是可以透過定義Application.Manfest來要求執行於系統管理員權限下。

 

AppGenerate\app.manifest


<?xml version="1.0" encoding="utf-8"?>

<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>

  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">

    <security>

      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">

      

        <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

      </requestedPrivileges>

    </security>

  </trustInfo>



  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">

    <application>

     



    </application>

  </compatibility>

</asmv1:assembly>

 

最後將此AppGenerate.exe加入執行Windows Store Application應用程式的專案中即可。


圖002



注意其Copy To Output Directorys設定值。

下面是WpfApplication1的程式碼列表。

 

WpfApplication1\MainWindow.xaml.cs

 


using Microsoft.Win32;

using System;

using System.Collections.Generic;

using System.Diagnostics;

using System.IO;

using System.Linq;

using System.Reflection;

using System.Runtime.CompilerServices;

using System.Runtime.InteropServices;

using System.Text;

using System.Threading.Tasks;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using Windows.ApplicationModel;

using Windows.Management.Deployment;

using Windows.System;



namespace WpfApplication1

{



    public enum ActivateOptions

    {

        None = 0x00000000,  // No flags set

        DesignMode = 0x00000001,  // The application is being activated for design mode, and thus will not be able to

        // to create an immersive window. Window creation must be done by design tools which

        // load the necessary components by communicating with a designer-specified service on

        // the site chain established on the activation manager.  The splash screen normally

        // shown when an application is activated will also not appear.  Most activations

        // will not use this flag.

        NoErrorUI = 0x00000002,  // Do not show an error dialog if the app fails to activate.                               

        NoSplashScreen = 0x00000004,  // Do not show the splash screen when activating the app.

    }



    [ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

    interface IApplicationActivationManager

    {

        // Activates the specified immersive application for the "Launch" contract, passing the provided arguments

        // string into the application.  Callers can obtain the process Id of the application instance fulfilling this contract.

        IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);

        IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);

        IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);

    }



    [ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]//Application Activation Manager

    class ApplicationActivationManager : IApplicationActivationManager

    {

        [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]

        public extern IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);

        [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]

        public extern IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);

        [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]

        public extern IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);

    }







    /// 

    /// Interaction logic for MainWindow.xaml

    /// 

    public partial class MainWindow : Window

    {

        private Dictionary _allList = new Dictionary();



        public MainWindow()

        {

            InitializeComponent();

        }



        private void Button_Click(object sender, RoutedEventArgs e)

        {

            Process p = new Process();

            p.StartInfo = new ProcessStartInfo("AppsGenerate.exe");

            p.Start();

            p.WaitForExit();

            using (FileStream fs = new FileStream("list.dat", FileMode.Open, FileAccess.Read))

            {

                using(StreamReader sr = new StreamReader(fs))

                {

                    while (sr.Peek() != -1)

                    {

                        string[] data = sr.ReadLine().Split(',');

                        try

                        {

                            _allList.Add(data[0], data[1]);

                        }

                        catch (Exception)

                        {

                        }

                    }

                }

                lst1.ItemsSource = _allList;

                lst1.DisplayMemberPath = "Key";

            }



        }



        private void Button_Click_1(object sender, RoutedEventArgs e)

        {

            if (lst1.SelectedIndex != -1)

            {

                string id = ((KeyValuePair)lst1.SelectedItem).Value;

                RegistryKey reg = Registry.CurrentUser.OpenSubKey("Software\\Classes\\ActivatableClasses\\Package\\"+id+"\\Server");

                if (reg != null)

                {

                    bool found = false;

                    foreach (var item in reg.GetSubKeyNames())

                    {

                        if (item.StartsWith("Appex"))

                        {

                            string modelId = (string)reg.OpenSubKey(item).GetValue("AppUserModelId");

                            ApplicationActivationManager appActiveManager = new ApplicationActivationManager();//Class not registered

                            uint pid;

                            appActiveManager.ActivateApplication(modelId, null, ActivateOptions.None, out pid);



                            found = true;

                        }

                    }

                    if (!found && reg.GetSubKeyNames().Length > 0)

                    {

                            string modelId = (string)reg.OpenSubKey(reg.GetSubKeyNames()[0]).GetValue("AppUserModelId");

                            ApplicationActivationManager appActiveManager = new ApplicationActivationManager();//Class not registered

                            uint pid;

                            appActiveManager.ActivateApplication(modelId, null, ActivateOptions.None, out pid);

                            found = true;

                    }

                }



            }

        }

    }

}





執行畫面如下:

 

圖003