Solved

Delphi - Automating Windows Scripting Host

Posted on 2006-06-10
7
1,090 Views
Last Modified: 2008-01-09
hi,
I am doing something stupid here (not for the first time<g>) but I just cannot see it.

I simply want to use wsh to retrieve info about all the drives (specifically whether they are NTFS or not).  (actually I did not want to use WSH or WMI which is a wrapper over WSH, and only wanted to find out which drives were NTFS but I could find no way of doing that - if anyone knows appropriate API calls for that, I would be pleased).

But now I am stuck with this stupid automation problem. I tried late binding and that did not work .. tried early binding and I got the same problem.

Here is the early binding version after having imported  the  IWshRuntimeLibrary_TLB;

procedure TForm1.btnTestForNTFSClick(Sender: TObject);
var fso: IFilesystem3;
var drvs: IdriveCollection;
var drv : Idrive;
var k:Olevariant;

begin
  fso := coFileSystemObject.create;
  drvs :=fso.Drives;
  showmessage(inttostr(drvs.Count)); // 8 drives
//   drv :=drvs.Item[1] as Idrive;                               .. ***** crashes
k:=1;
     drv :=drvs.get_Item(k) as Idrive;                   // ************ also crashes
//  if fso.DriveExists('C:') then showmessage('ok') else showmessage('nope');
end;

===================
here is the late binding version
var
fso, drv, drvs,itm : OleVariant;
var k: integer;
begin
fso := CreateOleObject('Scripting.FileSystemObject');
drvs := fso.drives;
showmessage('drive count =' +inttostr(drvs.count));
for k:=1 to drvs.count  do begin
itm :=drvs(k);                          // *************** crashes
showmessage(itm.Path);
end; // end for

=======================

what am I doing wrong? casting? some problem with my understanding of collections?




0
Comment
Question by:Mutley2003
  • 5
  • 2
7 Comments
 
LVL 26

Accepted Solution

by:
Russell Libby earned 500 total points
ID: 16879879
The simplest api calls would be:

GetLogicalDriveStrings - to get the listing of drives on the system
GetVolumeInformation - which can be used to get the file system type for the volume.

As to your problem with the FileSystemObject Drives collection, the issue is that you made the assumption that the index is an integer; it isnt.  It expects a string value which is the drive letter to access. Not the most intuitive collection, but thats the way it works nonetheless. Most VB code you will see dealing with this collection ends up using the ForEach to enumerate the items. Some delphi code that does the same thing:

var  ovFso,
     ovDrv,
     ovDrvs,
     ovEnum:        OleVariant;
     pvEnum:        IEnumVariant;
     pdParams:      TDispParams;
     dwFetch:       Cardinal;
begin

  // Create file system scripting object
  ovFso:=CreateOleObject('Scripting.FileSystemObject');

  // Resource protection
  try
     // Get drives collection
     ovDrvs:=ovFso.drives;
     // Resource protection
     try
        // Clear disp params
        FillChar(pdParams, SizeOf(TDispParams), 0);
        // Get enumerator
        if (IDispatch(ovDrvs).Invoke(DISPID_NEWENUM, GUID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET or DISPATCH_METHOD, pdParams, @ovEnum, nil, nil) = S_OK) then
        begin
           // Cast
           pvEnum:=IUnknown(ovEnum) as IEnumVariant;
           // Resource protection
           try
              // Reset
              pvEnum.Reset;
              // Enumerate items
              while (pvEnum.Next(1, ovDrv, dwFetch) = S_OK) and (dwFetch = 1) do
              begin
                 // Resource protection
                 try
                    // Show name
                    ShowMessage(ovDrv.DriveLetter);
                    // Check if ready
                    if ovDrv.IsReady then
                    begin
                       // Show the file system
                       ShowMessage(ovDrv.FileSystem);
                    end;
                 finally
                    // Release interface
                    ovDrv:=Unassigned;
                 end;
              end;
           finally
              // Release interfaces
              ovEnum:=Unassigned;
              pvEnum:=nil;
           end;
        end;
     finally
        // Release interface
        ovDrvs:=Unassigned;
     end;
  finally
     // Release interface
     ovFso:=Unassigned;
  end;

end;

If you need an example using the api calls, then let me know.

Regards,
Russell

0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 16879944

Using straight APIs, another way of getting just the NTFS drives. Note: for removeable drives that have no media in them, the call to GetVolumeInformation will fail (as to be expected) and the drive will only be added to the list if a blank filter is passed. This corresponds with the IsReady call in the automation example, which if not checked before attemping to read the FileSystem property, will cause the automation code to error.

Regards,
Russell

