Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to programmatically check the applicability rules of a Windows update?

By exploring the contents of a Windows update .msu file (for example, using a tool such as 7zip), one may find, among others, a series of files that define prerequisites and applicability rules. For example:

<UpdateIdentity UpdateID="E6CF1350-C01B-414D-A61F-263D14D133B4" RevisionNumber="1" /><Properties UpdateType="Category" /><ApplicabilityRules><IsInstalled><True /></IsInstalled></ApplicabilityRules>
....
<UpdateIdentity UpdateID="2bf7ed9c-6f43-493a-b156-db20f08c44c4" RevisionNumber="101" /><Properties UpdateType="Detectoid" /><Relationships /><ApplicabilityRules><IsInstalled><b.RegSz Key="HKEY_LOCAL_MACHINE" Subkey="SYSTEM\CurrentControlSet\Control\Nls\Language" Value="InstallLanguage" Comparison="EqualTo" Data="0409" /></IsInstalled></ApplicabilityRules>
....
<UpdateIdentity UpdateID="6AECE9A4-19E3-4BC7-A20C-070A5E31AFF4" RevisionNumber="100" /><Properties UpdateType="Detectoid" /><Relationships>
...
<UpdateIdentity UpdateID="3B4B8621-726E-43A6-B43B-37D07EC7019F" /><ApplicabilityRules><IsInstalled><b.WmiQuery Namespace="root\cimv2" WqlQuery="SELECT Manufacturer FROM Win32_ComputerSystem WHERE Manufacturer = 'Samsung Electronics' or Manufacturer = 'Hewlett-Packard' or Manufacturer = 'Gateway'" /></IsInstalled></ApplicabilityRules>
...

Now, given a certain .msu file and my local computer, is there a way to iterate over those rules and find out if one is not satisfied - and which one?

Can I use WSUS 3.0 Class Library for this purpose? Or is there a tool / script?

What I actually want is to know precisely what condition made a computer to reject a certain Windows update (KB2973201) with the message The update is not applicable to your computer (the error code behind this is WU_E_NOT_APPLICABLE).

It seems to be too little documentation regarding these applicability rules of an update. Are there any good sources?

References:

  • How the Windows Update Agent determines the status of an update
  • A bit of WSUS stuff on msdn
  • Windows Update error code list
like image 864
turdus-merula Avatar asked Apr 24 '15 09:04

turdus-merula


2 Answers

Now, given a certain .msu file and my local computer, is there a way to iterate over those rules and find out if one is not satisfied - and which one?
Can I use WSUS 3.0 Class Library for this purpose? Or is there a tool / script?

You can Update Applicability Rules via the WSUS 3.0 Class Library though it doesn't offer functionality to check if the rules will pass, unless (I guess) you run the installer but that doesn't tell you which one failed.

Simon mentioned the WUAPI library doesn't expose the inner rules and (afaik) there is no way to match the WUAPI ResultCodes to the ApplicabilityRules that fail.

And unfortunately librarys like Microsoft.Deployment.WindowsInstaller.dll dont work with MSU files so we're out of luck with the "off-the-shelf" options. Therefore you have to do it manually with code and the (msu.xml) XML file:

<Updates>
  <UpdateIdentity UpdateID="E6CF1350-C01B-414D-A61F-263D14D133B4" RevisionNumber="1" />
  <Properties UpdateType="Category" />
  <ApplicabilityRules>
    <IsInstalled>
      <True />
    </IsInstalled>
  </ApplicabilityRules>
  <UpdateIdentity UpdateID="2bf7ed9c-6f43-493a-b156-db20f08c44c4" RevisionNumber="101" />
  <Properties UpdateType="Detectoid" />
  <Relationships />
  <ApplicabilityRules>
    <IsInstalled>
      <b.RegSz Key="HKEY_LOCAL_MACHINE" Subkey="SYSTEM\CurrentControlSet\Control\Nls\Language" Value="InstallLanguage" Comparison="EqualTo" Data="0409" />
    </IsInstalled>
  </ApplicabilityRules>
  <UpdateIdentity UpdateID="6AECE9A4-19E3-4BC7-A20C-070A5E31AFF4" RevisionNumber="100" />
  <Properties UpdateType="Detectoid" />
  <Relationships></Relationships>
  <UpdateIdentity UpdateID="3B4B8621-726E-43A6-B43B-37D07EC7019F" />
  <ApplicabilityRules>
    <IsInstalled>
      <b.WmiQuery Namespace="root\cimv2" WqlQuery="SELECT Manufacturer FROM Win32_ComputerSystem WHERE Manufacturer = 'Dell Inc.' or Manufacturer = 'Samsung Electronics' or Manufacturer = 'Hewlett-Packard' or Manufacturer = 'Gateway'" />
    </IsInstalled>
  </ApplicabilityRules>
