With Windows 10 running on hundreds of millions of devices, the Windows Store has become a trustworthy source for applications. By bringing established solutions to the Windows Store, we can merge the best of two worlds.

Using the Desktop Bridge, we could bring Hogia Lön into the Universal Windows Platform (UWP) space and make it eligible for the Windows Store.


“Our Hogia Lön developers were really impressed by how easy it was to integrate Desktop Bridge into our build-and-release pipeline.”

— Oskar Börjesson, Development Manager, Hogia


This allows for Hogia Lön to enjoy many features, such as the UWP setup authoring, malware protection, automatic updates, easy distribution, and more—all without adding a single line of code to the existing code base.

In addition, this laid the foundation for Hogia Lön to take advantage of modern features available in Windows 10.

Key technologies used:

Core team:

  • Oskar Börjesson – Development Manager, Hogia
  • Elisabet Everbäck – Technical Lead Hogia Lön, Hogia
  • Malin Samuelsson – Product Owner Hogia Lön, Hogia
  • Elena Kamras – Developer Hogia Lön, Hogia
  • Nina Gustafsson – Developer Hogia Lön, Hogia
  • Anders Thun – Principal Technical Evangelist, Microsoft
  • Simon Jäger – Technical Evangelist, Microsoft

Customer profile

The Hogia Group comprises 27 companies in Scandinavia and the United Kingdom with a total of 600 employees. With software as a common denominator, the Hogia Group currently operates in three business areas: finance and business systems, human resource systems, and transport systems.

The Hogia Group’s entire development and operation has always been completely self-financed. No external capital has ever been introduced (apart from the family´s original 5,000 SEK), and no shares in the company have ever changed owner. The owners say they have no intention of selling the company, listing it on the stock exchange, or taking in external capital in the future.

The company has a very healthy balance sheet and no loans. The Hogia Group experienced its 25th consecutive year of profit in 2015 and Hogia AB is rated AAA by Soliditet (Dun & Bradstreet’s Nordic partner). The Hogia Group’s annual turnover currently stands at € 60 million, increasing at a rate of 5-10 percent every year.

In 1987 Hogia AB was appointed and has since been purveyor to the king of Sweden.

hogia logo

Hogia Lön

Hogia Lön is a solution to help businesses with their financial responsibilities. It helps to automatically calculate holiday pay, employer contributions, and taxes. It’s fast and efficient, and users can add several useful plug-ins or software.

Hogia Lön allows users to create and send many types of reports, such as salary specifications, payment lists, accounting instructions, employer declarations, holiday debt lists, and statistics.

It enables users to do calculations in the reports while grouping and summarizing according to their own preferences. Users can choose how information is sorted: by name, employee number, number, salary type, and so on.

Problem statement

Before the existence of the Desktop Bridge, putting a sophisticated solution such as Hogia Lön onto the UWP ecosystem would require an immense amount of resources. Not only would you have to reengineer and port the existing code base, but also its dependencies and installers. It’s an expensive investment. But using the Desktop App Converter, we can bring solutions to the UWP ecosystem with ease.

Hogia Lön is a highly sophisticated solution, combining multiple types of executables and dependencies. This included binaries built with Visual Basic 6, unmanaged and managed (.NET) COM components, and a dependency on previous frameworks/runtimes to support the bits.

In this case, we initially faced a challenge with the installer because we couldn’t run it in an unattended (silent) mode, required by the Desktop App Converter. Instead of reengineering the current installer, we constructed the Windows app package manually by supplying the required resources and modifications.

This required us to gain a complete understanding of the installation process, while handling the registration of different types of dependencies.

The solution also had a dependency on .NET Framework 3.5 (CLR 2.0), which is not supported by the Desktop App Converter itself. The solution does, however, work with common language runtime (CLR) 4.0. We could configure the application to run with this without adjusting the existing code base. Additionally, the Assembly Registration tool (RegAsm) needed to register the COM-exposed .NET dependencies does not work with CLR 2.0 dependencies.

