Share via



July 2013

Volume 28 Number 7

Windows Phone - Create a Windows Phone 8 Company Hub App

By Tony Champion

Prior to the release of Windows Phone 8, companies had only a couple of options for deploying enterprise apps to their employees’ devices. They could release the apps to the Windows Phone Store and require user authentication, allowing the apps to be successfully deployed while securing their usage. Of course, the app would then be available in the store and available to the general public for download. The other approach was to use a mobile device management (MDM) solution to directly manage the devices. Microsoft currently offers two MDMs: Windows Intune and System Center 2012 Configuration Manager. While MDMs are still the preferred approach for many large-scale enterprises, they might not align with a company’s deployment goals.

Windows Phone 8 brings a new, unmanaged option for deploying enterprise apps that allows devices to install apps directly from an e-mail attachment or by downloading the app from a URL. This direct approach to deploying enterprise apps opens enterprise app development and deployment to more companies.

A drawback to this unmanaged approach is that installation and app management for the user is not altogether intuitive. When it comes to mobile devices, users expect to be able to install apps with the push of a button. Having to manually install apps from an e-mail or a Web site goes against the normal app experience and can cause uncertainty in users and training issues for the IT staff. Luckily, there’s a solution—a company hub app.

Windows Phone 8 includes some additions to the SDK that support this new concept. A company hub app gives the enterprise a mechanism for presenting and delivering apps to the user for installation. And it gives users a familiar experience to discover and install company apps.

Requirements for Company Hub Apps

Before you can publish a company hub app to your employees, there are a few administrative steps that need to be taken. The first step is to register for a company account on the Windows Phone Dev Center. The registration process is similar to a regular development account, but a company account goes through some additional account verification.

After creating an account, you must purchase an Enterprise Mobile Code Signing Certificate from Symantec Corp. The certificate-purchasing process requires a valid Symantec ID from the Dev Center, so this can only be completed after the company account has been established and verified. Once the certificate is purchased and installed on a machine, you’ll need to export the certificate in a PFX format that includes the private key. This certificate will be used for generating an application enrollment token (AET) and for signing any apps developed by the company. When installing the certificate on your development machine, it’s important to follow the steps outlined at bit.ly/1287H8j. Otherwise, you’ll end up with an incomplete PFX file that won’t validate correctly during deployment. Skipping these steps is a common headache for many developers.

An AET must be installed on a device before that device can install any apps developed by a company. By installing the AET, the device is enrolled in the previously established company account. The Windows Phone SDK 8.0 includes a tool, AETGenerator, that can be used to create the AET from the exported certificate. The generator creates three different forms of the AET: a raw version containing the AET in XML format (.xml), a Base64-encoded version used with an MDM such as Windows Intune or System Center 2012 Configuration Manager (.aet), and an XML format that can be installed directly on the device through e-mail or Internet Explorer (.aetx).

When deciding which method to use to distribute the AET and your company apps, there’s an important point to consider. When a device installs an AET, it’s valid until its expiration date, which by default is one year. Once the AET expires, the device won’t be able to run any apps signed and distributed by the company—including hub apps—until a new, valid AET is installed. This creates two items you need to add to your deployment strategy planning.

The first is how to handle the expiration of the AET. If you’re using an MDM to manage your devices, an updated AET can be published to the devices directly from the MDM. This will help to minimize the impact of the expiration of the original AET on the devices. If the AET is installed via an unmanaged process—e-mail or Internet Explorer—the new AET will need to be installed manually by the user. Because the user won’t be able to run any apps from the company, including the hub app, once the AET expires, it’s a good practice to create and distribute a new AET before the original expires. This will prevent the user from losing access to company apps.

The second item to consider is the removal of the AET as well as any company apps installed on the device. In the current Bring Your Own Device (BYOD) world, this can be a major consideration. Using an MDM gives the company complete control over its apps installed on a device. Apps and the AET can be remotely removed directly from the MDM. However, in an unmanaged deployment, this isn’t possible. Once an AET is added to the device, it’s valid and can’t be removed prior to its expiration. Similarly, the SDK doesn’t provide a way to remove an app from a device through code. The best practice for addressing this issue is requiring users to authenticate to all company apps. This enables you to prevent a user’s account from launching an app. While this isn’t the same as being able to remove the app, it does give you a way to manage the security of your app once it’s deployed to a device. 

