I use TurboPower onGuard for licensing and there is an option to specify and generate the machine specific keys for license validation. With following code I create machine modifier based on machineID
const
Ckey :TKey = ($56,$1B,$B0,$48,$2D,$AF,$F4,$E5,$30,$6C,$E5,$3B,$A7,$8E,$1A,$14);
procedure TForm1.FormCreate(Sender: TObject);
var
InfoSet : TEsMachineInfoSet;
MachineID : Longint;
begin
{ initialize the machine information set }
InfoSet := [midUser, midSystem, midNetwork, midDrives];
{ create the machine ID and display in hex }
try
MachineID := CreateMachineID(InfoSet);
Edit1.Text := '$' + BufferToHex(MachineID, SizeOf(MachineID));
except on E:Exception do
ShowMessage(E.Message);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
modifier :LongInt;
key :TKey;
releasecode,unlockcode :TCode;
inexpirydate: TdateTime;
begin
modifier := GenerateMachineModifierPrim ;
ApplyModifierToKeyPrim(Modifier, CKey, SizeOf(CKey));
//HexToBuffer(Ckey, key, SizeOf(TKey));
InitRegCode(Ckey, edit1.Text, inExpiryDate, releaseCode);
Edit2.text := BufferToHex(unlockCode, SizeOf(releaseCode));
end;
On the other hand to validate serial number and relase code I use the following code on client side to validate application, I place SerialNumberCode component to implement that.
Ckey :TKey = ($56,$1B,$B0,$48,$2D,$AF,$F4,$E5,$30,$6C,$E5,$3B,$A7,$8E,$1A,$14);
procedure TForm1.OgSerialNumberCode1GetKey(Sender: TObject; var Key: TKey);
begin
Key := Ckey;
end;
procedure TForm1.OgSerialNumberCode1GetModifier(Sender: TObject;
var Value: LongInt);
begin
Value := GenerateMachineModifierPrim;
end;
function TForm1.GetSNData(var S: string): integer;
var
TC : TCode;
SNC : string;
modifier :LOngInt;
begin
try
modifier := GenerateMachineModifierPrim;
ApplyModifierToKeyPrim(Modifier, CKey, SizeOf(CKey));
IniSNVal := StrToInt(InputBox('Serial number', 'Enter serial Value ', '12345678'));
SNC := (InputBox('release code', 'Enter code ', ''));
{Check that Release Code was entered correctly}
HexToBuffer(SNC, TC, SizeOf(TCode));
if not (IsSerialNumberCodeValid(CKey, TC)) then begin
S := 'Release code not entered correctly';
Result := mrCancel;
end else begin
IniFile := TIniFile.Create(TheDir + 'test.INI');
try
IniFile.WriteInteger('Codes', 'SN', IniSNVal);
IniFile.WriteString('Codes', 'SNCode', SNC);
finally
IniFile.Free;
end;
end;
finally
end;
end;
procedure TForm1.OgSerialNumberCode1GetCode(Sender: TObject; var Code: TCode);
var
S1 : string;
L : integer;
begin
if not (FileExists(TheDir + 'test.INI')) then
Exit;
{open Ini File}
IniFile := TIniFile.Create(TheDir + 'test.INI');
try
{try to read release code}
S1 := IniFile.ReadString('Codes', 'SNCode', '');
IniSNVal := IniFile.ReadInteger('Codes', 'SN', 0);
{convert retrieved string to a code}
HexToBuffer(S1, Code, SizeOf(Code));
finally
IniFile.Free;
end;
end;
procedure TForm1.OgSerialNumberCode1Checked(Sender: TObject; Status: TCodeStatus);
var
S,
C1,
C2 : string;
TC : TCode;
LI : longint;
begin
case Status of
ogValidCode : begin
{check if retrieved Serial Number matches Code}
LI := OgSerialNumberCode1.GetValue;
if (LI <> IniSNVal) then begin
Status := ogInvalidCode;
S := 'The serial number has been changed';
end else begin
ShowMessage('Serial #: ' + IntToStr(IniSNVal));
Exit;
end;
end;
ogInvalidCode : begin
{if INI file doesn't exist, presume this is first run}
if not (FileExists(TheDir + 'test.INI')) then
begin
if not (GetSNData(S) = mrCancel) then begin
{Check the SN/ReleaseCode}
OgSerialNumberCode1.CheckCode(True);
{must Exit since line above began a recursive call}
Exit;
end;
end else
S := 'Invalid Code';
end;
ogCodeExpired : S := 'Evaluation period expired';
end;
ShowMessage(S);
Application.Terminate;
end;
But at the client side machine never accepts the validation and throws Release code not entered correctly. Probably I don't understand usage of machineID exmaple in onguard folder. Please also note that the key for client side and generation for relaease code is same so there shouldn't be any mismatch.
Related
I have tried to get my SQLServer instances names from the registry by using this code:
type
TRegistryHelper = class helper for TRegistry
public
function ReadMultiSz(const name: string; var Strings: TStrings): boolean;
end;
function TRegistryHelper.ReadMultiSz(const name: string;
var Strings: TStrings): boolean;
var
iSizeInByte: integer;
Buffer: array of WChar;
iWCharsInBuffer: integer;
z: integer;
sString: string;
begin
iSizeInByte := GetDataSize(name);
if iSizeInByte > 0 then begin
SetLength(Buffer, Floor(iSizeInByte / sizeof(WChar)));
iWCharsInBuffer := Floor(ReadBinaryData(name, Buffer[0], iSizeInByte) / sizeof(WChar));
sString := '';
for z := 0 to iWCharsInBuffer do begin
if Buffer[z] <> #0 then begin
sString := sString + Buffer[z];
end else begin
if sString <> '' then begin
Strings.Append(sString);
sString := '';
end;
end;
end;
result := true;
end else begin
result := false;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
const
cKey = '\SOFTWARE\Microsoft\Microsoft SQL Server';
var
Registry: TRegistry;
MyList: TStrings;
begin
Registry := TRegistry.Create;
Registry.RootKey := HKEY_LOCAL_MACHINE;
if Registry.OpenKeyReadOnly(cKey) then
try
MyList := TStringList.Create();
Registry.ReadMultiSz('InstalledInstances', MyList);
ListBox1.Items.Assign(MyList);
finally
MyList.Free;
end;
Registry.Free;
end;
But I have noted the iSizeInByte = -1 every time, and I failed to get those names by this way.
Also, I have noted that when trying to get those instances from my TADOQuery connection string builder, the component also failed to get those names.
Is there any way to get them?
I have the following JSON structure, calling it from web:
[
{
"Code":"31212",
"Desc":"JOHN",
"DOYDESC":"MADRID",
"Street":"Str41",
"StreetNo":"86"
},
{
"Code":"30214",
"Desc":"GEORGE",
"DOYDESC":"NEW YORK",
"Street":"Str3",
"StreetNo":null
},
{
"Code":"09215",
"Desc":"MARY",
"DOYDESC":"PARIS",
"Street":"Str3",
"StreetNo":"22"
},
{
"Code":"10217",
"Desc":"MEGAN",
"DOYDESC":"ROME",
"Street":"Str4",
"StreetNo":null
}
]
I want the user to give a number in Edit.Text, and then search the values of Code to find if the given value exists.
Is there a way to create an array named Code with the values from the JSON?
For example, to create something like this:
Code = ["31212","30214","09215","10217"]
and then make the search? Or, is there another way to find a specific value of Code from the JSON? I tried this so far:
procedure Tlog_form.Button2Click(Sender: TObject);
var
jso : TJsonObject;
jsv,jsval : TJsonValue;
jsa,jsarr : TJsonArray;
data,str : string;
i,j,user : integer;
jsp : TJsonPair;
arr : array of string;
begin
try
data := GetURLAsString('http://...');
except
on E: exception do
end;
try
jsv := TJSONObject.ParseJSONValue(data);
try
jsa := jsv as TJSONArray;
try
for I := 0 to jsa.Size - 1 do
begin
jso := jsa.Get(i) as TJsonObject;
if jso.ToString = 'Code' then
begin
str := jso.GetValue('Code').ToString;
if Edit1.Text = str then
begin
ShowMessage('found it');
end;
end;
end;
finally
end;
finally
jsv.Free;
end;
except
on E: exception do
end;
end;
When I debug, it doesn't get any error. but it still doesn't work.
You are converting each TJSONObject in the array to a string, and then comparing that to your TEdit text. Which will obviously not match. You need to compare only the Code field of each TJSONObject, which you would be doing if you remove the if jso.ToString = 'Code' then check completely, eg:
procedure Tlog_form.Button2Click(Sender: TObject);
var
jsv : TJSONValue;
jsa : TJSONArray;
jso : TJSONObject;
data, str, code : string;
I : integer;
begin
str := Edit1.Text;
try
data := GetURLAsString('http://...');
jsv := TJSONObject.ParseJSONValue(data);
if jsv <> nil then
try
jsa := jsv as TJSONArray;
for I := 0 to jsa.Size - 1 do
begin
jso := jsa.Get(I) as TJSONObject;
code := jso.GetValue('Code').ToString;
if str = code then
begin
ShowMessage('found it');
end;
end;
finally
jsv.Free;
end;
except
end;
end;
I'm not sure I understand you very well, but if you try TALJsonDocument (https://github.com/Zeus64/alcinoe), you have this function:
Function ALFindJsonNodeByTextChildNodeValue(const JsonNode:TalJsonNode;
Const ChildNodeName: AnsiString;
Const ChildNodeValue : AnsiString;
Const Recurse: Boolean = False): TALJsonNode;
Here's the current problem:
Executing an uninstall string with command line parameters following this solution:
Here's what we have in the form of non-functioning code:
const MAX_PATH = 260;
function GetUninstallString(): TArrayOfString;
var
sUnInstPath: String;
sUnInstallString: String;
sUnInstallStringPrm: String;
begin
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#MyAppName}');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
if sUnInstallString = '' Then
Begin
sUnInstPath := ExpandConstant('Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{#MyAppName}');
RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString);
End;
if sUnInstallString <> '' Then
Begin
sUnInstallStringPrm:= copy(sUnInstallString, pos(' ', sUnInstallString) + 1, MAX_PATH);
MsgBox(sUnInstallStringPrm, mbInformation, MB_OK);
Delete(sUnInstallString, pos(' ', sUnInstallString), MAX_PATH);
End;
Result := [sUnInstallString, UnInstallStringPrm];
end;
/////////////////////////////////////////////////////////////////////
function IsUpgrade(): Boolean;
begin
Result := (GetUninstallString() <> '');
end;
/////////////////////////////////////////////////////////////////////
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
// Return Values:
// 1 - uninstall string is empty
// 2 - error executing the UnInstallString
// 3 - successfully executed the UnInstallString
// default return value
Result := 0;
// get the uninstall string of the old app
sUnInstallString := GetUninstallString();
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if ShellExec('open', sUnInstallString, '','', SW_SHOW, ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
/////////////////////////////////////////////////////////////////////
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;
There is very little info on how to use GetUninstallString in this context. Possible at all?
Not sure if, as an alternative, the string array containing sUnInstallString... can be passed as a parameter within the above fixture, albeit the return type as array appears to fit a little better.
The GetUninstallString is used twice in the code.
In IsUpgrade
and in UnInstallOldVersion.
In IsUpgrade it's only tested for non-empty value.
In UnInstallOldVersion it is executed.
So make the function return whole UninstallString as a simple string.
For IsUpgrade it is enough.
And do the actual parsing to program path and its arguments in the UnInstallOldVersion only.
Also you should:
Handle a situation where there's no space in the string (no parameters)
The path to the uninstaller can contain spaces too (and it typically will as the uninstallers tend to be in Program Files).
I have buttons which when clicked store their caption in a TStringList
ChairList : TStringList;
procedure TForm1.FormCreate(Sender: TObject);
begin
ChairList := TStringList.Create;
end;
An example of one button is:
procedure TForm1.Table17Click(Sender: TObject);
begin
Label1.Visible := false;
if (BottomPanel.Visible = false) then
begin
Label1.Visible := true;
LastClicked := 'Table 17';
ChairList.Add(LastClicked);
But when I read the file I get no result back. I tested it by using a ShowMessage just to see if anything would be read and I just get a blank ShowMessage
The following code is the save procedure:
Procedure TForm1.SaveToFile(Const Filename : String);
Var
INI : TMemIniFile;
Procedure SaveReserve();
var
section : String;
Begin
Section := 'Table';
ini.writeString(Section, 'LastClickedID', LastClicked);
End;
begin
Ini := Tmeminifile.Create(filename);
ini.Clear;
try
SaveReserve();
Ini.UpdateFile;
finally
Ini.Free;
end;
end;
Then the next Procedure is the load file:
Procedure TForm1.LoadFile(const Filename : String);
var
INI : TMemInifile;
Sections : TStringList;
i : Integer;
LastClickedID : String;
Procedure LoadChair(Const Section: String);
Begin
LastClickedID := INI.ReadString(Section, 'LastClickedID', LastClicked)
End;
Begin
ChairList.Clear;
INI := TMEMINIFILE.Create(Filename);
Try
Sections := TStringList.Create;
Try
Ini.ReadSections(Sections);
for i := 0 to (Sections.Count - 1) do
Begin
if startstext('LastClickedID', Sections[I]) then
begin
LastClickedID := (copy(Sections[I], 12, MaxInt));
end;
End;
Finally
Sections.Free;
End;
Finally
INI.Free;
end;
ShowTable(LastClickedID);
end;
ShowTable(LastClickedID); is just the part where i test it
How can i make it so it saves the caption and then when i load it up it shows any table caption saved?
I only need it to save the caption of an object selected. When the user selects a button it adds the caption to the stringlist which is then read to the ini file
try this code!
Procedure for write ini:
procedure write_ini;
var
ini:TIniFile;
begin
ini:=TIniFile.Create('MainForm.ini');
ini.WriteInteger('FORM', 'Top', MainForm.Top);
ini.WriteInteger('FORM', 'Left', MainForm.Left);
ini.WriteInteger('FORM', 'Width', MainForm.Width);
ini.WriteInteger('FORM', 'Height', MainForm.Height);
ini.WriteString('USER', 'Name', MainForm.userName.Text);
ini.WriteInteger('USER','Pol', MainForm.Combo.ItemIndex);
ini.Free;
end;
Procedure for read ini:
procedure read_ini;
var
ini:TIniFile;
begin
ini:=TiniFile.Create('MainForm.ini');
MainForm.Top:=ini.ReadInteger('FORM', 'Top', 0);
MainForm.Left:=ini.ReadInteger('FORM', 'Left', 0);
MainForm.Width:=ini.ReadInteger('FORM', 'Width', 226);
MainForm.Height:=ini.ReadInteger('FORM', 'Height', 123);
MainForm.userName.Text:=ini.ReadString('USER', 'Name', 'Anonim');
MainForm.Combo.ItemIndex:=ini.ReadInteger('USER', 'Pol', 1);
ini.Free;
end;
Your code fails to load the value back because you are trying to read it from the wrong area of the INI file.
You are writing the value to the 'LastClickedID' entry of the 'Table' section, which produces this INI data:
[Table]
LastClickedID=Value
But then you are trying to read the value from the 'LastClickedID' entry of any section that begins with the 'LastClickedID' prefix, not from the 'Table' section that you previously wrote to. That would only work if the INI data looked more like this instead:
[LastClickedID]
LastClickedID=Value
Since that section does not exist in the INI, that is why your LastClickedID variable is always blank (if LastClicked is also blank to begin with).
You don't need to use Ini.ReadSections() in this example. Just use Ini.ReadString() by itself with the proper 'Table' section name, eg:
procedure TForm1.LoadFile(const Filename : String);
var
Ini : TMemIniFile;
LastClickedID: string;
procedure LoadReserve;
var
section : String;
begin
Section := 'Table';
LastClickedID := Ini.ReadString(Section, 'LastClickedID', LastClicked);
end;
begin
Ini := TMemIniFile.Create(Filename);
try
LoadReserve;
finally
Ini.Free;
end;
ShowTable(LastClickedID);
end;
In general, you should be mirroring the opposite of whatever your SaveToFile() code is doing.
I advise you to read this documentation and example to use TMemIniFile and TIniFile. It should be helpful...
Here is my code :
Procedure TForm1.SaveToFile(Const Filename : String);
Var
Ini : TIniFile;
begin
Ini := TIniFile.Create(filename);
try
Ini.WriteString('Table', 'LastClickedID', LastClicked);
finally
Ini.Free;
end;
end;
Procedure TForm1.LoadFile(const Filename : String);
var
Ini : TInifile;
LastClickedID : String;
begin
Ini := TIniFile.Create(Filename);
try
LastClickedID := Ini.ReadString('Table', 'LastClickedID', '');
finally
Ini.Free;
end;
ShowTable(LastClickedID);
end;
You can create a general ini file utility object, which can read/write under the predefined key, the properties collected by a dictionary object. This is a flexible, reusable solution.
You can collect the clicked control captions into the dictionary and save the list at the end of your process. You should use a dictionary because a key/value pair needed for an ini file operation.
uses
Generics.Collections
, System.IniFiles
;
TIniFileUtility = class
public
class read( ini_ : TIniFile; keyName_ : string; props_ : TDictionary<string,string> );
class write( ini_ : TIniFile; keyName_ : string; props_ : TDictionary<string,string> );
end;
class procedure TIniFileUtility.write( ini_ : TIniFile; keyName_ : string; props_ : TDictionary<string,string>);
var
i : integer;
key, value : string;
begin
//... some input parameter checking
for key in props_.keys do
begin
value := props_.Items[key];
ini_.writeString( keyName_, key, value );
end;
end;
procedure caller(Sender: TObject);
var
ini : TIniFile;
props : TDictionary<string,string>;
begin
ini := TIniFile.create( 'c:\temp\defaults.ini' );
try
props := TDictionary<string,string>.Create;
try
props.Add( 'key1', 'stringValue' );
props.Add( 'key2', intToStr( 2 ) );
TIniFileUtility.write( ini, '\x\y\z\', props );
finally
props.Free;
props := NIL;
end;
finally
ini.Free;
ini := NIL;
end;
end;
I want to select more than one text file in the windows explorer and open the files via context menu in my app. For one file I found the solution but for more files there some ideas but no (working) solutions.
Anyone here that has the answer?
Here is an example that i've just searched and collected from internet.
Aim: Select multiple folders in Windows Explorer and get list of these folders' names via a shell context menu item "SelectedFolders", or using SendTo menu or drag-and-drop folders from shell onto the application form.
Please put a listbox named lstSelectedFolders and a speed button named sbClearList.
The main form name is frmSelectedFolders.
Here we go.
/////////////////////////////////////////////////////////////
program selectedfolders;
uses
Windows, Messages, SysUtils, Forms,
uSelectedFolders in 'uSelectedFolders.pas' {frmSelectedFolders};
{$R *.res}
var
receiver: THandle;
i, result: integer;
s: string;
dataToSend: TCopyDataStruct;
Mutex : THandle;
begin
Mutex := CreateMutex(nil, True, 'SelectedFolders');
if (Mutex <> 0) and (GetLastError = 0) then
begin
Application.Initialize;
Application.Title := 'Selected Folders';
Application.CreateForm(TfrmSelectedFolders, frmSelectedFolders);
Application.Run;
if Mutex <> 0 then CloseHandle(Mutex);
end
else
begin
receiver := FindWindow(PChar('TfrmSelectedFolders'), PChar('Selected Folders'));
if receiver <> 0 then
begin
for i:=1 to ParamCount do
begin
s := trim(ParamStr(i));
if s <> '' then
begin
dataToSend.dwData := 0;
dataToSend.cbData := 1 + Length(s);
dataToSend.lpData := PChar(s);
result := SendMessage(receiver, WM_COPYDATA, Integer(Application.Handle), Integer(#dataToSend));
//sleep(100);
//if result > 0 then
// ShowMessage(Format('Sender side: Receiver has %d items in list!', [result]));
end;
end; // for i
end;
end;
end.
/////////////////////////////////////////////////////////////
unit uSelectedFolders;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ShellAPI, ActiveX, ComObj, ShlObj, Registry, Buttons;
type
TfrmSelectedFolders = class(TForm)
lstSelectedFolders: TListBox;
sbClearList: TSpeedButton;
procedure FormCreate(Sender: TObject);
procedure sbClearListClick(Sender: TObject);
private { Private declarations }
public { Public declarations }
procedure WMDROPFILES(var Message: TWMDROPFILES); message WM_DROPFILES;
procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
function GetTarget(const LinkFileName: string): string;
end;
var
frmSelectedFolders: TfrmSelectedFolders;
implementation
{$R *.dfm}
procedure RegisterContextMenuForFolders();
const
Key = 'Directory\shell\SelectedFolders\command\';
begin
with TRegistry.Create do
try
// for all users, class registration for directories
RootKey := HKEY_CLASSES_ROOT;
if OpenKey(Key, true) then
WriteString('', '"' + Application.ExeName + '" "%l"');
finally
Free;
end;
end;
procedure TfrmSelectedFolders.WMDROPFILES(var Message: TWMDROPFILES);
var
N, i: integer;
buffer: array[0..255] of Char;
s: string;
begin
try
N := DragQueryFile(Message.Drop, $FFFFFFFF, nil, 0);
for i:=1 to N do
begin
DragQueryFile(Message.Drop, i-1, #buffer, SizeOf(buffer));
s := buffer;
if UpperCase(ExtractFileExt(s)) = '.LNK' then
s := GetTarget(s);
if lstSelectedFolders.Items.IndexOf(s) < 0 then
lstSelectedFolders.Items.Add(s);
end;
finally
DragFinish(Message.Drop);
end;
end;
function TfrmSelectedFolders.GetTarget(const LinkFileName: string): string;
var
//Link : String;
psl : IShellLink;
ppf : IPersistFile;
WidePath : Array[0..260] of WideChar;
Info : Array[0..MAX_PATH] of Char;
wfs : TWin32FindData;
begin
if UpperCase(ExtractFileExt(LinkFileName)) <> '.LNK' then
begin
Result := 'NOT a shortuct by extension!';
Exit;
end;
CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IShellLink, psl);
if psl.QueryInterface(IPersistFile, ppf) = 0 Then
Begin
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PChar(LinkFileName), -1, #WidePath, MAX_PATH);
ppf.Load(WidePath, STGM_READ);
psl.GetPath(#info, MAX_PATH, wfs, SLGP_UNCPRIORITY);
Result := info;
end
else
Result := '';
end;
procedure TfrmSelectedFolders.WMCopyData(var Msg: TWMCopyData);
var
s: string;
begin
s := trim(PChar(Msg.copyDataStruct.lpData));
if s = '' then
begin
msg.Result := -1;
exit;
end;
if UpperCase(ExtractFileExt(s)) = '.LNK' then
s := GetTarget(s);
if lstSelectedFolders.Items.IndexOf(s) < 0 then
lstSelectedFolders.Items.Add(s);
msg.Result := lstSelectedFolders.Items.Count;
end;
procedure TfrmSelectedFolders.FormCreate(Sender: TObject);
var
i: integer;
s: string;
begin
RegisterContextMenuForFolders();
DragAcceptFiles(Handle, TRUE);
lstSelectedFolders.Clear;
s := ExtractFileDir(Application.ExeName);
lstSelectedFolders.Items.Add(s);
for i:=1 to ParamCount do
begin
s := trim(ParamStr(i));
if UpperCase(ExtractFileExt(s)) = '.LNK' then
s := GetTarget(s);
if lstSelectedFolders.Items.IndexOf(s) < 0 then
lstSelectedFolders.Items.Add(s);
end;
end;
procedure TfrmSelectedFolders.sbClearListClick(Sender: TObject);
begin
lstSelectedFolders.Clear;
end;
end.