выдача команды Netsh из программы Delphi

Я пытаюсь захватить идентификатор устройства AirCard. Я использую следующий код с намерением сохранить результаты в текстовом файле (imei.txt), который я сохраняю в папке Temp, и перебираю содержимое в поисках ID УСТРОЙСТВА.

Проблемы в том, что пишет только "Не найдена следующая команда: mbn show interface". в файл.

Я протестировал команду Netsh из командной строки, и она возвращает то, что я ожидал.

    xs1 := CreateOleObject('WSCript.Shell');
    xs1.run('%comspec% /c netsh mbn show interface > "' + IMEIFileName +
      '"', 0, true);

Не удается правильно обработать команду NetSh. Правильно ли я передаю его через Comspec? Кажется, он не запускает команду «NetSh» и действует так, как будто я запускаю «mbn» из командной строки.

Спасибо

unit uMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Win.ComObj, ShlObj,     Vcl.StdCtrls;

type
  TfrmMain = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    procedure GetAirCardInformation;
    { Private declarations }
  public
    { Public declarations }
    IMEI: string;
    PhoneNumber: string;
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure TfrmMain.Button1Click(Sender: TObject);
begin
  GetAirCardInformation;
end;

procedure TfrmMain.GetAirCardInformation;
var
  xs1 : OleVariant;
  IMEIFileName: String;
  IMEIStrings: TStringList;
  I: Integer;

  function GetSpecialFolder(const CSIDL: Integer): string;
  var
    RecPath: PWideChar;
  begin
    RecPath := StrAlloc(MAX_PATH);
    try
      FillChar(RecPath^, MAX_PATH, 0);
      if SHGetSpecialFolderPath(0, RecPath, CSIDL, false) then
        result := RecPath
      else
        result := '';
    finally
      StrDispose(RecPath);
    end;
  end;

begin
  IMEI := '';
  IMEIFileName := GetSpecialFolder(CSIDL_LOCAL_APPDATA) + '\Temp\imei.txt';
  Memo1.Lines.Add('IMEIFileName: ' + IMEIFileName);
  try
    if FileExists(IMEIFileName) then
      DeleteFile(IMEIFileName);

    xs1 := CreateOleObject('WSCript.Shell');
    xs1.run('%comspec% /c netsh mbn show interface > "' + IMEIFileName +
      '"', 0, true);

    if FileExists(IMEIFileName) then
    begin
      IMEIStrings := TStringList.Create;
      IMEIStrings.LoadFromFile(IMEIFileName);
      IMEIStrings.NameValueSeparator := ':';
      Memo1.Lines.Add('IMEIStrings Count: ' + intToStr(IMEIStrings.Count));
      for I := 0 to IMEIStrings.Count - 1 do
      begin
        Memo1.Lines.Add(IMEIStrings.text);
        if (Uppercase(Trim(IMEIStrings.Names[I])) = 'DEVICE ID') then
        begin
          IMEI := Trim(IMEIStrings.Values[IMEIStrings.Names[I]]);
          Memo1.Lines.Add('IMEI:' + IMEI);
          break;
        end;
      end;
    end;

  except
    IMEI := '';
  end;
  Memo1.Lines.Add('process complete');
end;

end.

person Meta Mussel    schedule 14.07.2016    source источник
comment
Почему вы используете COM-объект WShell для вызова cmd.exe? Вместо этого используйте CreateProcess(). Сообщение об ошибке означает, что сам netsh не распознает команду mbn show interface. На какой системе вы работаете? Почему вы вообще используете netsh? В Windows есть Mobile Broadband API, используйте WwanEnumerateInterfaces() или IMbnInterfaceManager, чтобы получить список интерфейсов MBN.   -  person Remy Lebeau    schedule 15.07.2016
comment
спасибо Реми. Можете ли вы указать мне пример своего рода? Все, что я могу найти, находится в MSDN. Я работаю в Windows 10 Pro и не вижу библиотеки типов для этого или чего-либо связанного.   -  person Meta Mussel    schedule 15.07.2016
comment
TypeLibrary для интерфейсов MBN — mbnapi.tlb. Я нашел для него модуль Delphi (MbnApi.pas) на форуме DelphiPraxis. Однако вам не нужна TypeLibrary для вызова функций Wwan...().   -  person Remy Lebeau    schedule 15.07.2016


Ответы (1)