After you’ve completed the initial steps, you’re ready to begin creating apps that can be deployed to your company’s employees without needing to go through the store. You’ll find a more complete look at creating a company account, obtaining an Enterprise Mobile Code Signing Certificate and generating an AET at bit.ly/SBN6Tf.

Preparing for Development

A company hub app requires a bit more setup than most Windows Phone apps. For one thing, it’s quite handy to have at least a few apps available to install and use as test cases within the app. While the apps themselves can be blank Windows Phone apps, they all have to have one thing in common: a Publisher ID.

If you’ve already created a Windows Phone app, you’re probably familiar with the Windows Phone app manifest file, which by default is the WMAppManifest.xml file located in the Properties folder of the solution. This file contains information Windows Phone and the store need to know about your app. Opening the WMAppManifest.xml file from within Visual Studio launches a designer to make it easier to maintain the file. The designer contains four tabs, one of which is the Packaging tab.

The Packaging tab provides information about the developer of the app, versioning info and supported languages. For the purposes of this article, the Version, Product ID and Publisher ID are the most important items on this tab. By default, the Windows Phone project templates generate a new GUID for the Product ID and Publisher ID when the project is created. When working with an app such as a company hub, the app will have visibility only into other apps that have the same Publisher ID it does. This Publisher ID is commonly set to the Publisher GUID that’s assigned to your developer account in the Dev Center. The Publisher GUID can be found on your Account Summary page in the Dev Center.

The downloadable file for this article contains seven solutions. The first six are named CDSAPP[1-6]. Each of these apps was created from the Windows Phone App project template and only has the app title on the main page and the Publisher ID modified. If you’re going to use these apps for your testing, it’s important to use the same Publisher ID for your company hub app or to change the app IDs to yours.

The Version and Product ID are two important pieces of information to know when creating a company hub solution. The Version allows you to determine when apps need to be upgraded on a device, and the Product ID is used for identifying the app.

The next thing to consider is your method of testing. Generally, you can do most of your testing on the Windows Phone Emulator. It does a great job and allows you to test most of the things you need to test during the development process. The difficulty with testing a company hub app is that your app needs to have additional apps installed on the emulator to test with. However, each time you launch a new instance of the emulator, it starts from a clean version of the OS. This means that anything you’ve previously installed for testing is no longer there.

There are two ways to approach this problem. The first is to leave the emulator running, install some test apps, and then do development of your company hub app while the emulator is still running. Of course, if the emulator gets restarted for any reason, you have to reinstall those apps to begin testing again.

The preferred approach is to do your development testing against a real device. This provides a more stable platform for your testing. If you’re going to do your testing on a live device, you’ll need to make sure the Publisher ID of that device corresponds to the Publisher ID of your account.

The Company Hub SDK

The Windows Phone SDK 8.0 includes two classes that are primarily responsible for managing and interacting with the company apps that are installed on the machine. While there are some supporting objects that you’ll be introduced to along the way, understanding these two classes is critical to your hub app.

The Package Class Each app installed on the device is represented by the Package class, which is located in the Windows.ApplicationModel namespace. The Package class contains a couple of important members that I’ll discuss here. The first is the Id property, which returns a PackageId class. The class contains most of the manifest information that was entered into the Packaging tab in the manifest designer, including the Product Id that was assigned to the app, the app name, publisher information and the current version.

The other important member of the Package class is the Launch method, which enables you to launch the app that the Package represents from the current app. Not only is this a great tool for constructing a company hub app, it can also be useful for other line-of-business (LOB) apps.

InstallationManager Class InstallationManager, in the Windows.Phone.Management.Deployment namespace, is the class responsible for installing packages on the device. This is accomplished through the AddPackageAsync method. A list of all apps from the same PublisherId as the current app, including the Package representing the current app, is returned by the FindPackagesFromCurrentPublisher method. In addition, the class can also be used to get the installation progress of any apps with the same PublisherId.

Building a Company Hub App

I’m going to introduce you to the core principles of developing a company hub app, using the demo shown in Figure 1. The demo contains a three-page panorama app that lists available company apps in three categories: apps that are currently not installed, apps with an available update and apps that have the latest version installed. In addition, the app will have a detail page that presents the information about the app and provides the commands to install the latest version and launch the app from within the hub. The working solution can be found as a downloadable resource from msdn.com/magazine/msdnmag0713.

The Company Hub App
Figure 1 The Company Hub App

