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