DART Setup Wizard Doesn’t Detect Installed ADK

Trying to create a DART recovery image, got the message during the installation from Microsoft Desktop Optimization Pack 2015 running installer from \DaRT\DaRT 10\Installers\en-us\x64\MSDart100.msi

However, the latest Windows ADK + Windows PE ADK component has been installed. Suspected the issue was a specific version is required, but the download link in the setup is a dead link and just takes you to a generic Microsoft page.

We could look for components not found either through Windows Installer logging, or ProcMon, but here want to demonstrate some ways to analyze how the installer is making the checks.

We can check with ORCA how the ADK installation check is occurring.

Opening the installation MSI in Orca we can set a condition that will prevent the DaRTRecoveryImage feature from installing.;

We could just remove the condition, however was curious how the check actually ocurred…

In Custom Action we can see DetectAdk action

In Binary view we can extract this item by clicking the [Binary Data] and Write Binary to Filename to save the item to disk. Typically these will be a DLL or a Script.

As this is a 32-bit DLL we can test calling this custom action with 32-bit PowerShell

$code = @'
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace NativeMethods
{
    public static class CustomActionRoutines
    {
        [DllImport("msi.dll", ExactSpelling=true)]
        public static extern IntPtr MsiCreateRecord(uint cParams);
        
        [DllImport("SetupCommonDLLCmp2.dll", SetLastError = true)]
        public static extern uint DetectAdk(IntPtr hMsiHandle);

        [DllImport("msi.dll", ExactSpelling=true)]
        public static extern uint MsiCloseHandle(IntPtr hAny);
     }
}
'@
Add-Type -TypeDefinition $code

$msiHandle = [NativeMethods.CustomActionRoutines]::MsiCreateRecord(0)
[NativeMethods.CustomActionRoutines]::DetectAdk($msiHandle)
[void][NativeMethods.CustomActionRoutines]::MsiCloseHandle($msiHandle)

When we run this while monitoring with Process Monitor we can see it triggers creating a process with the following command line:

rundll32.exe “C:\WINDOWS\SYSTEM32\SetupCommonDLLCmp2.dll”,zzzzInvokeManagedCustomActionOutOfProc SfxCA_5457953 7 Microsoft.Dart.MuCustomActions!Microsoft.Dart.CustomActions.ADKCustomActions.DetectAdk

We can see this extracts a number of files, which are deleted straight after being created. Using 7-zip was able to extract the files from the DLL so we could analyze them:

These DLLs are .NET assemblies, in Microsoft.Dart.MuCustomActions.dll we find with a .NET decompiler a class Microsoft.Dart.CustomActions.ADK.CustomActions with the following code:

// Decompiled with JetBrains decompiler
// Type: Microsoft.Dart.CustomActions.ADKCustomActions
// Assembly: Microsoft.Dart.MuCustomActions, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// MVID: BB93085A-8824-4EAB-996B-D904BBE67B8D
// Assembly location: D:\reverse\SetupCommonDLLCmp2\Microsoft.Dart.MuCustomActions.dll

using Microsoft.Dart.Commands.Api;
using Microsoft.Deployment.WindowsInstaller;

namespace Microsoft.Dart.CustomActions
{
  public static class ADKCustomActions
  {
    [CustomAction]
    public static ActionResult DetectAdk(Session session)
    {
      if (!WindowsAdk.IsValidAdkPath())
        return ActionResult.Failure;
      session["WINDOWSKITSINSTALLED"] = "1";
      return ActionResult.Success;
    }
  }
}

This references the following ADK related queries:

// Decompiled with JetBrains decompiler
// Type: Microsoft.Dart.Commands.Api.WindowsAdk
// Assembly: Microsoft.Dart.MuCustomActions, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// MVID: BB93085A-8824-4EAB-996B-D904BBE67B8D
// Assembly location: D:\reverse\SetupCommonDLLCmp2\Microsoft.Dart.MuCustomActions.dll

using Microsoft.Win32;
using System;
using System.IO;

namespace Microsoft.Dart.Commands.Api
{
  internal static class WindowsAdk
  {
    private static string adkPath;
    private static string bootFilesPathx64;
    private static string bootFilesPathx86;
    private static string oscdImagePathx64;
    private static string oscdImagePathx86;
    private static string imagePathx64;
    private static string imagePathx86;
    private static string optionalComponentPathx64;
    private static string optionalComponentPathx86;