What’s Missing For the sake of completeness, it’s important to point out a few items that aren’t included in the demo app but  would have to be created for any real-world solution. The first is the source of the available company apps. If you examine the CompanyPackage class, you’ll see it contains a GenerateData method. This method is used to fake the available company apps by generating a list of the installed company apps on the device, modifying some of the data and creating some fictional data, as well.

The second missing piece is a Web site to host the .xap files to be downloaded and installed on the device. For the company hub to be functional, it must be able to download the apps from some location. This Web solution would have to be created as well.

The CompanyPackage Class In order to represent the list of available apps to install on the device, the first thing to define is a CompanyPackage class that’s modeled after the Package class. The CompanyPackage class contains the display information and the location of the installation package:

public class CompanyPackage
{
  public string Id { get; set; }
  public string Name { get; set; }
  public string Description { get; set; }
  public string Thumbnail { get; set; }
  public string Version { get; set; }
  public Uri SourceUri { get; set; }
  public CompanyPackageStatus Status { get; set; }
}

The Status property is used to determine if the app is currently installed on the device and if there’s a newer version. CompanyPackageStatus is an enum that I’ll explore later in the article:

public enum CompanyPackageStatus
{
  Unknown,
  New,
  Update,
  Installed
};

Creating a View Model For this company hub app, you have to create a single view model, CompanyPackageViewModel, shown in Figure 2. The view model should have three properties that return an IEnumerable collection of CompanyPackage objects: NewPackage, UpdatePackages and InstalledPackages. The NewPackage property contains any apps that are available that aren’t currently installed on the machine. UpdatePackages represents any apps that are currently installed on the machine, but have a newer version available. Finally, InstalledPackages contains all of the apps that are currently installed and up-to-date on the device.

Figure 2 The CompanyPackage View Model

