一般來說,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
	
