Search code examples
delphifiledriverenumerate

Getting driver files for a particular device


I would like to know how I can get all the driver files for a particular device just like the Device Manager does?

I have the following code:

procedure TdlgMain.Test(const DeviceIndex: Integer);
var
  PnPHandle: HDEVINFO;
  DevData: TSPDevInfoData;
  DeviceInterfaceData: TSPDeviceInterfaceData;
  FunctionClassDeviceData: PSPDeviceInterfaceDetailData;
  Success: LongBool;
  Devn: Integer;
  BytesReturned: DWORD;
  SerialGUID: TGUID;
begin
  ZeroMemory(@DevData, SizeOf(SP_DEVINFO_DATA));
  DevData.cbSize := SizeOf(SP_DEVINFO_DATA);

  ZeroMemory(@DeviceInterfaceData, SizeOf(TSPDeviceInterfaceData));
  DeviceInterfaceData.cbSize := SizeOf(TSPDeviceInterfaceData);

  if not SetupDiEnumDeviceInfo(hAllDevices,
    DeviceIndex, DevData) then Exit;

  SerialGUID := DevData.ClassGuid;

  PnPHandle := SetupDiGetClassDevs(@SerialGUID, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE);
  if PnPHandle = Pointer(INVALID_HANDLE_VALUE) then
    Exit;

  Devn := 0;
  repeat
    DeviceInterfaceData.cbSize := SizeOf(TSPDeviceInterfaceData);
    Success := SetupDiEnumDeviceInterfaces(PnPHandle, nil, SerialGUID, Devn, DeviceInterfaceData);
    if Success then
    begin
      DevData.cbSize := SizeOf(DevData);
      BytesReturned := 0;
      // get size required for call
      SetupDiGetDeviceInterfaceDetail(PnPHandle, @DeviceInterfaceData, nil, 0, BytesReturned, @DevData);
      if (BytesReturned <> 0) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then
      begin
        // allocate buffer and initialize it for call
        FunctionClassDeviceData := AllocMem(BytesReturned);
        FunctionClassDeviceData.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
        //FunctionClassDeviceData.cbSize := BytesReturned;
        if SetupDiGetDeviceInterfaceDetail(PnPHandle, @DeviceInterfaceData,
          FunctionClassDeviceData, BytesReturned, BytesReturned, @DevData) then
        begin
          ShowMessage(FunctionClassDeviceData.DevicePath);
        end else
          RaiseLastOSError();
        FreeMem(FunctionClassDeviceData);
      end;
    end;
    Inc(Devn);
  until not Success;
  SetupDiDestroyDeviceInfoList(PnPHandle);

But the ShowMessage() is either not called at all or returns \. How do I get the files properly?

I had a look at devcon from the WinDDK, but it does not return the files either.

Thank you.


