Archive

Archive for April, 2014

Getting a list of mapped network drives

April 26, 2014 Leave a comment

Continuing the theme of mapped network drives (see earlier post about how to connect a network drive by code), here is another class that is quite helpful. In short, it simply queries the operative system for mapped network paths and their drive letter.

Very simply to use: just create an instance, call the refresh method (this is called automatically by the constructor) and use the properties to traverse the list.

 


  TQTXNetworkDriveList = Class(TObject)
  Private
    FObjects:   TStringList;
    Function    GetCount:Integer;
    Function    GetItem(index:Integer):String;
    Function    GetDriveLetter(index:Integer):String;
    Function    GetMapPath(index:Integer):String;
  Public
    Property    Count:Integer read GetCount;
    Property    DriveLetter[index:Integer]:String read GetDriveLetter;
    Property    MapPath[index:Integer]:String read GetMapPath;
    Property    Items[index:Integer]:String read GetItem;default;
  Public
    Procedure   Refresh;
    function    toString:String;override;
    Procedure   AfterConstruction;override;
    Constructor Create;
    Destructor  Destroy;Override;
  End;

//##########################################################################
// TQTXNetworkDriveList
//##########################################################################

Constructor TQTXNetworkDriveList.Create;
Begin
  inherited;
  FObjects:=TStringList.Create;
end;

Destructor TQTXNetworkDriveList.Destroy;
Begin
  FObjects.free;
  inherited;
end;

Procedure TQTXNetworkDriveList.AfterConstruction;
Begin
  inherited;
  Refresh;
end;

function TQTXNetworkDriveList.toString:String;
begin
  result:=FObjects.Text;
end;

Function TQTXNetworkDriveList.GetDriveLetter(index:Integer):String;
Begin
  result:=FObjects.Names[index] + ':';
end;

Function TQTXNetworkDriveList.GetMapPath(index:Integer):String;
Begin
  result:=FObjects.Values[FObjects.Names[index]];
end;

Function TQTXNetworkDriveList.GetCount:Integer;
Begin
  result:=FObjects.Count;
end;

Function TQTXNetworkDriveList.GetItem(index:Integer):String;
Begin
  result:=GetDriveLetter(index) + getMapPath(index);
end;

Procedure TQTXNetworkDriveList.Refresh;
const
  CNT_DRIVEMASK = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var
  i:      integer;
  mPath:  string;
  mLen:   DWord;
  mDrive: String;
Begin
  FObjects.Clear;
  for i:=1 to length(CNT_DRIVEMASK) do
  Begin
    mDrive:=CNT_DRIVEMASK[i] + ':';
    mLen := MAX_PATH;
    SetLength(mPath,MAX_PATH);
    If NO_ERROR=WNetGetConnection(PChar(mDrive),PChar(mPath),mLen) then
    FObjects.Values[CNT_DRIVEMASK[i]]:=copy(mPath,1,mLen);
  end;
end;


Map network path by code

April 25, 2014 11 comments

Ever wanted to map a network path by code? I mean, instead of mapping a network path to a drive letter, like you can do with using Windows Explorer. What If you could map a known network location without any other program or even Windows Explorer knowing about it? Well, this is what my little class does.

This class came about many years ago while I was coding a collection of services meant to receive, process and transport data to various places in a huge network. The win32 services all ran in the background (like services do) and there was no way to ensure that a specific network path was mapped and ready to use.

Map paths without drive letters

Map paths without drive letters

Is it useful?

Let me demonstrate with a simple task. You are hired to create a win32 service that accepts data on a listening Indy server. This data should be processed and stored in 3 locations on the network (a huge network). The user account that executes your service will have access rights for these 3 locations, but there is one problem — the locations can change without any warning – and the network entrypoint-paths are stored in the registry only.

So, how do you solve this? You can’t hardcode the target locations (you should avoid hardcoding as much as possible at all times), you can’t keep mapping network paths to drive-letters, because it will be a complete mess, and since stuff can change at any time — you need to map the path for each call to the service to be safe.

The answer is to map the network paths by code, with no drive letter involved. And here is a mini-class that does this!


  TQTXNetworkPath = Class(TObject)
  Private
    FHostName:    String;
    FRemotePath:  String;
    FUser:        String;
    FPassword:    String;
    FConnected:   Boolean;
    FOwned:       Boolean;
    FLastError:   String;
    FFailed:      Boolean;
    FUNCData:     packed array[0..4096] of char;
    FURI:         String;
  protected
    procedure     ClearLastError;
    Procedure     setLastError(aValue:String);
  Public
    Property      Active:Boolean read FConnected;
    Property      HostName:String read FHostName;
    Property      NetworkPath:String read FRemotePath;
    Property      LastError:String read FLastError;
    Property      Failed:Boolean read FFailed;

    Function      getRelativePath(aFilename:String):String;

    Function      Connect(aHostName:String;
                  aNetworkPath:String;
                  const aUsername:String='';
                  const aPassword:String=''):Boolean;
    Procedure     Disconnect(const aForce:Boolean=False);
    Destructor    Destroy;override;
  End;

