Solved

Delphi - Automating Windows Scripting Host

Posted on 2006-06-10
7
1,165 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
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

Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

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…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Finding and deleting duplicate (picture) files can be a time consuming task. My wife and I, our three kids and their families all share one dilemma: Managing our pictures. Between desktops, laptops, phones, tablets, and cameras; over the last decade…

730 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