    public static string AdkPath
    {
      get
      {
        if (WindowsAdk.adkPath == null)
        {
          string str = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Windows Kits\\10\\");
          RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots");
          if (registryKey != null)
            WindowsAdk.adkPath = registryKey.GetValue("KitsRoot10", (object) null) as string;
          if (WindowsAdk.adkPath == null)
            WindowsAdk.adkPath = str;
        }
        return WindowsAdk.adkPath;
      }
      internal set => WindowsAdk.adkPath = value;
    }

    public static bool IsValidAdkPath() => Directory.Exists(WindowsAdk.BootFilesPathx64) && Directory.Exists(WindowsAdk.BootFilesPathx86) && Directory.Exists(WindowsAdk.OscdImagePathx64) && Directory.Exists(WindowsAdk.OscdImagePathx86) && Directory.Exists(WindowsAdk.ImagePathx64) && Directory.Exists(WindowsAdk.ImagePathx86) && Directory.Exists(WindowsAdk.OptionalComponentPathx64) && Directory.Exists(WindowsAdk.OptionalComponentPathx86);

    public static string BootFilesPathx64
    {
      get
      {
        if (WindowsAdk.bootFilesPathx64 == null)
          WindowsAdk.bootFilesPathx64 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\amd64\\Media");
        return WindowsAdk.bootFilesPathx64;
      }
      internal set => WindowsAdk.bootFilesPathx64 = value;
    }

    public static string BootFilesPathx86
    {
      get
      {
        if (WindowsAdk.bootFilesPathx86 == null)
          WindowsAdk.bootFilesPathx86 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\x86\\Media");
        return WindowsAdk.bootFilesPathx86;
      }
      internal set => WindowsAdk.bootFilesPathx86 = value;
    }

    public static string OscdImagePathx64
    {
      get
      {
        if (WindowsAdk.oscdImagePathx64 == null)
          WindowsAdk.oscdImagePathx64 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Deployment Tools\\amd64\\Oscdimg");
        return WindowsAdk.oscdImagePathx64;
      }
      internal set => WindowsAdk.oscdImagePathx64 = value;
    }

    public static string OscdImagePathx86
    {
      get
      {
        if (WindowsAdk.oscdImagePathx86 == null)
          WindowsAdk.oscdImagePathx86 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Deployment Tools\\x86\\Oscdimg");
        return WindowsAdk.oscdImagePathx86;
      }
      internal set => WindowsAdk.oscdImagePathx86 = value;
    }

    public static string ImagePathx64
    {
      get
      {
        if (WindowsAdk.imagePathx64 == null)
          WindowsAdk.imagePathx64 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\amd64\\en-us");
        return WindowsAdk.imagePathx64;
      }
      internal set => WindowsAdk.imagePathx64 = value;
    }

    public static string ImagePathx86
    {
      get
      {
        if (WindowsAdk.imagePathx86 == null)
          WindowsAdk.imagePathx86 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\x86\\en-us");
        return WindowsAdk.imagePathx86;
      }
      internal set => WindowsAdk.imagePathx86 = value;
    }

    public static string OptionalComponentPathx64
    {
      get
      {
        if (WindowsAdk.optionalComponentPathx64 == null)
          WindowsAdk.optionalComponentPathx64 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\amd64\\WinPE_OCs");
        return WindowsAdk.optionalComponentPathx64;
      }
      internal set => WindowsAdk.optionalComponentPathx64 = value;
    }

    public static string OptionalComponentPathx86
    {
      get
      {
        if (WindowsAdk.optionalComponentPathx86 == null)
          WindowsAdk.optionalComponentPathx86 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\x86\\WinPE_OCs");
        return WindowsAdk.optionalComponentPathx86;
      }
      internal set => WindowsAdk.optionalComponentPathx86 = value;
    }

    public static string GetWinPeOcsPath(Architecture architecture)
    {
      switch (architecture)
      {
        case Architecture.X86:
          return WindowsAdk.OptionalComponentPathx86;
        case Architecture.X64:
          return WindowsAdk.OptionalComponentPathx64;
        default:
          return string.Empty;
      }
    }
  }
}

From reviewing this we can see that all of the following paths must exist for ADK to be detected as “installed”

