Lista evasiva de actualizaciones de Windows instaladas

¿Alguna vez te has preguntado por qué se genera una lista de actualizaciones de Windows instaladas? ¿Y a través de qué API conseguirlo? Intentaré dar respuestas a estas y otras preguntas emergentes en mi pequeño estudio.



Antecedentes o cómo comenzó todo.


Cada año se celebra una conferencia de jóvenes especialistas en nuestra empresa , donde cada participante puede resolver el problema de un departamento (se propone una lista de temas por adelantado). Y el departamento de SPAS (soporte de software y hardware) tenía la siguiente tarea, que me interesó, además de que me permitió volver a la programación nuevamente (desafortunadamente, en este momento trabajo en esta empresa como un simple operador de NPPS).

Anteriormente, por cada "TO" con la ayuda de WSUS, todas las actualizaciones lanzadas se extraían y distribuían a todas las máquinas. TSB (boletines de servicio técnico) también aparecían periódicamente, lo que indicaba que era necesario instalar las actualizaciones necesarias en forma de paquetes aislados. Como resultado, acumulamos actualizaciones que no se pueden rastrear en WSUS, pero solo se pueden ver a través del panel de control en la sección "Actualizaciones instaladas".



Esquema de actualización visual

Hay situaciones en las que la estación de trabajo o el servidor se "cuelga" y debe restaurarlo a partir de una imagen creada hace algún tiempo. Al recuperarse de una imagen, existe la posibilidad de que perdamos las actualizaciones que necesitamos (que vienen en forma de paquetes aislados) que se instalaron antes de que la máquina se bloqueara. Explicado lo más detallado posible, porque las aclaraciones ya serán un secreto comercial.

Es por eso que surgió la idea de crear un programa que pudiera extraer esta lista de actualizaciones (preferiblemente de forma remota a través de la red local), escribir en un archivo / base de datos, comparar la lista actual con una plantilla determinada y enviar un mensaje al sistema SCADA a través de uno de los protocolos: SNMP, OPC.

Como habrás adivinado por el título del artículo, ya tenía una tarea difícil para elegir el método de recuperación de la lista. Como de costumbre, decidí buscar el correcto en el motor de búsqueda, hice preguntas sobre recursos especializados ( uno , dos , por alguna razón, a stackoverflow en inglés no me gustó mi pregunta y tuve que eliminarlo), pero todas las respuestas no dieron el resultado deseado. Por lo tanto, tuve que resolverlo yo mismo, lo que se discutirá más adelante.

Comandos de la consola


Comencemos con uno simple y aprovechemos lo que Windows nos ofrece sin usar herramientas de terceros. Esto se puede hacer usando los siguientes comandos:

  • wmic qfe list
  • systeminfo
  • dism / online / get-packages
  • a través de PowerShell:

    • Obtener revisión
    • Get-SilWindowsUpdate (solo disponible en ediciones de servidor)
    • Get-WmiObject -Class win32_quickfixengineering - a través del acceso a la clase WMI win32_quickfixengineering (sobre WMI un poco más tarde)



Puede obtener la lista a través de la interfaz gráfica a través del elemento estándar del Panel de control "Agregar o quitar programas", pero no podemos copiar nada desde allí. Cada herramienta del panel de control está representada por un archivo .cpl en la carpeta Windows \ System. Los archivos .pl en la carpeta del sistema de Windows se descargan automáticamente cuando se inicia el panel de control. El archivo Appwiz.cpl es responsable del elemento del programa. Su análisis no condujo a nada.

La salida del comando de la consola se puede redirigir a un archivo y luego se puede analizar, pero esto es incorrecto, más una llamada al programa (de acuerdo con las reglas de SB, no funcionará) y no se trata de recibir la lista de forma remota. Por lo tanto, le sugiero que simplemente llame a los comandos, compare el número de actualizaciones en cada lista, con la lista a través del Panel de control y continúe nuestra investigación.

Formalmente, todos los métodos para obtener la lista de actualizaciones se pueden dividir en dos grupos: local y de red.

Metodos locales y de red para obtener información.

Todos los métodos se probaron en imágenes limpias del sistema (Windows 7, 8, Server 2012 R2) con actualizaciones integradas, después de cada actualización a través del Centro de actualizaciones de los servidores oficiales de Microsoft se realizó una verificación adicional. Detengámonos en cada uno de ellos con más detalle.

WUA