Solution

  • I figured it out. There's no API to do it for you, you need to parse the INF files to achieve the result. Here's a quick-n-dirty solution for all of you, who are interested.

    procedure TdlgMain.Test(const DeviceIndex: Integer);
    var
      Paths: TStringList;
      I: Integer;
    
      function GetWinDir: string; inline;
      var
        dir: array [0 .. MAX_PATH] of Char;
      begin
        GetWindowsDirectory(dir, MAX_PATH);
        Result := IncludeTrailingBackslash(StrPas(dir));
      end;
    
      function GetSpecialFolderPath(const folder: Integer): string; inline;
      const
        SHGFP_TYPE_CURRENT = 0;
      var
        path: array [0 .. MAX_PATH] of Char;
      begin
        if SUCCEEDED(SHGetFolderPath(0, folder, 0, SHGFP_TYPE_CURRENT, @path[0]))
        then
          Result := IncludeTrailingBackslash(path)
        else
          Result := '';
      end;
    
      function LocateInfFile(const F: String): String; inline;
      var
        T: String;
      begin
        Result := '';
    
        if (Pos(SysUtils.PathDelim, F) > 0) then
        begin
          Result := F;
          Exit;
        end;
    
        T := GetWinDir();
        if (FileExists(T + 'inf\' + F)) then
          Result := T + 'inf\' + F
        else if (FileExists(T + 'system32\' + F)) then
          Result := T + 'system32\' + F;
      end;
    
      procedure ReadSectionNoKeys(const AFile, ASection: String;
        const SL: TStringList);
      var
        TheFile: TStringList;
        Line: String;
        TrimEnd: Boolean;
        Idx, Tmp: Integer;
      begin
        TrimEnd := False;
    
        TheFile := TStringList.Create();
        try
          TheFile.LoadFromFile(AFile);
          Idx := TheFile.IndexOf('[' + ASection + ']');
          if (Idx <> -1) then
          begin
            Idx := Idx + 1;
            while True do
            begin
              Line := Trim(TheFile[Idx]);
              Inc(Idx);
              if (Pos(';', Line) = 1) then
                continue;
    
              if (Pos('[', Line) > 0) then
                Break;
    
              Tmp := Pos(',', Line);
              if (Tmp > 0) then
                TrimEnd := True
              else
              begin
                Tmp := PosEx(';', Line, 3);
                if (Tmp > 0) then
                  TrimEnd := True;
              end;
    
              if (Line <> '') then
              begin
                if (TrimEnd) then
                begin
                  Line := Trim(Copy(Line, 1, Tmp - 1));
                  TrimEnd := False;
                end;
    
                SL.Add(Line);
              end;
    
              if (Idx = (TheFile.Count - 1)) then
                Break;
            end;
          end;
        finally
          TheFile.Free();
        end;
      end;
    
      function IniReadStr(const Ini: TIniFile; const S, L, D: String): String;
      var
        T: Integer;
      begin
        Result := Ini.ReadString(S, L, D);
    
        T := Pos(';', Result);
        if (T > 0) then
          Result := Trim(Copy(Result, 1, T - 1));
      end;
    
      procedure ParseInfFile(const InfFile, SectionName: String);
      var
        I: TIniFile;
        SL, FilesList: TStringList;
        X, Y, Tmp: Integer;
        Pth, S, S1: String;
      begin
        I := TIniFile.Create(InfFile);
        try
          if (SectionName <> '') and (I.SectionExists(SectionName)) then
          begin
            // Check if the section has a value called "CopyFiles".
            if (I.ValueExists(SectionName, 'CopyFiles')) then
            begin
              // It has. Read it to a string and separate by commas.
              SL := TStringList.Create();
              try
                SL.CommaText := IniReadStr(I, SectionName, 'CopyFiles', '');
    
                // Now, every line of the string list is a section name. Check
                // the destination directory of each.
                if (I.SectionExists('DestinationDirs')) then
                  for X := 0 to SL.Count - 1 do
                  begin
                    S := IniReadStr(I, 'DestinationDirs', SL[X], '');
                    if (S = '') then
                      S := IniReadStr(I, 'DestinationDirs', 'DefaultDestDir', '');
    
                    if (S <> '') then
                    begin
                      // Split the path by comma, if any.
                      Tmp := Pos(',', S);
                      S1 := '';
                      if (Tmp > 0) then
                      begin
                        S1 := Trim(Copy(S, Tmp + 1, Length(S)));
                        S := Trim(Copy(S, 1, Tmp - 1));
                      end;
    
                      // Convert the numeric value of S to a proper directory.
                      Pth := '';
                      if (S = '10') then
                        Pth := GetWinDir();
                      if (S = '11') then
                        Pth := GetWinDir() + 'system32\';
                      if (S = '12') then
                        Pth := GetWinDir() + 'system32\drivers\';
                      if (S = '50') then
                        Pth := GetWinDir() + 'system\';
                      if (S = '30') then
                        Pth := ExtractFileDrive(GetWinDir());
                      if (StrToInt(S) >= 16384) then
                        Pth := GetSpecialFolderPath(StrToInt(S));
    
                      if (S1 <> '') then
                        Pth := IncludeTrailingBackslash(Pth + S1);
    
                      // If we got the path, read the files.
                      if (Pth <> '') then
                      begin
                        FilesList := TStringList.Create();
                        try
                          ReadSectionNoKeys(InfFile, SL[X], FilesList);
                          for Y := 0 to FilesList.Count - 1 do
                            if (Paths.IndexOf(Pth + FilesList[Y]) = -1) then
                              Paths.Add(Pth + FilesList[Y]);
                        finally
                          FilesList.Free();
                        end;
                      end;
                    end;
                  end;
              finally
                SL.Free();
              end;
            end;
    
            // Check if there're "Include" and "Needs" values.
            if ((I.ValueExists(SectionName, 'Include')) and
              (I.ValueExists(SectionName, 'Needs'))) then
            begin
              // Split both by comma.
              SL := TStringList.Create();
              FilesList := TStringList.Create();
              try
                SL.CommaText := IniReadStr(I, SectionName, 'Include', '');
                FilesList.CommaText := IniReadStr(I, SectionName, 'Needs', '');
                if (SL.Text <> '') and (FilesList.Text <> '') then
                  for X := 0 to SL.Count - 1 do
                    for Y := 0 to FilesList.Count - 1 do
                      ParseInfFile(LocateInfFile(SL[X]), FilesList[Y]);
              finally
                FilesList.Free();
                SL.Free();
              end;
            end;
          end;
        finally
          I.Free();
        end;
      end;
    
    begin
      Paths := TStringList.Create();
      try
        ParseInfFile(LocateInfFile(DeviceHelper.InfName), DeviceHelper.InfSection);
        Paths.Sort();
    
        ListView_InsertGroup(lvAdvancedInfo.Handle, 'Driver Files', 2);
        for I := 0 to Paths.Count - 1 do
          ListView_AddItemsInGroup(lvAdvancedInfo, '', Paths[I], 2);
      finally
        Paths.Free();
      end;
    end;