Without a silent installer, we could still leverage the Desktop App Converter for capturing the COM registration information (by supplying the Desktop App Converter with a batch file performing regsvr32 on every COM component in use by the application). However, the Desktop App Converter triggers the compatibility layer subsystem WOW64 (because the Desktop App Converter itself requires a 64-bit system). This subsystem creates a 32-bit environment for the solution to run within, unmodified, on a 64-bit system. This in turn impacts how the registry hive is formed, because the keys will be mapped with the distinct architecture. This rendered the registry hive output from the Desktop App Converter incompatible with 32-bit Windows systems, something that had to be supported in our case.

You can read more about the WOW64 subsystem here: Running 32-bit Applications

The reason this became a problem is because the solution uses COM components, which rely heavily on the registry. The registry contains much of the important information that COM uses. For 32-bit systems, to even begin locating the COM dependencies, the registry needed to be formed in such a way that the 32-bit system can do so, constructed without the interference of the WOW64 subsystem.

Hogia Lön is heavily used by users running 32-bit systems, which made this issue an important one.

Another consideration that arose was the matter of dealing with existing (traditional MSI-based) installations of the solution. We had to provide the ability to remove the existing installation to allow for a better experience and less confusion.

The challenges

The challenges for bringing Hogia Lön to the UWP ecosystem were summarized as such:

  • Migrating the users (uninstall existing software).
  • No unattended (silent) mode on the installer.
  • WOW64 subsystem interference (using the Desktop App Converter—rendering a 64-bit-only Windows app package).
  • Unmanaged dependencies (COM).
  • Dependencies (COM) based on .NET Framework 3.5 (CLR 2.0).

Once everything was put in place and the Windows app package was created, we had the foundation for a trusted and simplified distribution mechanism (and automatic updates)—the Windows Store. In addition, we could invest in UWP features to further enhance the experience—as expected by Windows 10 users—such as mapping existing shortcuts, wherever they may be exposed within the Windows experience (for example, Start menu, taskbar, desktop).

Solution, steps, and delivery

Most of the work was put into building the Windows app package because the existing installer didn’t have an unattended (silent) mode. We were still able to use the Desktop App Converter to create the needed structure for the Windows app package, while supplying the final assets manually.

This included feeding the Desktop App Converter with what we could—generating registry entities and extending the registry hive, creating the virtual file system, and finally generating and signing the Windows app package.

The entire procedure can be illustrated as follows:

Diagram of the workflow


Later, we automated the process of creating the registry hive, based on the knowledge gained from the previous work. This utility flow can be illustrated as follows:

Diagram of the workflow


Using process monitor

Without an unattended (silent) mode for the installer, we needed to completely understand the operations performed. This included (but was not limited to) registry manipulation and file system activates.

Luckily, there’s a great monitoring tool available, Process Monitor. You can download Process Monitor here. This tool allows you to view activities by a process in the registry and file system, as they happen.

Screenshot of Process Monitor


Throughout this project, we relied heavily on this tool to detail the activities. We used this information, once gathered, to assemble the registry hive and file structure for the Windows app package.

Creating Registry.dat

Many existing (non-UWP) desktop apps tend to read and write from the registry. This isn’t something embraced by regular UWP apps, but is something that the Desktop Bridge had to support for converted apps, allowing developers to not reengineer their solutions before converting.

This begins when running the Desktop App Converter. The Desktop App Converter keeps track of the registry activities and creates a registry hive (Registry.dat), which is included in the Windows app package. During runtime, this registry hive is loaded and merged with the system registry.

This means the app will still have the registry available, with writes directed (virtualized) toward the local registry hive, rendering the system unimpacted after an installation/uninstallation of the app.

In our case, we first had to build the registry hive without using the installer and the Desktop App Converter (lack of unattended mode). Instead, we captured all the registry activities using a combination of the Desktop App Converter, RegAsm (with the /regFile switch) and Process Monitor to create the registry hive in a few steps. This allowed us to also create a registry hive for a 32-bit architecture, something we couldn’t do using the Desktop App Converter alone (due to the WOW64 subsystem being triggered).

Initial process

We began by creating a batch file, performing the same registration of unmanaged COM DLLs/OCXs as the installer. This was done using the command-line utility regsvr32:

regsvr32 /s [PATH TO DEPENDENCY].OCX
regsvr32 /s [PATH TO DEPENDENCY].DLL
...