function GetDriveList(List: TStrings; Filter: String): Integer;
var  lpszList:      Array [0..1024] of Char;
     lpszFileSystem:Array [0..1024] of Char;
     lpszDrive:     PChar;
     dwSize:        DWORD;
begin

  // Check list
  if Assigned(List) then
  begin
     // Lock list
     List.BeginUpdate;
     try
        // Clear list
        List.Clear;
        // Get locical drive strings
        dwSize:=GetLogicalDriveStrings(SizeOf(lpszList), lpszList);
        // Check result
        if (dwSize > 0) and (dwSize <= SizeOf(lpszList)) then
        begin
           // Get pointer to string
           lpszDrive:=@lpszList;
           // Walk the  list
           while (lpszDrive^ > #0) do
           begin
              // Get volume information
              if GetVolumeInformation(lpszDrive, nil, 0, nil, dwSize, dwSize, lpszFileSystem, SizeOf(lpszFileSystem)) then
              begin
                 // Check file system against passed in filter. If filter is blank,
                 // then all drives will be returned
                 if (Length(Filter) = 0) then
                    // Add to list
                    List.Add(lpszDrive)
                 else if (CompareText(Filter, lpszFileSystem) = 0) then
                    // Add to list
                    List.Add(lpszDrive);
              end
              else if (Length(Filter) = 0) then
                 // Add to list
                 List.Add(lpszDrive);
              // Get string end
              lpszDrive:=StrEnd(lpszDrive);
              // Skip null terminator; list is double null terminated
              Inc(lpszDrive);
           end;
        end;
     finally
        // Unlock list
        List.EndUpdate;
     end;
     // Return the filtered drive count
     result:=List.Count;
  end
  else
     // No list passed
     result:=(-1);

end;

// Example usage
procedure TForm1.Button1Click(Sender: TObject);
var  List:          TStringList;
begin

  // Create list
  List:=TStringList.Create;
  try
     // Get drive list; for all drives pass an empty filter, otherwise pass the file system
     // type to filter the drive list on (not case sensitive)
     GetDriveList(List, 'NTFS');
     // ... do whatever with list
  finally
     List.Free;
  end;

end;
0
 

Author Comment

by:Mutley2003
ID: 16880077
thanks Russell

..ok, I guess I should have thought that a collection could be indexed by a string.

q1. I notice in your enumeration code you are releasing interfaces a lot - is that neccessary, or just you being a careful coder?

Q2. that enumeration reminded me of Binh Ly's code (comlib.pas at techvanguards.com) , which meant I could clean it up to

procedure TForm1.btnTestDrives2Click(Sender: TObject);
var objFileSystem: FileSystemObject;
var drvs: IdriveCollection;
var drv: Idrive;
begin
  objFileSystem := CoFileSystemObject.Create;
  with comlib.TEnumVariant.Create(objFilesystem.Drives) do begin
    try
      while ForEachObject(drv, IID_IDrive) do begin
      if drv.IsReady then
        ShowMessage(drv.path+' ' +drv.Filesystem)
        else
        ShowMessage(drv.path+' not ready')
        end;
    finally
      Free;                             // free the enumerator
    end;
  end;                                  // end with
end;

- some small amount of messing around was needed because he redefined the standard IenumVariant
... any comments you have on his approach would be welcome.


Thanks a lot for your execellent (as always) help

regards
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 
LVL 26

Expert Comment

by:Russell Libby
ID: 16880985

When working with automation objects, you can/should expect indexed properties to be indexed by data types other than strings.

1. Not really necessary, as Delphi v3 and above does it for you. Just being complete, so someone looking at the code realizes the full impact of what the code does (ever converted code from say VB, etc, and had difficulties due to the hidden features of the language)

2. It appears to shorten the code by placing a wrapper object over the inteface (thus reducing your coding). Whatever works for you, as I am not sure what you want me to comment towards. My intent was only to provide examples of both the Automation and straight API ways of obtaining the drive information.

Russell
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 16881265

This is getting slightly off track from the original question(s), but...
if minimizing the code is your objective, then you could take the enumerator handling code one step further. It would appear from the example you gave above that Binh's code uses a TObject wrapper around the IEnumVariant. I can't say for sure having never seen his code, but the .Free usage in your code is a pretty good tip off. Take the example below (unit code provided after), which exposes the enumerator as an interface, thus letting delphi automatically release it when it goes out of scope. (the actual implementation is done as an TInterfacedObject).

// Late bound example
procedure TForm1.Button1Click(Sender: TObject);
var  ovFso:         OleVariant;
     ovDrv:         OleVariant;
     pvEnum:        IEnumerator;
begin
  ovFso:=CreateOleObject('Scripting.FileSystemObject');
  pvEnum:=CreateEnumerator(ovFso.Drives);
  while pvEnum.ForEach(ovDrv) do
  begin
     if ovDrv.IsReady then
        ShowMessage(ovDrv.path+' ' +ovDrv.Filesystem)
     else
        ShowMessage(ovDrv.path+' not ready');
  end;
end;

// Early bound example
procedure TForm1.Button2Click(Sender: TObject);
var  objFS:      FileSystemObject;
     objDrv:     IDrive;
     pvEnum:     IEnumerator;
begin
  objFS:=CoFileSystemObject.Create;
  pvEnum:=CreateEnumerator(objFS.Drives);
  while pvEnum.ForEachObject(IDrive, objDrv) do
  begin
     if objDrv.IsReady then
        ShowMessage(objDrv.path+' ' +objDrv.Filesystem)
     else
        ShowMessage(objDrv.path+' not ready');
  end;
end;

Supporting unit:

-- Enumerator.pas --

unit Enumerator;
////////////////////////////////////////////////////////////////////////////////
//
//   Unit        :  Enumerator
//   Author      :  rllibby
//   Date        :  06.11.2006
//   Description :  Wrapper interface around the COM IEnumVariant interface
//
////////////////////////////////////////////////////////////////////////////////
interface

////////////////////////////////////////////////////////////////////////////////
//   Include units
////////////////////////////////////////////////////////////////////////////////
uses
  Windows, SysUtils, Classes, ActiveX, ComObj;

////////////////////////////////////////////////////////////////////////////////
//   Enumerator interface
////////////////////////////////////////////////////////////////////////////////
type
  IEnumerator       =  interface
     function       ForEach(out Obj: OleVariant): Boolean;
     function       ForEachObject(const IID: TGUID; out Obj): Boolean;
     function       Reset: Boolean;
     function       Skip(Count: LongWord): Boolean;
     function       Clone: IEnumerator;
  end;

////////////////////////////////////////////////////////////////////////////////
//   Enumerator function
////////////////////////////////////////////////////////////////////////////////
function   CreateEnumerator(Dispatch: IDispatch): IEnumerator;

implementation

////////////////////////////////////////////////////////////////////////////////
//   Enumerator implementation
////////////////////////////////////////////////////////////////////////////////
type
  TEnumerator       =  class(TInterfacedObject, IEnumerator)
  private
     // Private declarations
     FEnumVariant:  IEnumVariant;
     procedure      GetEnumVariant(Dispatch: IDispatch);
  protected
     // Protected declarations
     function       ForEach(out Obj: OleVariant): Boolean;
     function       ForEachObject(const IID: TGUID; out Obj): Boolean;
     function       Reset: Boolean;
     function       Skip(Count: LongWord): Boolean;
     function       Clone: IEnumerator;
  public
     // Public declarations
     constructor    Create(Dispatch: IDispatch);
     constructor    CreateWrapper(EnumVariant: IEnumVariant);
     destructor     Destroy; override;
  end;

//// Enumerator construction ///////////////////////////////////////////////////
function CreateEnumerator(Dispatch: IDispatch): IEnumerator;
begin

  // Create from interface
  result:=TEnumerator.Create(Dispatch);

end;

//// TEnumerator ///////////////////////////////////////////////////////////////
procedure TEnumerator.GetEnumVariant(Dispatch: IDispatch);
var  pdParams:      TDispParams;
     ovEnum:        OleVariant;
begin

  // Check interface
  if Assigned(Dispatch) then
  begin
     // Clear disp params
     FillChar(pdParams, SizeOf(TDispParams), 0);
     // Get enumerator
     OleCheck(Dispatch.Invoke(DISPID_NEWENUM, GUID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET or DISPATCH_METHOD, pdParams, @ovEnum, nil, nil));
     // Resource protection
     try
        // Check returned interface
        if (TVariantArg(ovEnum).vt = VT_UNKNOWN) then
           // Query interface for the IEnumVariant
           OleCheck(IUnknown(ovEnum).QueryInterface(IEnumVariant, FEnumVariant))
        else
           // Throw error
           OleError(E_NOINTERFACE);
     finally
        // Clear interface
        ovEnum:=Unassigned;
     end;
  end
  else
     // Throw error
     OleError(E_NOINTERFACE);

end;

function TEnumerator.ForEach(out Obj: OleVariant): Boolean;
var  dwFetch:       Cardinal;
begin

  // Get the next item
  result:=(FEnumVariant.Next(1, Obj, dwFetch) = S_OK) and (dwFetch = 1);

end;

function TEnumerator.ForEachObject(const IID: TGUID; out Obj): Boolean;
var  ovItem:        OleVariant;
begin

  // Get next item as OleVariant
  if ForEach(ovItem) then
  begin
     // Resource protection
     try
        // Check interface for IUknown
        if (TVariantArg(ovItem).vt = VT_UNKNOWN) then
           // Query interface for the desired interface
           result:=(IUnknown(ovItem).QueryInterface(IID, Obj) = S_OK)
        // Check interface for IDispatch
        else if (TVariantArg(ovItem).vt = VT_DISPATCH) then
           // Query interface for the desired interface
           result:=(IDispatch(ovItem).QueryInterface(IID, Obj) = S_OK)
        else
        begin
           // Pacify the compiler
           result:=False;
           // Throw error
           OleError(E_NOINTERFACE);
        end;
     finally
        // Clear obtained item
        ovItem:=Unassigned;
     end;
  end
  else
     // Failed to get item
     result:=False;

end;

function TEnumerator.Reset: Boolean;
begin

  // Reset enumerator
  result:=(FEnumVariant.Reset = S_OK);

end;

function TEnumerator.Skip(Count: LongWord): Boolean;
begin

  // Skip items in enumerator
  result:=(FEnumVariant.Skip(Count) = S_OK);

end;

function TEnumerator.Clone: IEnumerator;
var  pvEnum:        IEnumVariant;
begin

  // Clone
  if (FEnumVariant.Clone(pvEnum) = S_OK) then
  begin
     // Resource protection
     try
        // Return wrapper
        result:=TEnumerator.CreateWrapper(pvEnum);
     finally
        // Release interface
        pvEnum:=nil;
     end
  end
  else
     // Return nil
     result:=nil;

end;

constructor TEnumerator.CreateWrapper(EnumVariant: IEnumVariant);
begin

  // Perform inherited
  inherited Create;

  // Check interface pointer
  if Assigned(EnumVariant) then
     // Bind to the passed interface
     FEnumVariant:=EnumVariant
  else
     // Throw error
     OleError(E_NOINTERFACE);

end;

constructor TEnumerator.Create(Dispatch: IDispatch);
begin

  // Perform inherited
  inherited Create;

  // Get enumerator interface
  GetEnumVariant(Dispatch);

end;

destructor TEnumerator.Destroy;
begin

  // Resource protection
  try
     // Release the interface
     FEnumVariant:=nil;
  finally
     // Perform inherited
     inherited Destroy;
  end;

end;

end.


0
 

Author Comment

by:Mutley2003
ID: 16882457
Thanks again Russell

I had no idea about the existence of  enumerators and IEnumVariant until this problem, so I thank you for introducing me to it.

I have NO idea what is going on with
     // Get enumerator
     OleCheck(Dispatch.Invoke(DISPID_NEWENUM, GUID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET or DISPATCH_METHOD, pdParams, @ovEnum, nil, nil));
but I guess I can live with a bit of mystery <g>

I also did a biit more research about IenumVariant now that I know it exists, and found the _NewEnum property .. I don't know if it is universally applicable, but this code works fwiw


procedure TForm1.btnTesDrives6Click(Sender: TObject);
var  objFS:      FileSystemObject;
     ovDrv : olevariant;
     objDrv:     IDrive;
     enum : IenumVariant;
     ItemNum : longword;
// http://www.experts-exchange.com/Programming/Programming_Languages/Delphi/Q_10237914.html?query=Ienumvariant&clearTAFilter=true
begin
  objFS:=CoFileSystemObject.Create;
  enum := Iunknown(objFS.Drives._NewEnum) as IenumVariant;
  while enum.Next(1,ovDrv,ItemNum) = S_OK do begin
    objDrv := Iunknown(ovDrv) as Idrive;
     if objDrv.IsReady then
        ShowMessage(objDrv.path+' ' +objDrv.Filesystem)
     else
        ShowMessage(objDrv.path+' not ready');
  end;
end;


Thanks again for all your help

0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 16882610

Its just the lower level stuff that the language normally shields us from:

OleCheck(Dispatch.Invoke(DISPID_NEWENUM, GUID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET or DISPATCH_METHOD, pdParams, @ovEnum, nil, nil));

tranlsates to your code line of:

objFS.Drives._NewEnum

as the DISPID_NEWENUM (-4) is a standard constant for querying a collections enumerator. The only difference is that in your example of querying "_NewEnum", the object must (be willing to) translate the name "_NewEnum" to a dispatch id using GetIDsOfNames. The code I provided will work when:

- The object uses a name other than _NewEnum to expose the enumerator (not common)
- The object does not directly expose the enumerator via a property name
- The object does not make the _NewEnum property visible to the user

So in regards to a universal way of obtaining a collections enumerator, it would be using the code I provided you.

Regards,
Russell

0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now