//##########################################################################
// TQTXNetworkPath
//##########################################################################

Destructor TQTXNetworkPath.Destroy;
Begin
  if FConnected then
  Disconnect(True);
  inherited;
end;

function TQTXNetworkPath.Connect(aHostName, aNetworkPath: String;
         const aUsername, aPassword: String): Boolean;
var
  mNet: TNetResource;
  mRes: Cardinal;
  mTxt: String;
begin
  clearLastError;
  result:=False;
  aHostName:=trim(aHostName);
  aNetworkPath:=trim(aNetworkPath);

  if length(aHostName)>0 then
  Begin
    (* Already connected? Disconnect and force closing of all files *)
    if FConnected then
    Disconnect(true);

    (* Build complete network path *)
    if length(aNetworkPath)>0 then
    FURI:='\\' + aHostname else
    FURI:=format('\\%s\%s',[aHostName,aNetworkPath]);

    (* Initialize UNC path *)
    ZeroMemory(@FUNCData,SizeOf(FUNCData));
    Move(pointer(@FURI[1])^,pointer(@FUNCData)^,length(FURI) * SizeOf(char) );

    (* initialize network resource data *)
    ZeroMemory(@mNet,SizeOf(mNet));
    mNet.dwScope:=RESOURCE_GLOBALNET;
    mNet.dwType:=RESOURCETYPE_DISK;
    mNet.dwDisplayType:=RESOURCEDISPLAYTYPE_GENERIC;
    mNet.dwUsage:=RESOURCEUSAGE_CONNECTABLE;
    mNet.lpRemoteName:=@FUNCData;

    (* Now attempt to connect the sucker *)
    mRes:=WNetAddConnection2(mNet,
          pchar(aPassword),
          pchar(aUserName),CONNECT_TEMPORARY);
    if mRes<>NO_ERROR then
    Begin
      ZeroMemory(@FUNCData,SizeOf(FUNCData));
      setLength(FURI,0);

      Case mRes of
      ERROR_ACCESS_DENIED:            mTxt:='Access_denied:';
      ERROR_ALREADY_ASSIGNED:         mTxt:='Already assigned:';
      ERROR_BAD_DEV_TYPE:             mTxt:='Bad device type:';
      ERROR_BAD_DEVICE:               mTxt:='Bad device:';
      ERROR_BAD_NET_NAME:             mTxt:='Bad network Name:';
      ERROR_BAD_PROFILE:              mTxt:='Bad profile:';
      ERROR_BAD_PROVIDER:             mTxt:='Bad provider:';
      ERROR_BUSY:                     mTxt:='Busy:';
      ERROR_CANCELLED:                mTxt:='Canceled:';
      ERROR_CANNOT_OPEN_PROFILE:      mTxt:='Cannot_open_profile:';
      ERROR_DEVICE_ALREADY_REMEMBERED:mTxt:='Device_already_remembered:';
      ERROR_EXTENDED_ERROR:           mTxt:='Extended error:';
      ERROR_INVALID_PASSWORD:         mTxt:='Invalid password:';
      ERROR_NO_NET_OR_BAD_PATH:       mTxt:='No_Net_or_bad_path:';
      ERROR_NO_NETWORK:               mTxt:='No_Network:';
      end;
      setLastError(Format('Failed to connect [%s], %s',
      [mTxt,SysErrorMessage(mRes)]));
    end else
    Begin
      FOwned:=True;
      FConnected:=True;
      FHostName:=aHostName;
      FUser:=aUserName;
      FPassword:=aPassword;
      FRemotePath:=aNetworkPath;
    end;
  end else
  setLastError('Failed to connect, invalid hostname error');
end;

procedure TQTXNetworkPath.Disconnect(const aForce: Boolean);
var
  mRes: Integer;
  mCount: Integer;
Begin
  (* Close network connection if connected *)
  If (FConnected=True) and (FOwned=True) then
  Begin

    (* Attempt to close the connection *)
    mRes:=WNetCancelConnection(@FUNCData,aForce);

    (* Since files can be open temporarily, we try to
       wait a little before we re-try to close again.
       A maximum of 100 attempts is set *)
    if mRes=ERROR_OPEN_FILES then
    Begin
      mCount:=0;
      While mRes=ERROR_OPEN_FILES do
      Begin
        inc(mCount);
        if mCount=100 then
        break;
        Sleep(100);
        mRes:=WNetCancelConnection(@FUNCData,aForce);
      end;
    end;

    FConnected:=False;
    FOwned:=False;
    setLength(FHostName,0);
    setLength(FRemotePath,0);
    setLength(FUser,0);
    setLength(FPassword,0);
    setLength(FURI,0);
  end;
end;

procedure TQTXNetworkPath.ClearLastError;
Begin
  FFailed:=False;
  setLength(FLastError,0);
end;

Procedure TQTXNetworkPath.setLastError(aValue:String);
Begin
  FLastError:=trim(aValue);
  FFailed:=Length(FLastError)>0;