This batch file was fed into the Desktop App Converter (as the installer), creating a Windows app package manifest, file structure, and the registry hive.

DesktopAppConverter -Installer [PATH TO BATCH FILE] -Destination [OUTPUT PATH] -PackageName "Hogia Lön" -Publisher "CN=Hogia Group" -Version 1.0.0.0 -MakeAppx -Verbose


To finish the registry hive, we needed to register the COM-exposed .NET dependencies. We used the Assembly Registration tool, RegAsm, to produce registry files for the assemblies:

regasm /regfile:[OUTPUT PATH] [PATH TO DEPENDENCY].DLL
...


The output was then merged with the registry hive. This was done by loading it into the Registry Editor (using the Load Hive feature) and importing the registry files into the registry hive.

Automating the flow

To automate and streamline this process, we needed to get away from the manual steps described earlier, so we decided to build a utility that is able to construct a registry hive with both unmanaged COM DLLs/OCXs and COM-exposed .NET dependencies registered for the UWP app. This work extended the insights gained from using Process Monitor and registering the dependencies using the various tools. The app was written in a combination of C# and C++.

In addition, this could also tie into a build process, automating the process further.

The utility we built is available here: Registry Capture Helper

The steps performed by the utility can be broken down into the following:

  1. Load a JSON file with pointers to all of the required dependencies for the app.
  2. Create the registry hive and basic structure.
  3. Load the registry hive and map predefined registry keys.
  4. Register unmanaged COM DLLs/OCXs.
  5. Patch hard-coded paths resulting from the previous step.
  6. Register COM-exposed .NET dependencies.
  7. Unload the registry hive.

The JSON file contains the pointers to the dependencies needed by the UWP app:

{
  "DllDirectory": "[DIRECTORY PATH]",
  "NativeCOMDLLs": [
    "[FILE PATH]"
  ],
  "ComAssemblies": [
    "[FILE PATH]"
  ]
}


The registry hive was then created (as ActiveHive.dat) from a registry hive template (TemplateHive.dat), which contains the basic structure. It is then loaded and prepared by the utility as follows:

REGISTRYCAPTUREHELPER_API BOOL __stdcall StartCapture(void)
{
  LSTATUS lRet;

  DebugOut(L"StartCapture...");
  auto fRet = SetPrivileges();
  if (!fRet) goto err0;

  CoInitialize(NULL);

  lRet = RegLoadKey(HKEY_LOCAL_MACHINE, hiveName, L"ActiveHive.dat");
  if (lRet != ERROR_SUCCESS) { DebugOut(L"RegLoadKey failed = %d", lRet); goto err0; }

  lRet = RegOpenKey(HKEY_LOCAL_MACHINE, hiveName, &hkHive);
  if (lRet != ERROR_SUCCESS) { DebugOut(L"RegOpenKey failed = %d", lRet); goto err0; }

  lRet = RegCreateKey(hkHive, L"REGISTRY\\MACHINE", &hkRedirMachine);
  if (lRet != ERROR_SUCCESS) { DebugOut(L"RegCreateKey(LM) failed = %d", lRet); goto err0; }

  lRet = RegCreateKey(hkHive, L"REGISTRY\\MACHINE\\SOFTWARE\\Classes", &hkRedirClassesRoot);
  if (lRet != ERROR_SUCCESS) { DebugOut(L"RegCreateKey(CR) failed = %d", lRet); goto err0; }

  lRet = RegCreateKey(hkHive, L"REGISTRY\\USER", &hkRedirCurrentUser);
  if (lRet != ERROR_SUCCESS) { DebugOut(L"RegCreateKey(CU) failed = %d", lRet); goto err0; }

  lRet = RegOverridePredefKey(HKEY_LOCAL_MACHINE, hkRedirMachine);
  if (lRet != ERROR_SUCCESS) { DebugOut(L"RegOverridePredefKey(HKLM) failed = %d", lRet); goto err0; }

  lRet = RegOverridePredefKey(HKEY_CLASSES_ROOT, hkRedirClassesRoot);
  if (lRet != ERROR_SUCCESS) { DebugOut(L"RegOverridePredefKey(HKCR) failed = %d", lRet); goto err0; }

  lRet = RegOverridePredefKey(HKEY_CURRENT_USER, hkRedirCurrentUser);
  if (lRet != ERROR_SUCCESS) { DebugOut(L"RegOverridePredefKey(HKCU) failed = %d", lRet); goto err0; }

  return TRUE;
err0:
  return FALSE;
}