Вы не должны использовать COM-объект WShell для запуска cmd.exe. Это перебор. Вместо этого вы можете использовать CreateProcess(). Однако при программном запуске cmd.exe вы не можете перенаправить его вывод с помощью оператора >, который работает только в реальном командном окне. Вместо этого вы можете использовать структуру STARTUPINFO для перенаправления вывода в анонимный канал, созданный с помощью CreatePipe(), а затем вы можете читать из этого канала с помощью ReadFile(). Нет необходимости использовать временный файл вообще. В MSDN есть статья на эту тему:

Создание дочернего процесса с перенаправленным вводом и выводом

Существует множество примеров, демонстрирующих эту технику в Delphi.

При этом лучше вообще не использовать netsh. Windows 7 и более поздние версии имеют API мобильного широкополосного доступа. Вы можете перечислить интерфейсы MBN непосредственно в коде.

Например, с помощью функции WwanEnumerateInterfaces():

unit WwApi;

{$MINENUMSIZE 4}

interface

uses
  Windows;

const
  WWAN_STR_DESC_LENGTH = 256;

type
  WWAN_INTERFACE_STATE = (
    WwanInterfaceStateNotReady,
    WwanInterfaceStateDeviceLocked,
    WwanInterfaceStateUserAccountNotActivated,
    WwanInterfaceStateRegistered,
    WwanInterfaceStateRegistering,
    WwanInterfaceStateDeregistered,
    WwanInterfaceStateAttached,
    WwanInterfaceStateAttaching,
    WwanInterfaceStateDetaching,
    WwanInterfaceStateActivated,
    WwanInterfaceStateActivating,
    WwanInterfaceStateDeactivating
  );

  WWAN_INTF_OPCODE = (
    WwanIntfOpcodePin,
    WwanIntfOpcodeRadioState,
    WwanIntfOpcodePreferredProviders,
    WwanIntfOpcodeCurrentConnection,
    WwanIntfOpcodeProvisionedContexts,
    WwanIntfOpcodeActivateUserAccount,
    WwanIntfOpcodeVendorSpecific,
    WwanIntfOpcodeInterfaceObject,
    WwanIntfOpcodeConnectionObject,
    WwanIntfOpcodeAcState,
    WwanIntfOpcodeClearManualConnectState,
    WwanIntfOpcodeGetStoredRadioState,
    WwanIntfOpcodeGetRadioInfo,
    WwanIntfOpcodeHomeProvider
  );

  // I don't know the definition of this type!
  WWAN_STATUS = DWORD; //?

  WWAN_INTERFACE_STATUS = record
    fInitialized: BOOL;
    InterfaceState: WWAN_INTERFACE_STATE;
  end;

  PWWAN_INTERFACE_INFO = ^WWAN_INTERFACE_INFO;
  WWAN_INTERFACE_INFO = record
    InterfaceGuid: TGuid;
    strInterfaceDescription: array[0..WWAN_STR_DESC_LENGTH-1] of WCHAR;
    InterfaceStatus: WWAN_INTERFACE_STATUS;
    ParentInterfaceGuid: TGuid;
    fIsAdditionalPdpContextInterface: BOOL;
  end;

  PWWAN_INTERFACE_INFO_LIST = ^WWAN_INTERFACE_INFO_LIST;
  WWAN_INTERFACE_INFO_LIST = record
    dwNumberOfItems: DWORD;
    pInterfaceInfo: array[0..0] of WWAN_INTERFACE_INFO;
  end;

function WwanOpenHandle(dwClientVersion: DWORD; pReserved: Pointer; var pdwNegotiatedVersion: DWORD; var phClientHandle: THandle): DWORD; stdcall;
function WwanCloseHandle(hClientHandle: THandle; pReserved: Pointer): DWORD; stdcall;
function WwanEnumerateInterfaces(hClientHandle: THandle; pdwReserved: PDWORD; var ppInterfaceList: PWWAN_INTERFACE_INFO_LIST): DWORD; stdcall;
procedure WwanFreeMemory(pMem: Pointer); stdcall;
function WwanQueryInterface(hClientHandle: THandle; const pInterfaceGuid: TGuid; opCode: WWAN_INTF_OPCODE; pReserved: Pointer; var pdwDataSize: DWORD; var ppData: PByte; var pRequestId: ULONG; var pStatus: WWAN_STATUS): DWORD; stdcall;

implementation

const
  WwApiLib = 'WwApi.dll';

function WwanOpenHandle; external WwApiLib delayed;
function WwanCloseHandle; external WwApiLib delayed;
function WwanEnumerateInterfaces; external WwApiLib delayed;
procedure WwanFreeMemory; external WwApiLib delayed;
function WwanQueryInterface; external WwApiLib delayed;

end.

unit uMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,  Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls;

type
  TfrmMain = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    procedure GetAirCardInformation;
    { Private declarations }
  public
    { Public declarations }
    IMEI: string;
    PhoneNumber: string;
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

uses
  WwApi;

procedure TfrmMain.Button1Click(Sender: TObject);
begin
  GetAirCardInformation;
end;

procedure TfrmMain.GetAirCardInformation;
var
  dwNegotiatedVersion: DWORD;
  hClientHandle: THandle;
  pInterfaceList: PWWAN_INTERFACE_INFO_LIST;
  pInterface: PWWAN_INTERFACE_INFO;
  I: DWORD;
begin
  IMEI := '';
  Memo1.Clear;
  try
    // The value of the first parameter is undocumented!
    // WlanOpenHandle() has a similar parameter, where 1
    // is for XP and 2 is for Vista+. Maybe it is the same
    // for WwanOpenHandle()?...
    //
    if WwanOpenHandle(2, nil, dwNegotiatedVersion, hClientHandle) = 0 then
    try
      if WwanEnumerateInterfaces(hClientHandle, nil, pInterfaceList) = 0 then
      try
        Memo1.Lines.Add('IMEIStrings Count: ' + IntToStr(pInterfaceList.dwNumberOfItems));
        if pInterfaceList.dwNumberOfItems > 0 then
        begin
          pInterface := @pInterfaceList.pInterfaceInfo[0];
          for I := 0 to pInterfaceList.dwNumberOfItems-1 do
          begin
            // use pInterface as needed...

            Memo1.Lines.Add('Desc:' + StrPas(pInterface.strInterfaceDescription));
            Memo1.Lines.Add('Intf:' + GUIDToString(pInterface.InterfaceGuid));

            // and so on ...

            Memo1.Lines.Add('');
            Inc(pInterface);
          end;
        end;
      finally
        WwanFreeMemory(pInterfaceList);
      end;
    finally
      WwanCloseHandle(hClientHandle, nil);
    end;
  except
  end;

  Memo1.Lines.Add('process complete');
end;

end.

Alternatively, using the IMbnInterfaceManager and IMbnInterface COM interfaces, which give you more detailed information:

unit uMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,  Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls;

type
  TfrmMain = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    procedure GetAirCardInformation;
    { Private declarations }
  public
    { Public declarations }
    IMEI: string;
    PhoneNumber: string;
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

uses
  // I found the MbnApi.pas unit on the DelphiPraxis forum:
  //
  // http://www.delphipraxis.net/1342330-post2.html
  //
  // It is too large to post here on StackOverflow!
  // Otherwise, you can import the mbnapi.tlb TypeLibrary yourself...
  //
  MbnApi, ActiveX, ComObj;

procedure TfrmMain.Button1Click(Sender: TObject);
begin
  GetAirCardInformation;
end;

procedure TfrmMain.GetAirCardInformation;
var
  Mgr: IMbnInterfaceManager;
  pInterfaceArray, pPhoneNumberArray: PSafeArray;
  pInterface: IMbnInterface;
  subscriber: IMbnSubscriberInformation;
  ReadyState: MBN_READY_STATE;
  lIntfLower, lIntfUpper: LONG;
  lPhoneNumLower, lPhoneNumUpper: LONG;
  I, J: LONG;
  wStr: WideString;
begin
  Memo1.Clear;
  try
    OleCheck(CoCreateInstance(CLASS_MbnInterfaceManager, nil, CLSCTX_ALL, IMbnInterfaceManager, Mgr));
    OleCheck(Mgr.GetInterfaces(pInterfaceArray));
    try
      OleCheck(SafeArrayGetLBound(pInterfaceArray, 1, lIntfLower));
      OleCheck(SafeArrayGetUBound(pInterfaceArray, 1, lIntfUpper));
      for I = lIntfLower to lIntfUpper do
      begin
        OleCheck(SafeArrayGetElement(pInterfaceArray, I, pInterface));
        try
          // use pInterface as needed...

          OleCheck(pInterface.get_InterfaceID(wStr));
          try
            Memo1.Lines.Add('Interface ID:' + wStr);
          finally
            wStr := '';
          end;

          OleCheck(pInterface.GetReadyState(ReadyState));
          Memo1.Lines.Add('Ready State:' + IntToStr(Ord(ReadyState)));

          OleCheck(pInterface.GetSubscriberInformation(subscriber));
          try
            OleCheck(subscriber.Get_SubscriberID(wStr));
            try
              Memo1.Lines.Add('Subscriber ID: ' + wStr);
            finally
              wStr := '';
            end;

            OleCheck(subscriber.Get_SimIccID(wStr));
            try
              Memo1.Lines.Add('Sim ICC ID: ' + wStr);
            finally
              wStr := '';
            end;

            OleCheck(subscriber.Get_TelephoneNumbers(pPhoneNumberArray));
            try
              OleCheck(SafeArrayGetLBound(pPhoneNumberArray, 1, lPhoneNumLower));
              OleCheck(SafeArrayGetUBound(pPhoneNumberArray, 1, lPhoneNumUpper));
              for J = lPhoneNumLower to lPhoneNumUpper do
              begin
                OleCheck(SafeArrayGetElement(pPhoneNumberArray, J, wStr));
                try
                  Memo1.Lines.Add('Phone #:' + wStr);
                finally
                  wStr := '';
                end;
              end;
            finally
              SafeArrayDestroy(pPhoneNumberArray);
            end;
          finally
            subscriber := nil;
          end;

          // and so on...

          Memo1.Lines.Add('');
        finally
          pInterface := nil;
        end;
      end;
    finally
      SafeArrayDestroy(pInterfaceArray);
    end;
  except
  end;

  Memo1.Lines.Add('process complete');
end;

end.
person Remy Lebeau    schedule 15.07.2016
comment
Реми, я пытался использовать подход WwApi, но не могу получить номер телефона или идентификатор устройства. Это все, что хочет мой клиент. У меня есть пакетный файл с двумя строками ниже netsh mbn show interfaces › imei.txt netsh mbn show readyinfo=Mobile Broadband Connection›pno.txt Он делает именно то, что я хочу, но не когда я вызываю его из Delphi. Я использовал как ShellExe, так и CreateProcess, с Wow64DisableWow64FsRedirection и без него, но всегда оказывался в одном и том же месте. Кажется, он запускает NetSh, но не распознает остальную часть строки. Я просто хочу данные - person Meta Mussel; 20.07.2016
comment
IMbnInterface предоставляет информацию, которую вы ищете, в своих свойствах ReadyState и SubscriberInformation. Если вы настаиваете на использовании netsh, вы не можете использовать оператор > для перенаправления файлов с ShellExecute()/CreateProcess(). Вы должны использовать перенаправление каналов, как показано в статье MSDN, на которую я ссылаюсь. Не используйте временный файл вообще. - person Remy Lebeau; 20.07.2016
comment
Хорошо, я снова использую WwApi, но не могу найти путь к ReadyState или SubscriberInformation. Я теряюсь, как мячик в высокой траве. Все, что я хочу сделать, это получить моему клиенту номер телефона aircard и идентификатор устройства. Каждый маршрут кажется тупиковым. - person Meta Mussel; 20.07.2016
comment
Прочитайте мой предыдущий комментарий еще раз. Я не сказал WwApi, я сказал IMbnInterface. Вы читали IMbnInterface документацию, на которую я ссылаюсь? IMbnInterface предоставляет все виды информации. Я обновил свой пример. - person Remy Lebeau; 20.07.2016
comment
Я не могу скопировать оболочку mbnApi. Текст поврежден, около двух третей вниз. И не даёт скачать по ссылке, хоть я и зарегистрированный пользователь. Я не могу найти файл mbnapi.tlb ни на одной из своих машин, чтобы самостоятельно создать оболочку mbnapi. Где я могу найти эту библиотеку типов? - person Meta Mussel; 20.07.2016
comment
полный текст MbnApi.pas доступен по URL-адресу, который я указал в своем ответе (он слишком велик для публикации в StackOverflow). Текст НЕ поврежден и не обрезан, когда я его просматриваю. Если вы хотите импортировать mbnapi.tlb самостоятельно, и его еще нет на вашем компьютере, вам, вероятно, придется загрузить Windows SDK от Microsoft. - person Remy Lebeau; 20.07.2016
comment
Наконец заработало. Получил обертку после открытия в другом браузере. Пришлось добавить comObj и исправить циклы For, но в остальном все работает. Спасибо - person Meta Mussel; 20.07.2016
comment
Давайте продолжим обсуждение в чате. - person Remy Lebeau; 20.07.2016