WUApi (API del Agente de Windows Update): uso de la API del Agente de Windows Update. La opción más obvia, cuyo nombre habla por sí mismo. Usaremos la biblioteca Wuapi.dll para esto.
Nota: en lo sucesivo, para mi comodidad, incrustaré todos los resultados en la Lista. Esto puede no ser racional, pero me pareció una buena idea.
Ejemplo de implementación
using WUApiLib; public static List<string> listUpdateHistory() { //WUApi List<string> result = new List<string>(200); try { UpdateSession uSession = new UpdateSession(); IUpdateSearcher uSearcher = uSession.CreateUpdateSearcher(); uSearcher.Online = false; ISearchResult sResult = uSearcher.Search("IsInstalled=1 And IsHidden=0"); string sw = "   WUApi: " + sResult.Updates.Count; result.Add(sw); foreach (WUApiLib.IUpdate update in sResult.Updates) { result.Add(update.Title); } } catch (Exception ex) { result.Add("-   : " + ex.Message); } return result; } 


Hay una segunda variación de este método: Sesión de actualización : recibir información conectándose a la sesión de actualización del Agente de Windows Update (en este caso, no trabajamos directamente con la biblioteca).

Ejemplo de implementación
 public static List<string> Sessionlist(string pc) { List<string> result = new List<string>(50); //    object sess = null; object search = null; object coll = null; try { sess = Activator.CreateInstance(Type.GetTypeFromProgID("Microsoft.Update.Session", pc)); search = (sess as dynamic).CreateUpdateSearcher(); int n = (search as dynamic).GetTotalHistoryCount(); int kol = 0; //coll = (search as dynamic).QueryHistory(1, n); coll = (search as dynamic).QueryHistory(0, n); result.Add("  Update.Session: " + n); foreach (dynamic item in coll as dynamic) { if (item.Operation == 1) result.Add(item.Title); kol++; //Console.WriteLine(": " + kol); } result.Add("  : " + kol); } catch (Exception ex) { result.Add("-   : " + ex.Message); } finally { if (sess != null) Marshal.ReleaseComObject(sess); if (search != null) Marshal.ReleaseComObject(search); if (coll != null) Marshal.ReleaseComObject(coll); } return result; } 


Microsoft sugiere el uso remoto de la API .

Las principales desventajas de estos dos métodos son que no le permiten encontrar correcciones de KB que no se distribuyen a través de Windows Update. Solo puede ver lo que pasó por el agente de actualización en sí, es decir, esta opción no nos conviene.

DISM


Deployment Image Servicing and Management es una herramienta de línea de comandos que se puede utilizar para dar servicio a una imagen de Windows o para preparar una imagen de un entorno de preinstalación de Windows (Windows PE). Es un reemplazo para Package Manager (Pkgmgr.exe), PEimg e Intlcfg.

Esta utilidad se utiliza para integrar actualizaciones, paquetes de servicio en la imagen del sistema. Las actualizaciones de Windows son módulos separados que se pueden presentar de varias maneras:

  • Archivos .cab (gabinete) - archivos. Diseñado para distribución e instalación utilizando módulos de Windows Update en modo automatizado;
  • Archivos .msu (paquete independiente de Microsoft Update): archivos ejecutables. Diseñado para su distribución e instalación por los propios usuarios en modo manual a través del catálogo de actualizaciones de Microsoft. De hecho, son un conjunto empaquetado que consta de archivos .cab-, .xml, .txt.

El comando anteriormente mencionado dism / online / get-packages muestra información básica sobre todos los paquetes en la imagen de wim / sistema actual. Microsoft se ha ocupado de nosotros y proporciona paquetes NuGet para un uso conveniente de la API.

Ejemplo de implementación
 using Microsoft.Dism; public static List<string> DISMlist() { List<string> result = new List<string>(220); try { DismApi.Initialize(DismLogLevel.LogErrors); var dismsession = DismApi.OpenOnlineSession(); var listupdate = DismApi.GetPackages(dismsession); int ab = listupdate.Count; //Console.WriteLine("   DISM: " + ab); string sw = "   DISM: " + ab; result.Add(sw); foreach (DismPackage feature in listupdate) { result.Add(feature.PackageName); //result.Add($"[ ] {feature.PackageName}"); //result.Add($"[ ] {feature.InstallTime}"); //result.Add($"[ ] {feature.ReleaseType}"); } } catch (Exception ex) { result.Add("-   : " + ex.Message); } return result; } 


El número de actualizaciones coincidió con el número de la lista del Panel de control hasta la primera actualización a través del centro de control; después de eso, el número de actualizaciones disminuyó (fueron 214, se convirtió en 209), aunque lógicamente deberían haber aumentado. Ejemplos de salida Antes de actualizar , Después de actualizar .

Cuál es la razón de esto, solo puedo especular: quizás algunas actualizaciones reemplazaron a las anteriores, por lo tanto, el número se volvió menos.

Un poco más tarde, encontré una utilidad del DISM ++ chino, que no se basa en la API DISM o la API DISM Core, pero las bibliotecas que tiene no tienen los métodos que necesito abrir, por lo que abandoné esta idea y continué buscando más.

WSUS


Windows Server Update Services ( WSUS ) es un servidor para actualizar sistemas operativos y productos de Microsoft. El servidor de actualización se sincroniza con el sitio web de Microsoft, descargando actualizaciones que pueden distribuirse dentro de la LAN corporativa. Una vez más, una herramienta especial diseñada para trabajar con actualizaciones.

Distribuido solo en las ediciones del servidor de Windows, por lo que se implementó el siguiente soporte:

  • el sistema principal es Windows Server 2016;
  • y a través del sistema de virtualización Hyper-V, se implementaron dos sistemas operativos cliente:
    • Windows 8.1
    • Windows 7


Todos los sistemas están conectados a una única red de área local virtual, pero sin acceso a Internet .

Algunos consejos
Para no asignar una partición de disco duro para el nuevo sistema, uso WinNTSetup e instalo el sistema en discos VHD: el cargador de arranque, que comienza con Windows 7 (ediciones Professional / Ultimate), se adapta perfectamente al arranque desde una imagen de disco. Los discos así obtenidos se pueden usar de forma segura en Hyper-V: matas dos pájaros de un tiro a la vez. Recuerde hacer una copia del repositorio BCD por adelantado a través del comando bcdedit / export e: \ bcd_backup.bcd .

No quería configurar AD para la distribución de actualizaciones, por lo tanto, simplemente registré la ruta al servidor WSUS en las políticas de grupo:

Configuraciones

Asegúrese de prestar atención al puerto, debido a un error tipográfico (8350 en lugar de 8530) no pude recibir actualizaciones en las máquinas cliente, aunque todo se hizo correctamente. Además, los nombres de los elementos en las políticas de grupo en Windows 7 y Windows 8 son diferentes.

Para recibir el informe utilizando WSUS, debe instalar adicionalmente el paquete; el sistema se lo notificará.

Y ahora un pequeño código
 //      using Microsoft.UpdateServices.Administration; public static List<string> GetWSUSlist(params string[] list) { List<string> result = new List<string>(200); //    string namehost = list[0]; // ,     string = "example1"; string servername = list[1]; //  string = "WIN-E1U41FA6E55"; string Username = list[2]; string Password = list[3]; try { ComputerTargetScope scope = new ComputerTargetScope(); IUpdateServer server = AdminProxy.GetUpdateServer(servername, false, 8530); ComputerTargetCollection targets = server.GetComputerTargets(scope); // Search targets = server.SearchComputerTargets(namehost); // To get only on server FindTarget method IComputerTarget target = FindTarget(targets, namehost); result.Add(" : " + target.FullDomainName); IUpdateSummary summary = target.GetUpdateInstallationSummary(); UpdateScope _updateScope = new UpdateScope(); // See in UpdateInstallationStates all other properties criteria //_updateScope.IncludedInstallationStates = UpdateInstallationStates.Downloaded; UpdateInstallationInfoCollection updatesInfo = target.GetUpdateInstallationInfoPerUpdate(_updateScope); int updateCount = updatesInfo.Count; result.Add(" -   - " + updateCount); foreach (IUpdateInstallationInfo updateInfo in updatesInfo) { result.Add(updateInfo.GetUpdate().Title); } } catch (Exception ex) { result.Add("-   : " + ex.Message); } return result; } public static IComputerTarget FindTarget(ComputerTargetCollection coll, string computername) { foreach (IComputerTarget target in coll) { if (target.FullDomainName.Contains(computername.ToLower())) return target; } return null; } 


Como no hay Internet, la situación con las actualizaciones aparece como en la siguiente captura de pantalla:



El comportamiento es similar a WUApi: si las actualizaciones no han pasado por ellos, entonces no lo saben. Por lo tanto, este método no funciona nuevamente.

Wmi


El Instrumental de administración de Windows ( WMI ) en traducción literal es un kit de herramientas de administración de Windows.

WMI es un estándar implementado por Microsoft para administrar una empresa a través de Internet para la administración centralizada y el monitoreo de varias partes de una infraestructura informática que ejecuta una plataforma Windows. WMI es un sistema abierto unificado de interfaces de acceso a cualquier parámetro del sistema operativo, dispositivos y aplicaciones que operan en él.

Este método le permite recibir datos tanto de la máquina local como de forma remota dentro de la red local. Para acceder a los objetos WMI, se utiliza un lenguaje de consulta WMI (WQL) específico, que es una de las variedades de SQL. Recibiremos la lista a través de la clase WMI win32_quickfixengineering .

Ejemplo de implementación
 using System.Management; public static List<string> GetWMIlist(params string[] list) { List<string> result = new List<string>(200); //    ManagementScope Scope; string ComputerName = list[0]; string Username = list[1]; string Password = list[2]; int kol = 0; if (!ComputerName.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { //    ,      //  . ConnectionOptions Conn = new ConnectionOptions(); Conn.Username = Username; Conn.Password = Password; //      «NTLMDOMAIN:»  NTLM  ,       NTLM. Conn.Authority = "ntlmdomain:DOMAIN"; Scope = new ManagementScope(String.Format("\\\\{0}\\root\\CIMV2", ComputerName), Conn); } else Scope = new ManagementScope(String.Format("\\\\{0}\\root\\CIMV2", ComputerName), null); try { Scope.Connect(); ObjectQuery Query = new ObjectQuery("SELECT * FROM Win32_QuickFixEngineering"); ManagementObjectSearcher Searcher = new ManagementObjectSearcher(Scope, Query); foreach (ManagementObject WmiObject in Searcher.Get()) { result.Add(WmiObject["HotFixID"].ToString()); //Console.WriteLine("{0,-35} {1,-40}", "HotFixID", WmiObject["HotFixID"]);// String //result.Add(); /*result.Add("{0,-17} {1}", " : ", WmiObject["Description"]); result.Add("{0,-17} {1}", ": ", WmiObject["Caption"]); result.Add("{0,-17} {1}", " : ", WmiObject["InstalledOn"]);*/ kol++; } result.Add("  " + kol); } catch (Exception ex) { result.Add("-   : " + ex.Message); } return result; } 


Cuantitativamente, todo coincide (incluso después de las actualizaciones), por lo que se decidió utilizar este método. Para la creación programática de solicitudes WMI, le aconsejo que use la siguiente utilidad: WMI Delphi Code Creator . Gracias a ella, miré mi código un poco diferente y decidí usar un espacio en blanco de este programa.

XML


Los datos obtenidos por el método WMI no me detuvieron, y decidí "ingeniería inversa de superficie". Utilizaremos la utilidad Process Monitor de la colección de software Sysinternals Suite para identificar los archivos y las ramas de registro que se usan al llamar a los comandos de la consola enumerados anteriormente y acceder al elemento "Actualizaciones instaladas" a través del Panel de control.

Me llamó la atención el archivo wuindex.xml ubicado en la carpeta C: \ Windows \ servicing \ Packages \. Para analizarlo, se escribió el siguiente programa:

Ejemplo de aplicación de consola
 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; using System.Text.RegularExpressions; using System.IO; namespace XMLviewer { class Program { static void Main(string[] args) { string writePath = AppDomain.CurrentDomain.BaseDirectory + "XML  " + Environment.MachineName + ".txt"; if (!File.Exists(writePath)) { Console.WriteLine("  txt "); } else { Console.WriteLine(" XML .txt ,   "); File.Delete(writePath); } //      KB Regex regex = new Regex(@"KB[0-9]{6,7}"); //Regex(@"(\w{2}\d{6,7}) ?"); //SortedSet    ,     ""     SortedSet<string> spisok = new SortedSet<string>(); XmlDocument xDoc = new XmlDocument(); string path = "C:\\Windows\\servicing\\Packages\\wuindex.xml"; //   xml xDoc.Load(path); int kol = 0; //-  int total = 0; //-    xml int total2 = 0; //-   XmlNodeList name = xDoc.GetElementsByTagName("Mappings"); foreach (XmlNode xnode in name) { //Console.WriteLine(xnode.Name); kol++; XmlNode attr = xnode.Attributes.GetNamedItem("UpdateId"); //Console.WriteLine(attr.Value); foreach (XmlNode childnode in xnode.ChildNodes) { XmlNode childattr = childnode.Attributes.GetNamedItem("Package"); total++; //Console.WriteLine(childattr.Value); MatchCollection matches = regex.Matches(childattr.Value); if (matches.Count > 0) { foreach (Match match in matches) //Console.WriteLine(match.Value); spisok.Add(match.Value); } else { //Console.WriteLine("  "); } } } try { StreamWriter sw = new StreamWriter(writePath); foreach (string element in spisok) { //Console.WriteLine(element); sw.WriteLine(element); total2++; } sw.Close(); } catch (Exception ex) { Console.WriteLine(": " + ex.Message); } //Console.WriteLine("\n"); Console.WriteLine(" : " +kol); Console.WriteLine("    xml: " + total); Console.WriteLine(" KB : " + total2); Console.WriteLine("    ."); Console.Read(); } } } 


Desafortunadamente, este archivo no se encuentra en todos los sistemas y el principio de su generación y actualización ha sido un misterio para mí. Por lo tanto, nuevamente este método no nos conviene.

Cbs


Aquí llegamos a lo que todos estos métodos están asociados. Continuando con el análisis de los registros de Process Monitor, identifiqué las siguientes carpetas y archivos.

El archivo DataStore.edb ubicado en la carpeta C: \ Windows \ SoftwareDistribution \ DataStore . Esta es una base de datos que contiene el historial de todas las actualizaciones de la versión instalada de Windows, incluidas las que solo están en cola.

El programa ESEDatabaseView se utilizó para analizar el archivo DataStore.edb. Hay una tabla tbUpdates en la base de datos, cuyo contenido es difícil de interpretar.

Tabla TbUpdates en ESEDatabaseView

Después de llamar mi atención sobre el proceso TiWorker.exe , que se llamaba cada vez que abría un elemento en el Panel de control. Él "caminó" a través de muchas carpetas, una de las cuales me llevó por el camino correcto.

C: \ Windows \ SoftwareDistribution es una carpeta utilizada por Windows Update para descargar actualizaciones a una computadora e instalarlas, y también almacena información sobre todas las actualizaciones instaladas previamente.

Carpeta WinSxS ubicada en C: \ Windows \ winsxs . Esta es la carpeta de servicio del sistema operativo Windows utilizada para almacenar versiones instaladas previamente de componentes del sistema. Debido a su presencia, es posible volver a una versión anterior de la actualización si es necesario.

C: \ Windows \ servicing : el componente principal de todo el sistema, cuyo nombre es Servicio basado en componentes (CBS) .

CBS es un servicio basado en componentes que forma parte de Windows y está integrado con el servicio de actualización de Windows. A diferencia del servicio de mantenimiento basado en archivos (FBS) (para sistemas operativos anteriores a Windows Vista), en el que los archivos se actualizaban directamente en los directorios del sistema, CBS introdujo una jerarquía completa de directorios y una familia completa (pila) de módulos / bibliotecas de servicios.

CbsApi.dll es la biblioteca principal de soporte de tecnología CBS. No tiene métodos abiertos, por lo que no podría usarlo directamente. Microsoft usa TrustedInstaller.exe y TiWorker.exe para acceder a los métodos de esta biblioteca y ya a través de estos procesos muestra los datos que necesitamos. Los registros se mantienen en C: \ Windows \ Logs \ CBS \ CBS.log .

En el momento de la creación del prototipo del programa (puede ver mayo de 2019 en las capturas de pantalla), no había información en ruso sobre CBS, pero a fines de agosto había un muy buen artículo de blog: http://datadump.ru/component-based-servicing . Un artículo muy interesante que confirmó mi experiencia y recopiló la información necesaria. Y más sobre el tema: http://www.outsidethebox.ms/17988/

Conclusión


Microsoft ha complicado demasiado la tarea trivial de obtener una lista de actualizaciones y ha hecho que este proceso no sea del todo obvio. Todo esto se hace por seguridad, pero no por facilidad de uso. Estoy de acuerdo con el autor del artículo : la previsibilidad y la transparencia comenzaron a faltar al recibir actualizaciones.

Como resultado del estudio, se escribió el siguiente programa , cuya demostración se puede ver en este video:


Los planes para agregar:

  1. comparar la lista de actualizaciones necesarias con la recibida;
  2. envíe el resultado a través de SNMP / OPC (si alguien tiene experiencia, comparta los comentarios);
  3. organizar la instalación de las actualizaciones "fuera de línea" que faltan de la carpeta especificada.

Si conoce más métodos para obtener una lista no solo de actualizaciones, sino también componentes adicionales (Adobe Flash, Acrobat Reader, etc.) o si tiene otras sugerencias interesantes, escríbalas en los comentarios o en mensajes privados. Estaré encantado de recibir cualquier comentario . Y participe en la encuesta para este artículo, así sabré si mi experiencia con la audiencia de Habrahabr será interesante.

Source: https://habr.com/ru/post/467445/


All Articles