Once the registry hive had been created and prepared, the unmanaged COM DLLs/OCXs would then be registered:

REGISTRYCAPTUREHELPER_API BOOL __stdcall RegisterNativeCOMDll(LPCWSTR dllFileName)
{
  BOOL ret = TRUE;
  DebugOut(L"Registering COM component - %s", dllFileName);
  auto hMod = LoadLibrary(dllFileName);
  if (hMod == NULL) { DebugOut(L"LoadLibrary failed = %d", GetLastError()); ret = FALSE; goto err0; }
  DebugOut(L"\tDLL loaded");

  auto DllRegisterServer = (pfnDllRegisterReserver)GetProcAddress(hMod, "DllRegisterServer");
  if (DllRegisterServer == NULL) { DebugOut(L"GetProcAddress failed = %d", GetLastError()); ret = FALSE;  goto err1; }
  DebugOut(L"\tEntry point retrieved");

  auto dwRet = DllRegisterServer();
  if (dwRet != ERROR_SUCCESS) {
    DebugOut(L"\tDllRegisterServer failed = %X", dwRet);
  }
err1:
  FreeLibrary(hMod);
err0:
  return ret;
}


Once the unmanaged COM DLLs/OCXs are registered, they need to be patched because they reference the dependency files using an absolute path. The dependencies need to be referenced relative to the Windows app package for the UWP app to locate them. This is done using the [{AppVPackageRoot}] prefix in the path value.

We did this by traversing the keys (for example, HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID ...\InprocServer32 and HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID ...\ToolboxBitmap32) created by the registration of the unmanaged COM DLLs/OCXs. Once located, the value is patched.

void PatchRegValuePath(const HKEY hKehyParent, const HKEY hKeySub, TCHAR achKey[255], LPCWSTR achValueName, BOOL fLookForHelpdir)
{
  LSTATUS retCode;
  DWORD dwType, cbData;
  TCHAR  achValue[MAX_VALUE_NAME];

  if (fLookForHelpdir && wcscmp(achKey, L"HELPDIR") == 0) {
    retCode = RegQueryValueEx(hKeySub, NULL, NULL, &dwType, (LPBYTE)achValue, &cbData);
    if (retCode == ERROR_SUCCESS) {
      std::wstring directory(achValue);
      directory += L"\\";
      std::wstring newPath;
      if (wcscmp(achValue, g_DllDirectory) == 0) {
        newPath = std::wstring(L"[{AppVPackageRoot}]Dependencies\\");
        RegSetValueEx(hKeySub, NULL, 0, REG_SZ, (LPBYTE)(newPath.data()), newPath.size() * sizeof(wchar_t));
      } else if (wcscmp(directory.data(), g_DllDirectory) == 0) {
        newPath = std::wstring(L"[{AppVPackageRoot}]\\Dependencies");
      }
      if (newPath.size() > 0)
        RegSetValueEx(hKeySub, NULL, 0, REG_SZ, (LPBYTE)(newPath.data()), newPath.size() * sizeof(wchar_t));
    }
  } else if (wcscmp(achKey, achValueName) == 0) {
    retCode = RegQueryValueEx(hKeySub, NULL, NULL, &dwType, (LPBYTE)achValue, &cbData);
    if (retCode == ERROR_SUCCESS) {
      std::wstring dllFullPath(achValue);
      auto newPath = std::wstring(L"[{AppVPackageRoot}]\\Dependencies\\");

      auto currentPath = std::experimental::filesystem::path(dllFullPath);
      auto dllFileName = currentPath.filename();
      newPath = newPath.append(dllFileName);

      RegSetValueEx(hKeySub, NULL, 0, REG_SZ, (LPBYTE)(newPath.data()), newPath.size() * sizeof(wchar_t));
    }
  } else {
    TraverseKey(hKeySub, fLookForHelpdir);
  }
}