</Updates>

Use this code to see which ApplicabilityRules fail:

private void btnWillPassApplicabilityRules_Click(object sender, EventArgs e)
{
    XDocument doc = XDocument.Load("msu.xml");
    var elements = doc.Element("Updates").Elements("ApplicabilityRules").Elements("IsInstalled").Elements();

    foreach (var element in elements) {
        if (element.ToString().StartsWith("<b.RegSz")) {
            string subKeyName = element.Attribute("Subkey").Value;
            string keyName = element.Attribute("Value").Value;
            string keyValue = element.Attribute("Data").Value;

            //TODO: Leave the Registry Hive "Switch()" upto reader to fully implement
            if (!ValueExistsInRegistry(Registry.LocalMachine, subKeyName, keyName, keyValue)) {
                Console.WriteLine("Install is not applicable as Applicability Rule failed: " + element.ToString());
            }
        }
        else if (element.ToString().StartsWith("<b.WmiQuery")) {
            string nameSpace = element.Attribute("Namespace").Value;
            string wqlQuery = element.Attribute("WqlQuery").Value;
            if (!ValueExistsInWMI(nameSpace, wqlQuery)) {
                Console.WriteLine("Install is not applicable as Applicability Rule failed: " + element.ToString());
            }
        }
    }
}

private bool ValueExistsInRegistry(RegistryKey root, string subKeyName, string keyName, string keyValue)
{
    using (RegistryKey key = root.OpenSubKey(subKeyName)) {
        if (key != null) return keyValue == key.GetValue(keyName).ToString();
    }
    return false;
}

private bool ValueExistsInWMI(string nameSpace, string wqlQuery)
{
    ManagementScope scope = new ManagementScope(String.Format("\\\\{0}\\" + nameSpace, "."), null);  //The "." is for your local PC
    scope.Connect();
    ObjectQuery query = new ObjectQuery(wqlQuery);
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
    if (searcher.Get().Count == 0) {
        return false;
    }
    else {
        return true;
    }
    return false;
}
}

Before you run the Applicability Rules its best to first check if the update will pass the Operating System (OS) and Service Pack (SP) Applicability test. There is no point checking registry/wmi etc to determine if an upgrade will pass the rules if its not applicable to the OS and SP.

To see the ApplicabilityInfo, run the expand command line utility:

expand -f:* "C:\temp\msu\Windows6.1-KB2973201-x64.msu" "C:\temp\msu"

This will create the following files:

  • WSUSSCAN.cab
  • Windows6.1-KB2973201-x64.cab
  • Windows6.1-KB2973201-x64.xml
  • Windows6.1-KB2973201-x64-pkgProperties.txt

The xml and txt files take about 5 seconds to be created. Open the pkgProperties.txt file and the top line has the info:

ApplicabilityInfo="Windows 7.0 Client SP1;Windows 7.0 Server Core SP1;Windows 7.0 Embedded SP1;Windows 7.0 Server SP1;Windows 7.0 WinPE 3.1;"

MSDN Ref: Description of the Windows Update Standalone Installer in Windows

like image 83
Jeremy Thompson Avatar answered Oct 26 '22 09:10

Jeremy Thompson


You can use the Windows Update Agent API to query installed updates (it has a lot of information in fact), something like this:

  // in .NET, you need to add a reference
  // to the WUAPI COM component located in \windows\system32\wuapi.dll
  // to be able to access the WUAPI object model
  UpdateSearcher searcher = new UpdateSearcher();
  searcher.Online = false; // you can remove this line if you allow the API to get online to search
  var res = searcher.Search("IsInstalled=0"); // search not installed update
  foreach (IUpdate update in res.Updates)
  {
      Console.WriteLine("update:" + update.Title);

      // get history information
      // this can return nothing for example it it was hidden by end user
      // note we use update's identity and rev number here for matching a specific update
      var histories = searcher.QueryHistory(0, searcher.GetTotalHistoryCount()).OfType<IUpdateHistoryEntry>().Where(
          h => h.UpdateIdentity.UpdateID == update.Identity.UpdateID && h.UpdateIdentity.RevisionNumber == update.Identity.RevisionNumber);
      foreach (var history in histories)
      {
          Console.WriteLine(" code:" + history.ResultCode);
          Console.WriteLine(" hr:0x" + history.HResult.ToString("X8"));
      }
  }

However, that will not tell you what were the inner rules (registry/wmi, etc.) used to determine if the updagres were installed. This is not exposed by the WUAPI.

like image 22
Simon Mourier Avatar answered Oct 26 '22 11:10

Simon Mourier