public class CompanyPackageViewModel : INotifyPropertyChanged
{
  private IEnumerable<CompanyPackage> _packages;
  public CompanyPackageViewModel()
  {
    LoadData();       
  }
  private void LoadData()
  {
    // Get list of packages and populate properties
    _packages = CompanyPackage.GenerateData();
    UpdatePackageStatus();
  }
  private IEnumerable<CompanyPackage> _newPackages;
  public IEnumerable<CompanyPackage> NewPackages
  {
    get
    {
      return _newPackages;
    }
  }
  private IEnumerable<CompanyPackage> _updatePackages;
  public IEnumerable<CompanyPackage> UpdatePackages
  {
    get
    {
      return _updatePackages;
    }
  }
  private IEnumerable<CompanyPackage> _installedPackages;
  public IEnumerable<CompanyPackage> InstalledPackages
  {
    get
    {
      return _installedPackages;
    }
  }
  public event PropertyChangedEventHandler PropertyChanged;
  private void NotifyPropertyChanged(String propertyName)
  {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (null != handler)
    {
      handler(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

Defining the Three Statuses Before you can populate the three different properties of the view model, you need to generate the list of available apps represented as a collection of CompanyPackages. In the view model shown in Figure 2, the data is loaded and populated in the LoadData method. The LoadData method pulls a list of test data and stores it in the private _packages variable. It then calls the UpdatePackageStatus method shown in Figure 3.

Figure 3 Method for Determining the Status of Each CompanyPackage

public void UpdatePackageStatus()
{
  var devicePkgs = 
    InstallationManager.FindPackagesForCurrentPublisher();
  foreach (var pkg in _packages)
  {
    var qry = devicePkgs.Where(p => p.Id.ProductId == pkg.Id);
    if (qry.Count() > 0)
    {
      var devicePkg = qry.First();
      var devicePkgVersion =
        PackageVersionHelper.VersionToString(devicePkg.Id.Version);
      pkg.Status = PackageVersionHelper.IsNewer(
        pkg.Version, devicePkgVersion) ?
        CompanyPackageStatus.Update : CompanyPackageStatus.Installed;
    }
    else
    {
      pkg.Status = CompanyPackageStatus.New;
    }
  }
  _newPackages = _packages.Where(
     p => p.Status == CompanyPackageStatus.New).ToList();
  _updatePackages = _packages.Where(
     p => p.Status == CompanyPackageStatus.Update).ToList();
  _installedPackages = _packages.Where(
     p => p.Status == CompanyPackageStatus.Installed).ToList();
  // Fire notifications for all properties
  NotifyPropertyChanged("NewPackages");
  NotifyPropertyChanged("UpdatePackages");
  NotifyPropertyChanged("InstalledPackages");
}

The UpdatePackageStatus method has two responsibilities: determine the current status of each available CompanyPackage class and then populate the three collection properties of the view model based on that status.

The Status property is determined by comparing each CompanyPackage against the apps that are currently installed on the device. A list of installed apps is obtained from the InstallationManager.FindPackagesForCurrentPublisher static method. If a Package object with the same Id as the CompanyPackage doesn’t exist, it’s marked with a “New” status.

If a Package with the same Id does exist, then the Package Version property is compared to the Version of the CompanyPackage. The PackageId Version property returns a PackageVersion struct. Unlike the System.Version class, this struct is missing a couple of features. The first feature is the ability to convert the struct to a string representation. If you call the ToString method, it returns the type name and not the actual version number. The second missing feature is the ability to compare two instances of PackageVersion to determine which one is newer.

Figure 4 shows a helper class that implements both of these missing features. The VersionToString method returns a proper string representation of a PackageVersion. The IsNewer method takes two string representations of a version number and determines if the newVersion argument is newer than the oldVersion argument. It accomplishes this by converting the strings to System.Version objects and using the available CompareTo method.

Figure 4 A Helper Class for PackageVersion

public static class PackageVersionHelper
{
  public static string VersionToString(PackageVersion version)
  {
    return String.Format("{0}.{1}.{2}.{3}",
                         version.Major,
                         version.Minor,
                         version.Build,
                         version.Revision);
  }
  public static bool IsNewer(string newVersion, 
    string oldVersion)
  {
    var newVer = Version.Parse(newVersion);
    var oldVer = Version.Parse(oldVersion);
    return newVer.CompareTo(oldVer) > 0;
  }
}

Once the UpdatePackageStatus method has calculated the Status property for each Company­Package object, it populates the three collection properties using LINQ queries. Finally, the view model raises the PropertyChanged event for each of the properties.

Displaying the List of Apps The three lists of available apps are displayed within a Panorama control with three Panorama­Items, each containing a LongList­Selector bound to one of the lists. Each uses the same DataTemplate for displaying a CompanyPackage, and the complete Panorama XAML can be found in Figure 5. In the downloadable project, you’ll see that the DataContext of the Panorama control inherits the DataContext of the parent PhoneApplicationPage, which is set to an instance of the CompanyPackageViewModel. You saw the result of this in Figure 1.

Figure 5 Panorama to Display the Available Apps

<phone:Panorama Title="my company hub">
  <phone:Panorama.Resources>
    <DataTemplate x:Key="listItemTemplate">
      <StackPanel Margin="0,-6,0,12" Orientation="Horizontal">
        <Image  Source="{Binding Thumbnail,
          Converter={StaticResource debugConv}}"/>
        <TextBlock Text="{Binding Name}" TextWrapping="Wrap"
          VerticalAlignment="Center"
          Style="{StaticResource PhoneTextExtraLargeStyle}"
          FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
      </StackPanel>
    </DataTemplate>
  </phone:Panorama.Resources>
  <!--Panorama New Apps-->
  <phone:PanoramaItem Header="New Apps">
    <!--Single line list with text wrapping-->
    <phone:LongListSelector Margin="0,0,-22,0"
      ItemsSource="{Binding NewPackages}"
      SelectionChanged="ItemSelected"
      ItemTemplate="{StaticResource listItemTemplate}"/>
  </phone:PanoramaItem>
  <!--Panorama Update Apps-->
  <phone:PanoramaItem Header="Update Apps">
    <!--Single line list with text wrapping-->
    <phone:LongListSelector Margin="0,0,-22,0"
      ItemsSource="{Binding UpdatePackages}"
      SelectionChanged="ItemSelected"
      temTemplate="{StaticResource listItemTemplate}"/>
  </phone:PanoramaItem>
  <!--Panorama Installed Apps-->
  <phone:PanoramaItem Header="Installed Apps">
    <!--Single line list with text wrapping-->
    <phone:LongListSelector Margin="0,0,-22,0"
      ItemsSource="{Binding InstalledPackages}"
      SelectionChanged="ItemSelected"
      ItemTemplate="{StaticResource listItemTemplate}"/>
  </phone:PanoramaItem>
</phone:Panorama>

The CompanyPackage Detail View

Each LongListSelector shares the same event handler for the SelectionChanged event, ItemSelected. The event handler uses the NavigationService to navigate to a detail PhoneApplicationPage, PackagePage, and passes in the Id of the CompanyPackage. Because the current page will be cached in the navigation, the SelectedItem of the LongListSelector is reset to null to insure proper event firing each time:

private void ItemSelected(object sender, 
  SelectionChangedEventArgs e)
{
  if (e.AddedItems.Count > 0 && e.AddedItems[0] != null)
  {
    var pkg = e.AddedItems[0] as CompanyPackage;
    NavigationService.Navigate(
      new Uri("/PackagePage.xaml?id=" + pkg.Id, 
      UriKind.Relative));
    (sender as LongListSelector).SelectedItem = null;
  }
}

Because the PackagePage class only receives the Id of the CompanyPackage to be displayed, it has to use that Id to find the appropriate object. This is done by adding a FindPackage method to the CompanyPackageViewModel:

public CompanyPackage FindPackage(string id)
{
  return _packages.Where(p => p.Id == id).FirstOrDefault();
}

The Windows Phone Panorama App project exposes a global view model by adding a ViewModel property to the App class. This project uses that property to expose its view model to the main and detail pages.

The PackagePage class overrides the OnNavigatedTo method to set its DataContext to the CompanyPackage matching the provided Id. It then calls an UpdateUI method that toggles the Visibility and Content of two buttons that are added to the screen based on the Status of the CompanyPackage. The results of each Status type can be seen in Figure 6.

The Different Detail Pages
Figure 6 The Different Detail Pages

The two buttons expose the two actions available within the company hub classes. The first is the ability to launch the app. If the app is currently installed on the device, the Launch button is visible. The event handler of the button finds the correct Package object that matches the current CompanyPackage and calls the Launch method:

private void btnLaunch_Click(
  object sender, RoutedEventArgs e)
{
  var pkg = DataContext as CompanyPackage;
  var devicePkgs = InstallationManager.
    FindPackagesForCurrentPublisher();
  var devicePkg = devicePkgs.Where(p =>
    p.Id.ProductId == pkg.Id).FirstOrDefault();
  if (devicePkg != null)
  {
    devicePkg.Launch("");
  }        
}

If the CompanyPackage Status is “New” or “Update,” an Install button is visible on the page. The event handler for this button attempts to install the latest version of the app from the Uri provided in the Source­Uri property of the CompanyPackage. This is accomplished with the InstallationManager.AddPackageAsync method. The same method is called whether the app is being updated or it’s a new install. This method can be very temperamental and you need to make sure to take care of any errors that are generated. Figure 7 shows the event handler and the installation process. If the app is installed successfully, the UpdatePackage­Status method of the CompanyPackageView­Model is called to update the status collections being displayed in the main page and the UpdateUI method of the page to update the page.

Figure 7 Install and Update Button Event Handler

private async void btnInstall_Click(object sender, RoutedEventArgs e)
{
  var pkg = DataContext as CompanyPackage;
  if (MessageBox.Show("Install " + pkg.Name + "?", "Install app",  
   MessageBoxButton.OKCancel) == MessageBoxResult.OK)
  {
    try
    {
      var result =
        await InstallationManager.AddPackageAsync(pkg.Name, pkg.SourceUri);
      if (result.InstallState ==
        Windows.Management.Deployment.PackageInstallState.Installed)
      {
        MessageBox.Show(pkg.Name + " was installed.");
        App.ViewModel.UpdatePackageStatus();
        UpdateUI();
      }
      else
      {
        MessageBox.Show("An error occurred during installation.");
      }
    }
    catch (Exception)
    {
      MessageBox.Show("An error occurred during installation.");
    }
  }
}

Next Steps

In this article I took a quick look at developing a company hub app while exposing the details necessary to create a more robust solution. Some of the details that were omitted for the sake of brevity can be found in the included download. However, it’s important to remember that this is just the beginning.

There are a lot of features that can be added to a company hub to provide great benefits to your users, such as taking advantage of live tiles to inform the user when new apps are available. You can create solutions that only expose certain apps to certain users based on their roles within the company. An app can provide additional functionality, such as company news and notifications. While the possibilities might not be limitless, there are more than enough to keep you busy for quite some time.


Tony Champion is president of Champion DS, is a Microsoft MVP, and active in the community as a speaker, blogger, and author. He maintains a blog at tonychampion.net and can be reached via e-mail at tony@tonychampion.net.

THANKS to the following technical expert for reviewing this article: Cliff Strom (Microsoft)