void TraverseKey(HKEY hKey, BOOL fLookForHelpdir)
{
  TCHAR    achKey[MAX_KEY_LENGTH];   // buffer for subkey name
  DWORD    cbName;                   // size of name string 
  TCHAR    achClass[MAX_PATH] = TEXT("");  // buffer for class name 
  DWORD    cchClassName = MAX_PATH;  // size of class string 
  DWORD    cSubKeys = 0;               // number of subkeys 
  DWORD    cbMaxSubKey;              // longest subkey size 
  DWORD    cchMaxClass;              // longest class string 
  DWORD    cValues;              // number of values for key 
  DWORD    cchMaxValue;          // longest value name 
  DWORD    cbMaxValueData;       // longest value data 
  DWORD    cbSecurityDescriptor; // size of security descriptor 
  FILETIME ftLastWriteTime;      // last write time 

  DWORD i, retCode;

  // Get the class name and the value count. 
  retCode = RegQueryInfoKey(
    hKey,                    // key handle 
    achClass,                // buffer for class name 
    &cchClassName,           // size of class string 
    NULL,                    // reserved 
    &cSubKeys,               // number of subkeys 
    &cbMaxSubKey,            // longest subkey size 
    &cchMaxClass,            // longest class string 
    &cValues,                // number of values for this key 
    &cchMaxValue,            // longest value name 
    &cbMaxValueData,         // longest value data 
    &cbSecurityDescriptor,   // security descriptor 
    &ftLastWriteTime);       // last write time 

  // Enumerate the subkeys, until RegEnumKeyEx fails.
  if (cSubKeys) {
    DebugOut(L"Number of subkeys: %d", cSubKeys);

    for (i = 0; i<cSubKeys; i++) {
      cbName = MAX_KEY_LENGTH;
      retCode = RegEnumKeyEx(hKey, i, achKey, &cbName, NULL, NULL, NULL, &ftLastWriteTime);
      if (retCode == ERROR_SUCCESS) {
        HKEY hKeySub;
        DebugOut(L"(%d) %s", i + 1, achKey);
        retCode = RegOpenKey(hKey, achKey, &hKeySub);
        if (retCode != ERROR_SUCCESS) {
          DebugOut(L"RegOpenKey failed = %d", retCode);
        } else {
          PatchRegValuePath(hKey, hKeySub, achKey, L"InprocServer32", fLookForHelpdir);
          PatchRegValuePath(hKey, hKeySub, achKey, L"ToolboxBitmap32", fLookForHelpdir);
          PatchRegValuePath(hKey, hKeySub, achKey, L"win32", fLookForHelpdir);
          PatchRegValuePath(hKey, hKeySub, achKey, L"HELPDIR", fLookForHelpdir);
          RegCloseKey(hKeySub);
        }
      }
    }
  }
}

REGISTRYCAPTUREHELPER_API BOOL __stdcall PatchNativeCOMDllPaths(LPCWSTR DllDirectory)
{
  HKEY hkCLSID, hkTypeLib;
  g_DllDirectory = DllDirectory;

  LSTATUS lRet = RegOpenKey(hkHive, L"REGISTRY\\MACHINE\\SOFTWARE\\Classes\\CLSID", &hkCLSID);
  if (lRet != ERROR_SUCCESS) {
    DebugOut(L"RegCreateKey(Classes\\CLSID) failed = %d", lRet);
  } else {
    TraverseKey(hkCLSID, FALSE);
    RegCloseKey(hkCLSID);
  }

  lRet = RegOpenKey(hkHive, L"REGISTRY\\MACHINE\\SOFTWARE\\Classes\\TypeLib", &hkTypeLib);
  if (lRet != ERROR_SUCCESS) {
    DebugOut(L"RegCreateKey(Classes\\TypeLib) failed = %d", lRet);
  } else {
    TraverseKey(hkTypeLib, TRUE);
    RegCloseKey(hkTypeLib);
  }
  return TRUE;
}


Finally, the COM-exposed .NET dependencies were registered using the RegistrationServices class:

private static void RegisterCOMAssemblies(ComRegistration comReg)
{
    RegistrationServices RegistrationServices = new RegistrationServices();

    foreach (var fileName in comReg.ComAssemblies) 
    {
        RegisterCOMAssembly(RegistrationServices, comReg.DllDirectory + fileName);
    }
}

private static void RegisterCOMAssembly(RegistrationServices regSvcs, string assemblyFileName)
{
    var asm = Assembly.UnsafeLoadFrom(assemblyFileName);
    regSvcs.RegisterAssembly(asm, AssemblyRegistrationFlags.None);
}


This completed the registry hive. It was now ready to be included in the Windows app package as Registry.dat.

Existing installations (user transition)

Many solutions that might be under consideration for use of the Desktop Bridge and Desktop App Converter may already have been distributed and installed in a traditional sense. This may not be a problem for every solution. But we chose to make sure that existing users of Hogia Lön would not be impacted or confused by multiple installations.

We achieved this by building an executable that would identify existing solutions. This executable would then perform the uninstallation before starting the UWP app.

The executable was created as follows:

static void Main(string[] args)
{
    // Get the value of the uninstall registry key.
    var uninstallString = (string)Registry.GetValue(@"...", "UninstallString", null);

    if (uninstallString != null)
    {
        var uninstallMessage = "We recommend that you uninstall the old version of " +
            "this program to get a better experience. Would you like to uninstall the " +
            "old version of this program now?";
        var uninstallResult = MessageBox.Show(uninstallMessage, 
            "Uninstall the old version of this program", MessageBoxButtons.YesNo);

        // If the "Yes" button was pressed, launch the uninstallation process (blocked).
        if (uninstallResult.Equals(DialogResult.Yes))
        {
            // Once the process has finished, check the result.
            if (RunProcess(uninstallString, true) != 0)
            {
                // TODO: Developer can choose to block the app, or handle
                // the failure here.
            }
        }
    }

    // Start the UWP app.
    RunProcess(@"Shell:AppsFolder\WLPlusModern_qabskf98b4bwa!WLPlusModern");
}

private static int RunProcess(string appName, bool block = false)
{
    // Create and start the process.
    var process = new Process();
    process.StartInfo.FileName = appName;
    process.StartInfo.Arguments = null;
    process.StartInfo.CreateNoWindow = true;
    process.Start();

    // Wait for the process to finish.
    if (block)
    {
        process.WaitForExit();
        return process.ExitCode;
    }
    return 0;
}


You can read more about user transitions and other best practices at Desktop Bridge: Smooth User Transition and Data Migration.

We then used the above executable (WLPlusBootstrap.exe) as a bootstrapper for the UWP app. That means this will be the starting point, as defined in the Windows app package manifest:

<Application Id="WLPlusBootstrap" Executable="WLPlus\WLPlusBootstrap.exe" EntryPoint="Windows.FullTrustApplication">
  <uap:VisualElements
    DisplayName="Hogia Lön"
    Description="Hogia Lön"
    BackgroundColor="transparent"
    Square150x150Logo="Assets\Square150x150Logo.png"
    Square44x44Logo="Assets\Square44x44Logo.png">
  </uap:VisualElements>
</Application>


The executable (WLPlusBootstrap.exe) needs to run as full-trust (specified by Windows.FullTrustApplication in the EntryPoint of the Application) to launch the UWP app. This was done by requesting the runFullTrust capability in the Windows app package manifest.

<Package
  ...
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  ...

  <Capabilities>
    <rescap:Capability Name="runFullTrust" />
  </Capabilities>


You can read more about app extensions (for Desktop Bridge apps) here: Integrate your app with Windows 10 (Desktop Bridge)

Converting shortcuts

Users of a traditional desktop app are used to shortcuts. So is a user of a UWP app, similarly in the form of tiles on the Start screen/menu and other places. The trick is to know where the shortcuts are being put by the traditional installer because they can be anywhere from the Start menu to the taskbar to the desktop.

Once you’ve located the shortcuts, you can extend the Windows app package manifest with their location, thus migrating the shortcuts into the Windows 10 experience.

