Skip to content

Instantly share code, notes, and snippets.

@meitinger
Last active August 31, 2015 10:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save meitinger/e3ccc61ca1a8ac131c50 to your computer and use it in GitHub Desktop.
Save meitinger/e3ccc61ca1a8ac131c50 to your computer and use it in GitHub Desktop.
Utility that compiles multiple INFs into one compressed XML repository.
/* Copyright (C) 2015, Manuel Meitinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
[assembly: AssemblyTitle("Driver Compiler")]
[assembly: AssemblyDescription("Utility that compiles multiple INFs into one compressed XML repository.")]
[assembly: AssemblyProduct("Driver Management")]
[assembly: AssemblyCompany("AufBauWerk - Unternehmen für junge Menschen")]
[assembly: AssemblyCopyright("Copyright © 2015 by Manuel Meitinger")]
[assembly: AssemblyVersion("1.2.0.2")]
[assembly: ComVisible(false)]
namespace Aufbauwerk.Tools.DriverManagement.Compiler
{
public sealed class Inf : IEnumerable<Inf.Section>, IDisposable
{
#region SetupApi
readonly static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
const string SetupApi = "SetupApi.dll";
const uint INF_STYLE_WIN4 = 0x00000002;
const int MAX_PATH = 260;
const int ERROR_SUCCESS = 0;
const int ERROR_INSUFFICIENT_BUFFER = 122;
const int ERROR_NO_MORE_ITEMS = 259;
const int ERROR_LINE_NOT_FOUND = -536870654;
const uint SIGNERSCORE_UNKNOWN = 0xFF000000;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct SP_INF_SIGNER_INFO_V2
{
public int cbSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
public string CatalogFile;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
public string DigitalSigner;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
public string DigitalSignerVersion;
public uint SignerScore;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct INFCONTEXT
{
public IntPtr Inf;
public IntPtr CurrentInf;
public int Section;
public int Line;
}
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)]
extern static bool SetupVerifyInfFile(string InfName, IntPtr AltPlatformInfo, ref SP_INF_SIGNER_INFO_V2 InfFileName);
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)]
extern static bool SetupEnumInfSections(IntPtr InfHandle, int EnumerationIndex, StringBuilder ReturnBuffer, int ReturnBufferSize, out int RequiredSize);
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)]
extern static IntPtr SetupOpenInfFile(string FileName, string InfClass, uint InfStyle, out int ErrorLine);
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)]
extern static int SetupGetLineCount(IntPtr InfHandle, string Section);
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)]
extern static bool SetupFindFirstLine(IntPtr InfHandle, string Section, string Key, ref INFCONTEXT Context);
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)]
extern static bool SetupGetStringField(ref INFCONTEXT Context, int FieldIndex, StringBuilder ReturnBuffer, int ReturnBufferSize, out int RequiredSize);
[DllImport(SetupApi, CharSet = CharSet.Auto, SetLastError = true)]
extern static bool SetupFindNextMatchLine(ref INFCONTEXT ContextIn, string Key, ref INFCONTEXT ContextOut);
[DllImport(SetupApi, ExactSpelling = true, SetLastError = true)]
extern static int SetupGetFieldCount(ref INFCONTEXT Context);
[DllImport(SetupApi, ExactSpelling = true, SetLastError = true)]
extern static void SetupCloseInfFile(IntPtr InfHandle);
#endregion
public class Exception : ApplicationException
{
readonly string message;
public Exception(string path, string message)
{
if (path == null)
throw new ArgumentNullException("path");
if (message == null)
throw new ArgumentNullException("message");
Path = path;
this.message = message;
}
public Exception(string path, int line, string message)
: this(path, message)
{
if (line < 1)
throw new ArgumentOutOfRangeException("line");
Line = line;
}
public Exception(string path, string section, string key, string message)
: this(path, message)
{
if (section == null)
throw new ArgumentNullException("section");
if (key == null)
throw new ArgumentNullException("key");
Section = section;
Key = key;
}
internal Exception(Line line, string message)
{
Path = line.Section.Inf.Path;
Line = line.Number;
Section = line.Section.Name;
Key = line.Key;
this.message = message;
}
public string Path { get; private set; }
public int Line { get; private set; }
public string Section { get; private set; }
public string Key { get; private set; }
public override string Message
{
get
{
// create the message
var builder = new StringBuilder(Path);
if (Line > 0)
builder.Append(", line ").Append(Line);
if (!string.IsNullOrEmpty(Section))
builder.Append(", section ").Append(Section);
if (!string.IsNullOrEmpty(Key))
builder.Append(", key ").Append(Key);
builder.Append(": ");
builder.Append(message);
return builder.ToString();
}
}
}
public class Line
{
INFCONTEXT context;
internal Line(Section section, INFCONTEXT context)
{
// create a line
this.context = context;
Section = section;
}
string GetFieldInternal(int index)
{
// retrieve the given field
Section.Inf.CheckDisposed();
var requiredSize = 200;
var builder = new StringBuilder(requiredSize);
while (!SetupGetStringField(ref context, index, builder, builder.Capacity, out requiredSize))
{
if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER)
{
if (index == 0)
return null;
throw new Exception(this, "Cannot get string field: " + new Win32Exception().Message);
}
builder.Capacity = requiredSize;
}
return builder.ToString();
}
public Section Section { get; private set; }
public string Key { get { return GetFieldInternal(0); } }
public int Number { get { return context.Line; } }
public int FieldCount
{
get
{
Section.Inf.CheckDisposed();
var count = SetupGetFieldCount(ref context);
if (count == 0 && Marshal.GetLastWin32Error() != ERROR_SUCCESS)
throw new Exception(this, "Cannot get field count: " + new Win32Exception().Message);
return count;
}
}
public string GetField(int index)
{
// retrieve the field at the given index
if (index < 0)
throw new ArgumentOutOfRangeException("index");
var value = GetFieldInternal(index + 1);
return value;
}
public T GetFieldAs<T>(Converter<string, T> converter, int index)
{
// try to convert the given field
if (converter == null)
throw new ArgumentNullException("converter");
var field = GetField(index);
try { return converter(field); }
catch (FormatException e) { throw new Inf.Exception(this, "Cannot convert field: " + e.Message); }
catch (OverflowException e) { throw new Inf.Exception(this, "Cannot handle overflown value: " + e.Message); }
}
}
public class Section : IEnumerable<Line>
{
internal Section(Inf inf, string name)
{
Inf = inf;
Name = name;
}
IEnumerable<Line> GetLines(string key)
{
// enumerate lines
Inf.CheckDisposed();
var context = new INFCONTEXT();
if (SetupFindFirstLine(Inf.Handle, Name, key, ref context))
{
do
{
yield return new Line(this, context);
Inf.CheckDisposed();
}
while (SetupFindNextMatchLine(ref context, key, ref context));
}
if (Marshal.GetLastWin32Error() != ERROR_LINE_NOT_FOUND)
throw new Exception(Inf.Path, Name, key, "Cannot find line: " + new Win32Exception().Message);
}
public Inf Inf { get; private set; }
public string Name { get; private set; }
public bool Exists
{
get
{
Inf.CheckDisposed();
return SetupGetLineCount(Inf.Handle, Name) > -1;
}
}
public bool IsEmpty
{
get
{
Inf.CheckDisposed();
return SetupGetLineCount(Inf.Handle, Name) < 1;
}
}
public IEnumerable<Line> this[string key]
{
get
{
// enumerate all mathing lines
if (key == null)
throw new ArgumentNullException("key");
return GetLines(key);
}
}
public Line Single(string key)
{
// return the single item
var enumerator = GetLines(key).GetEnumerator();
if (!enumerator.MoveNext())
throw new Exception(Inf.Path, Name, key, "No such line exists.");
var result = enumerator.Current;
if (enumerator.MoveNext())
throw new Exception(Inf.Path, Name, key, "Multi lines exists.");
return result;
}
public IEnumerator<Line> GetEnumerator()
{
return GetLines(null).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public Inf(string path)
{
// parse the inf
if (path == null)
throw new ArgumentNullException("path");
var errorLine = 0;
Path = path;
Handle = SetupOpenInfFile(Path, null, INF_STYLE_WIN4, out errorLine);
if (Handle == INVALID_HANDLE_VALUE)
throw new Exception(Path, errorLine, "Cannot open file: " + new Win32Exception().Message);
}
public Section this[string name]
{
get
{
// create the section
if (name == null)
throw new ArgumentNullException("name");
return new Section(this, name);
}
}
IntPtr Handle { get; set; }
void CheckDisposed()
{
// check if the object is disposed
if (Handle == INVALID_HANDLE_VALUE)
throw new ObjectDisposedException(Path);
}
public uint SignerScore
{
get
{
// query the signer score (SetupVerifyInfFile can fail)
var info = new SP_INF_SIGNER_INFO_V2()
{
cbSize = Marshal.SizeOf(typeof(SP_INF_SIGNER_INFO_V2)),
SignerScore = SIGNERSCORE_UNKNOWN,
};
SetupVerifyInfFile(Path, IntPtr.Zero, ref info);
return info.SignerScore;
}
}
public string Path { get; private set; }
public IEnumerator<Section> GetEnumerator()
{
// enumerate all sections
CheckDisposed();
var requiredLen = 200;
var buffer = new StringBuilder(requiredLen);
var index = 0;
while (true)
{
if (SetupEnumInfSections(Handle, index, buffer, buffer.Capacity, out requiredLen))
{
yield return new Section(this, buffer.ToString());
CheckDisposed();
index++;
}
else
{
switch (Marshal.GetLastWin32Error())
{
case ERROR_NO_MORE_ITEMS:
yield break;
case ERROR_INSUFFICIENT_BUFFER:
buffer.Capacity = requiredLen;
break;
default:
throw new Exception(Path, "Cannot enumerate sections: " + new Win32Exception().Message);
}
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
void IDisposable.Dispose()
{
// close the inf file
if (Handle != INVALID_HANDLE_VALUE)
{
SetupCloseInfFile(Handle);
Handle = INVALID_HANDLE_VALUE;
}
}
}
public enum Architecture : ushort
{
x86 = 0,
ia64 = 6,
amd64 = 9,
}
public class TargetOSVersion
{
public static readonly TargetOSVersion Empty = new TargetOSVersion(string.Empty);
static readonly Action<TargetOSVersion, string>[] Converters = new Action<TargetOSVersion, string>[]
{
(t,v) => { t.Architecture = (Architecture)Enum.Parse(typeof(Architecture), v, true); },
(t,v) => { t.MajorVersion = ushort.Parse(v,CultureInfo.InvariantCulture); },
(t,v) => { t.MinorVersion = ushort.Parse(v,CultureInfo.InvariantCulture); },
(t,v) => { t.ProductType = v.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? byte.Parse(v.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture) : byte.Parse(v, CultureInfo.InvariantCulture); },
(t,v) => { t.SuiteMask = v.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? ushort.Parse(v.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture) : ushort.Parse(v, CultureInfo.InvariantCulture); },
};
private TargetOSVersion(string suffix)
{
Suffix = suffix;
}
public Architecture? Architecture { get; private set; }
public uint? MajorVersion { get; private set; }
public uint? MinorVersion { get; private set; }
public byte? ProductType { get; private set; }
public ushort? SuiteMask { get; private set; }
public static TargetOSVersion Parse(string s)
{
if (s == null)
throw new ArgumentNullException("s");
// check the prefix and split the parts
if (!s.StartsWith("nt", StringComparison.OrdinalIgnoreCase))
throw new FormatException("TargetOSVersion dosn't start with 'nt'");
var parts = s.Substring(2).Split('.');
if (parts.Length > Converters.Length)
throw new FormatException(string.Format("TargetOSVersion contains {0} parts, only {0} allowed", parts.Length, Converters.Length));
// convert each part
var result = new TargetOSVersion("." + s);
for (var i = 0; i < parts.Length; i++)
{
var part = parts[i].Trim();
if (part.Length == 0)
continue;
try { Converters[i](result, part); }
catch (Exception e) { throw new FormatException(string.Format("TargetOSVersion part #{0} '{1}' is invalid: {2}", i, part, e.Message)); }
}
if (result.MinorVersion.HasValue && !result.MajorVersion.HasValue)
throw new FormatException(string.Format("TargetOSVersion contains minor but no major version"));
return result;
}
public string Suffix { get; private set; }
}
static class Program
{
enum LogLevel
{
Warning,
Error,
Fatal,
}
static void Log(LogLevel level, Exception e)
{
Console.Error.WriteLine("{0}: {1}", level, e.Message);
}
const string InArg = "IN";
const string OutArg = "OUT";
static int Main(string[] args)
{
try
{
// parse the arguments and go over every file
var inputDir = (string)null;
var outputFile = (string)null;
foreach (var arg in args)
{
// check the arg syntax
if (arg.Length == 0 || (arg[0] != '/' && arg[0] != '-'))
goto Usage;
var parts = arg.Split(new char[] { ':', '=' }, 2);
if (parts.Length != 2)
goto Usage;
// handle each arg
switch (parts[0].Substring(1).ToUpperInvariant())
{
case InArg:
if (inputDir != null)
goto Usage;
inputDir = parts[1];
break;
case OutArg:
if (outputFile != null)
goto Usage;
outputFile = parts[1];
break;
default:
goto Usage;
}
}
if (inputDir == null || outputFile == null)
goto Usage;
// create the repository xml file
var xml = new XmlDocument();
var repository = xml.CreateElement("Repository");
repository.SetAttribute("Location", inputDir);
xml.AppendChild(repository);
var failed = 0;
foreach (var infPath in Directory.EnumerateFiles(inputDir, "*.inf", SearchOption.AllDirectories))
{
try
{
// read the inf file
using (var inf = new Inf(infPath))
{
var driver = xml.CreateElement("Driver");
var version = inf["Version"];
driver.SetAttribute("Date", version.Single("DriverVer").GetFieldAs(s => DateTime.ParseExact(s, "MM'/'dd'/'yyyy", CultureInfo.InvariantCulture), 0).ToString("yyyy'-'MM'-'dd", CultureInfo.InvariantCulture));
driver.SetAttribute("Version", version.Single("DriverVer").GetFieldAs(s => { try { return Version.Parse(s); } catch (Exception e) { throw new FormatException(e.Message, e); } }, 1).ToString());
driver.SetAttribute("ProviderName", version.Single("Provider").GetField(0));
driver.SetAttribute("InfPath", inf.Path);
// collect all manufacturers
var mfs = inf["Manufacturer"];
if (mfs.IsEmpty)
{
Log(LogLevel.Warning, new Inf.Exception(inf.Path, string.Format("[Manufacturer] section is {0}.", mfs.Exists ? "empty" : "missing")));
continue;
}
foreach (var mf in mfs.Where(mf => !string.IsNullOrEmpty(mf.Key)))
{
// create the xml node and find the best target os version
var mfnode = xml.CreateElement("Manufacturer");
mfnode.SetAttribute("Name", mf.Key);
var count = mf.FieldCount;
var baseSectionName = mf.GetField(0);
// get all matching versions
var osVersions = new TargetOSVersion[mf.FieldCount];
osVersions[0] = TargetOSVersion.Empty;
for (var i = 1; i < osVersions.Length; i++)
osVersions[i] = mf.GetFieldAs(TargetOSVersion.Parse, i);
foreach
(
var osVersion in osVersions
.OrderByDescending(v => v.MajorVersion)
.ThenByDescending(v => v.MinorVersion)
.ThenByDescending(v => v.ProductType)
.ThenByDescending(v => v.SuiteMask)
.ThenByDescending(v => v.Architecture)
)
{
// create the version node
var vernode = xml.CreateElement("TargetOSVersion");
if (osVersion.Architecture.HasValue)
vernode.SetAttribute("Architecture", osVersion.Architecture.Value.ToString("D"));
if (osVersion.MajorVersion.HasValue)
vernode.SetAttribute("MajorVersion", osVersion.MajorVersion.Value.ToString(CultureInfo.InvariantCulture));
if (osVersion.MinorVersion.HasValue)
vernode.SetAttribute("MinorVersion", osVersion.MinorVersion.Value.ToString(CultureInfo.InvariantCulture));
if (osVersion.ProductType.HasValue)
vernode.SetAttribute("ProductType", osVersion.ProductType.Value.ToString(CultureInfo.InvariantCulture));
if (osVersion.SuiteMask.HasValue)
vernode.SetAttribute("SuiteMask", osVersion.SuiteMask.Value.ToString(CultureInfo.InvariantCulture));
// check if the section exists
var sectionName = baseSectionName + osVersion.Suffix;
var section = inf[sectionName];
if (!section.Exists)
{
// ignore only a missing default section and only if there are target os decorations
if (osVersion.Suffix.Length == 0 && osVersions.Length > 1)
continue;
Log(LogLevel.Warning, new Inf.Exception(mf, string.Format("Section [{0}] is missing.", sectionName)));
}
else
{
// collect all devices
var suffixes = osVersion.Architecture.HasValue ? new string[] { ".nt" + osVersion.Architecture.Value, ".nt", string.Empty } : new string[] { ".nt", string.Empty };
foreach (var dev in section)
{
// try to get the feature score
var devnode = xml.CreateElement("Device");
var ddinstallName = dev.GetField(0);
foreach (var suffix in suffixes)
{
var ddinstall = inf[ddinstallName + suffix];
if (ddinstall.Exists)
{
var featureScore = ddinstall["FeatureScore"].FirstOrDefault();
if (featureScore != null)
devnode.SetAttribute("FeatureScore", featureScore.GetFieldAs(s => byte.Parse(s.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? s.Substring(2) : s, NumberStyles.HexNumber, CultureInfo.InvariantCulture), 0).ToString(CultureInfo.InvariantCulture));
break;
}
}
// add all ids
devnode.SetAttribute("Description", dev.Key);
devnode.SetAttribute("HardwareId", dev.GetField(1));
for (var compatid = 2; compatid < dev.FieldCount; compatid++)
{
var compatidnode = xml.CreateElement("CompatibleId");
compatidnode.InnerText = dev.GetField(compatid);
devnode.AppendChild(compatidnode);
}
vernode.AppendChild(devnode);
}
}
// always add the version node (empty sections must be preserved)
mfnode.AppendChild(vernode);
}
// add the manufactur if there are matching versions
if (mfnode.ChildNodes.Count > 0)
driver.AppendChild(mfnode);
}
// add the driver and query the signer score if there are manufactures
if (driver.ChildNodes.Count > 0)
{
driver.SetAttribute("SignerScore", inf.SignerScore.ToString(CultureInfo.InvariantCulture));
repository.AppendChild(driver);
}
}
}
catch (Inf.Exception e)
{
Log(LogLevel.Error, e);
failed++;
}
}
// save the file and return the number of failed infs
using (var outputFileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write))
using (var gzipStream = new GZipStream(outputFileStream, CompressionMode.Compress, true))
xml.Save(gzipStream);
return failed;
Usage:
// write the usage
Console.Error.WriteLine
(
"USAGE: {0}\n" +
" /{1}:<path to inf root directory>\n" +
" /{2}:<path to gzipped xml output file>",
Environment.GetCommandLineArgs()[0],
InArg,
OutArg
);
return -1;
}
catch (Exception e)
{
Log(LogLevel.Fatal, e);
return -1;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment