从 Delphi 程序发出 Netsh 命令

issuing Netsh command from Delphi program

我正在尝试获取 AirCard 的设备 ID。我正在使用以下代码,目的是将结果存储在我存储在 Temp 文件夹中的文本文件 (imei.txt) 中,并循环遍历内容,查找设备 ID。

问题是它只将 "The following command was not found: mbn show interface." 写入文件。

我已经从命令行测试了 Netsh 命令,它 returns 符合我的预期。

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

无法正确处理 NetSh 命令。我是否正确地通过了 Comspec?它似乎不是 运行 "NetSh" 命令,就像我在命令提示符下 运行ning "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.

您不应该将 WShell COM 对象用于 运行 cmd.exe。那太矫枉过正了。您可以改用 CreateProcess()。但是,当 运行 宁 cmd.exe 可编程时,您不能使用 > 运算符重定向其输出,这仅在实际命令 window 中有效。您可以改为使用 STARTUPINFO 结构将输出重定向到使用 CreatePipe() 创建的匿名管道,然后您可以使用 ReadFile() 从该管道读取。根本不需要使用临时文件。 MSDN 有一篇关于这个主题的文章:

Creating a Child Process with Redirected Input and Output

Delphi.

中有大量示例可以证明此技术

也就是说,更好的选择是根本不使用 netsh。 Windows 7及以后有一个Mobile Broadband 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.

或者,使用 IMbnInterfaceManager and IMbnInterface COM 接口,为您提供更详细的信息:

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 Whosebug!
  // 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.