<Package
  ...
  xmlns:rescap3="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/3"
  IgnorableNamespaces="rescap3"
  ...

  <Extensions>
    <rescap3:Extension Category="windows.desktopAppMigration">
      <rescap3:DesktopAppMigration>
        <rescap3:DesktopApp ShortcutPath="%ProgramData%\Microsoft\Windows\Start Menu\Programs\Hogia\HR\Hogia Lön.lnk" />
        <rescap3:DesktopApp ShortcutPath="%APPDATA%\Microsoft\Windows\Internet Explorer\Quick Launch\User Pinned\TaskBar\Hogia Lön.lnk" />
        <rescap3:DesktopApp ShortcutPath="%PUBLIC%\Desktop\Hogia Lön.lnk"/>
      </rescap3:DesktopAppMigration>
    </rescap3:Extension>
  </Extensions>


You can read more about pointing existing Start tiles and taskbar buttons to your converted app here: Point existing Start tiles and taskbar buttons to your packaged app

Enabling .NET Framework 3.5

In our case, the solution had a dependency on .NET Framework 3.5 (CLR 2.0). This could be solved either by making sure that .NET Framework 3.5 is present on the machine, or by forcing the runtime activation policy to a more recent version.

This was done by working with the startup configuration file for the solution. This file controls settings and policies for the app.

Setting the useLegacyV2RuntimeActivationPolicy attribute to true (of the startup element) allows the solution to bind any legacy activation techniques to a specific runtime version, enabling the solution to run without a dependency on a .NET Framework 3.5 installation.

<configuration>
    <startup useLegacyV2RuntimeActivationPolicy="true">
        <supportedRuntime version="v4.0"/>
    </startup>
</configuration>


You can read more about this attribute here: <startup> Element

Creating the Windows app package

Following these previous efforts, we could now assemble the Windows app package. The required artifacts were the following:

  1. Assets folder (with logos)
  2. Virtual file system folder (VFS)
  3. Application folder (executables, dependencies, and more)
  4. Windows app package manifest
  5. Registry hive

We created the assets folder, application folder, and registry hive by pulling together the needed artifacts.

The virtual file system folder is a folder that is mounted during runtime and merged with the file system. This folder serves the purpose of virtualizing any files that the application places outside of its own folder (for example, C:\Windows\System32). We used Process Monitor to locate these files and build the correct structure.

Then we constructed the Windows app package manifest, which can be built in many ways—for instance, by creating a new UWP app or using a plain text editor (Application Manifests).

Finally, we created a batch file to create a Windows app package from the above artifacts using MakeAppx.exe (App packager (MakeAppx.exe)).

The Windows app package was then signed using SignTool.exe.

del [EXISTING WINDOWS APP PACKAGE FILE]

MakeAppx.exe pack /l /d [ARTIFACTS FOLDER] /p [NEW WINDOWS APP PACKAGE FILE]

SignTool.exe sign -f [CERTIFICATE FILE] -fd SHA256 -v [NEW WINDOWS APP PACKAGE FILE]

copy /Y [NEW WINDOWS APP PACKAGE FILE] [OUTPUT FOLDER]


This wraps the Windows app package, ready for distribution.

Conclusion

We were able to show that much can be achieved with the Desktop Bridge—without any adjustments to the solution (code base) and original installer itself (keeping the binaries). With an understanding of the solution and its dependencies, you can produce the needed artifacts to bring it into the UWP ecosystem, allowing for the use of many features that can be experienced right away (once converted): UWP setup authoring, malware protection, automatic updates, easy distribution, and more.

We had to keep in mind that this solution is already widely used. This meant taking care of multiple installations (offering uninstalls) and exposing shortcuts, making the experience smooth for existing users.

Going forward, the solution now has the foundation for the customer to expand further into the capabilities of the UWP space, such as leveraging functionality within WinRT and Windows 10 exclusive features (Live Tiles, notifications, and so on).

In addition, the customer now has a first release with insights into bringing other solutions into the UWP ecosystem. They can offer their solutions in a safe and trusted manner with the Windows Store, as expected by Windows 10 users.

The way we can bring new life into applications built with seasoned technology and allow for a gradual migration using UWP features is evidence that the Desktop Bridge really delivers on its promise.

Additional resources