WindowsAdk.BootFilesPathx64
WindowsAdk.BootFilesPathx86
WindowsAdk.OscdImagePathx64
WindowsAdk.OscdImagePathx86
WindowsAdk.ImagePathx64
WindowsAdk.ImagePathx86
WindowsAdk.OptionalComponentPathx64
WindowsAdk.OptionalComponentPathx86

We could test the logic with this PowerShell code:

Add-Type -Path "Microsoft.Dart.MuCustomActions.dll"
Add-Type -Path "Microsoft.Deployment.WindowsInstaller.dll"
[Microsoft.Dart.CustomActions.ADKCustomActions]::DetectAdk($null)

This reports “Failure” as expected. However it doesn’t clearly show which of the paths missing is triggering the issue. We can break the logic of the check into some pure PowerShell and show as the result per path:

class WindowsAdk
{
    [string]$adkPath
    [string]$bootFilesPathx64
    [string]$bootFilesPathx86
    [string]$oscdImagePathx64
    [string]$oscdImagePathx86
    [string]$imagePathx64
    [string]$imagePathx86
    [string]$optionalComponentPathx64
    [string]$optionalComponentPathx86
}

$WindowsADK = New-Object WindowsADK
$WindowsADK.adkPath = [System.IO.Path]::Combine([Environment]::GetFolderPath("ProgramFilesX86"), "Windows Kits\10\")
$key = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry32).OpenSubKey("SOFTWARE\Microsoft\\Windows Kits\Installed Roots")
if ($key -ne $null)
{
    $WindowsADK.adkPath = $key.GetValue("KitsRoot10")
}


$windowsADK.bootFilesPathx64 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\Media")
$windowsADK.bootFilesPathx86 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Windows Preinstallation Environment\x86\Media")
$windowsADK.oscdImagePathx64 = [System.IO.Path]::Combine($WindowsADK.adkPath,"Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg")
$windowsADK.oscdImagePathx86 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Deployment Tools\x86\Oscdimg")
$windowsADK.imagePathx64 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\en-us")
$windowsADK.imagePathx86 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Windows Preinstallation Environment\x86\en-us")
$windowsADK.optionalComponentPathx64 = [System.IO.Path]::Combine($WindowsADK.adkPath,"Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs")
$windowsADK.optionalComponentPathx86 = [System.IO.Path]::Combine($WindowsADK.adkPath,"Assessment and Deployment Kit\Windows Preinstallation Environment\x86\WinPE_OCs")

"Windows ADK Path : $(Test-Path $WindowsADK.adkPath)"
"x64 Boot Files Path : $(Test-Path $WindowsADK.bootFilesPathx64)"
"x86 Boot Files Path : $(Test-Path $WindowsADK.bootFilesPathx86)"
"x64 OSCD Files Path : $(Test-Path $WindowsADK.oscdImagePathx64)"
"x86 OSCD Files Path : $(Test-Path $WindowsADK.oscdImagePathx86)"
"x64 Image Path : $(Test-Path $WindowsADK.imagePathx64)"
"x86 Image Path : $(Test-Path $WindowsADK.imagePathx86)"
"x64 Optional Components Path : $(Test-Path $WindowsADK.optionalComponentPathx64)"
"x86 Optional Components Path : $(Test-Path $WindowsADK.optionalComponentPathx86)"

On my machine this output the following:

So we ran these two commands to fix the missing folders. I didn’t want to create any 32-bit images so expected these missing shouldn’t be a problem:

mkdir "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\x86\Media"
mkdir "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\x86\WinPE_OCs"
mkdir "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\x86\en-us"

Now the installer works, and recovery images are generated fine…

About chentiangemalc

specializes in end-user computing technologies. disclaimer 1) use at your own risk. test any solution in your environment. if you do not understand the impact/consequences of what you're doing please stop, and ask advice from somebody who does. 2) views are my own at the time of posting and do not necessarily represent my current view or the view of my employer and family members/relatives. 3) over the years Microsoft/Citrix/VMWare have given me a few free shirts, pens, paper notebooks/etc. despite these gifts i will try to remain unbiased.
This entry was posted in Uncategorized. Bookmark the permalink.

3 Responses to DART Setup Wizard Doesn’t Detect Installed ADK

  1. Lorenzo says:

    Thanks! Great work!

  2. Juan says:

    Amazing, thank you!

  3. Richard Romero says:

    It makes sense since 32-bit DaRT compatibility ended with Windows 10 2004.

    Thanks for this work :D

Leave a comment