end;

Function TQTXNetworkPath.getRelativePath(aFilename:String):String;
Begin
  if FConnected then
  result:=FURI + aFilename else
  result:=aFilename;
end;

How does it work?

While the class should be self-explanatory (and remember, if you are to lazy to use MSDN to read about what WinAPI calls do, then you really should work harder to be a good programmer), here are a few words about the workings.

First, the relative path stuff.

As you probably know, network paths are not like ordinary local paths. A local fixed path can be something like:

 C:\MyFolder\Nextfolder\ThirdFolder\Somefile.dat

A network path is more abstract and designed to be relative to a mount-point:

 $MOUNTPOINT\MyFolder\Nextfolder\ThirdFolder\Somefile.dat

In many cases the mountpoint ($MOUNTPOINT above) doesnt even have to be included, you can reference the machine name directly, like this:

 \\somePC\shared folders\myshared folder\Somefile.dat

In other words, when working with paths that can change at all times, you really want to work exclusively with relative paths. Directories that are relative to the folder you are mounting.

When using the class above, the first method you want to use is the Connect() method. As you see it accepts two parameters: the name of the machine (server), followed by the network path you want to use. It also has two optional parameters for username and password. Note: The username and password is for the account you want to mount the drive for. It doesnt have to be your current account. In some network setups it can be wise to isolate access via proxy accounts to better control who has access to what.

Anyways, this network path is your entry-point (mount point) and whatever files you operate on are, naturally, relative to that. To know the relative path for a file, use the method getRelativePath().

Really not more to say about the class. It takes care of everything and is compatible with findFirst/findNext file-searching (all things normal windows and pascal). Once mounted, the mountpoint can be used anywhere in your process.

Hexlicense component suite almost ready

April 4, 2014 1 comment

I am proud to announce the nearby release of my license tracking software for Delphi and C++ builder. The software is published under Fuerza development and will be available for purchase shortly.

Integrates with the Delphi IDE

Integrates with the Delphi IDE

The suite is compatible with Delphi 7 – XE5, 32 and 64 bit. It is written in standard Delphi and is thus platform independent – with no dependencies or external libraries.

Generating and manage your license numbers with ease

Generate and manage your license numbers with ease

Introduction

You have a killer application and want to get it onto the marketplace. But how will you deal with serial numbers? How will do handle trial periods? How will you generate serial numbers in bulk, which online vendors need in order to sell your product?

This is where the license manager for Delphi comes in. Turning your application into a trial version is now a matter of adding some components, setting a few properties – and then deciding what to disable in your code during the trial. With plenty of events and straight forward mechanisms, the license manager component package will save you a lot of time!

What does it do?

Component based, easy to use

Component based, easy to use

Our components is about serial number generation, validation and management of trial periods from within your Delphi application. How you store the license data (file and registry storage components ship with the package) is up to you. If you want to handle the license data yourself, just drop a THexOwnerLicenseStorage component and take charge via the event handlers.

This model gives you the benefit of not having to deal with the low-level stuff (like encryption, data signatures, date and time checking, file validation etc). You are not boxed into “our” way of doing things.

If you have a custom server solution or ordering system online, you can easily extend serial-number validation by calling your server to check usage. You can also use the license generator to create as many serial numbers (based on a root key) as you wish. These lists can be uploaded to your webshop which takes care of delivery to your customers.

Components

The entire package consists of five non-visual components and the license generator application. Starting to use the package is a matter of dropping the desired components on a form, starting the generator application, generating a root key and a bulk of serial numbers, pasting the root-key into your code and setting the trial mode. That is more or less all you need to do for the most basic setup. It is highly recommended that you disable some features of your application when running under trial. This will encourage users to buy your application to enjoy it in full.

Generate up to 10.000 keys per root key

Generate up to 10.000 keys per root key

THexLicense is the central component. It allows you to define the trial mode (fixed time range, day trial, run trial) and it’s values. You can also set it to automatically start on loading, which means you don’t have to manually call the component for it to initialize. This component must be connected to a THexSerialNumber component and a THexLicenseStorage adapter.

THexSerialNumber deals exclusively with the serial number. In order for this component to do its work it must be connected to a THexSerialMatrix component.

THexSerialMatrix is the component that handles the “root key”. You provide the root-key via an event handler. The root key is generated with the serial-number generator application that ships with the package (you can also write your own).

THexLicenseStorage is a base component from which THexFileLicenseStorage, THexRegistryStorage and THexOwnerStorage adapters inherit. This is where reading and writing of the actual license data is handled. As the names imply the registry and file adapters are pre-configured to store data in either the windows registry or on disk. If you want to handle the storage yourself (which most people prefer) you can drop a THexOwnerStorage component on your form or datamodule, and take charge via the OnReadData, OnWriteData and OnDataExists events.

THexSerialGenerator is the component used by the generator application to generate serial numbers in bulk. It exists as a component so it can be easily added server-side if you already have an existing ordering system online.

Use the built-in dialog or take charge yourself

Use the built-in dialog or take charge yourself