Search code examples
delphidelphi-xenetsh

issuing Netsh command from Delphi program


I am trying to capture the Device ID of an AirCard. I am using the following code with the intentions of storing the results in a text file (imei.txt) that I store in the Temp folder and loop through the contents, looking for DEVICE ID.

The problems is that it only writes "The following command was not found: mbn show interface." to the file.

I have tested the Netsh command from the command line and it returns what I would expect.

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

It is failing to process the NetSh command properly. Am I passing it through the Comspec correctly? It seems to not run the "NetSh" command and acts as if I am running "mbn" from the command prompt.

Thanks

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.

Solution

  • You should not be using the WShell COM object to run cmd.exe. That is overkill. You can use CreateProcess() instead. However, when running cmd.exe programmably, you cannot redirect its output using the > operator, that only works in an actual command window. You can instead use the STARTUPINFO structure to redirect the output to an anonymous pipe created with CreatePipe(), and then you can read from that pipe using ReadFile(). No need to use a temp file at all. MSDN has an article on this topic:

    Creating a Child Process with Redirected Input and Output

    There are plenty of examples floating around that demonstrate this technique in Delphi.

    That being said, a better option is to not use netsh at all. Windows 7 and later have a Mobile Broadband API. You can enumerate the MBN interfaces directly in your code.

    For example, using the WwanEnumerateInterfaces() function:

    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.