Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Создание hardlink и symbolic link

01.01.2007
{ **** UBPFD *********** by kladovka.net.ru ****
>> Создание hardlink и symbolic link.
 
Исходный код утилиты, которая создает hard и symbolic links почти как в unix.
Hardlink можно создать только для файлов и только на NTFS.
Symbolic link можно создать только для директориев и только на
NTFS5
(Win2K/XP) и он не может указывать на сетевой ресурс.
 
Зависимости: Windows, SysUtils
Автор:       Alex Konshin, akonshin@earthlink.net, Boston, USA
Copyright:   http://home.earthlink.net/~akonshin/files/xlink.zip
Дата:        30 декабря 2002 г.
********************************************** }
 
program xlink
;
 
uses
 
Windows, SysUtils;
 
{$APPTYPE CONSOLE}
{$R xlink.res}
type
 
 
TOptions = set of (optSymbolicLink,optOverwrite,optRecursive,optDirectory);
 
  int64rec
= packed record
    lo
: LongWord;
    hi
: LongInt;
 
end;
 
const
  FILE_DOES_NOT_EXIST
= DWORD(-1);
 
//=============================================================
function isFileExists( const AFileName: String ): Boolean;
var
  h
: THandle;
  rFindData
: TWin32FindData;
begin
  h
:= Windows.FindFirstFile( PChar(AFileName), rFindData );
 
Result := h<>INVALID_HANDLE_VALUE;
 
if not Result then Exit;
 
Windows.FindClose(h);
 
Result := ( rFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY ) = 0;
end;
//-------------------------------------------------------------
// warning: function assumes that it is correct directory name
function isDirectoryEmpty( const ADirectoryName: String ): Boolean;
var
  h
: THandle;
  len
: Integer;
  rFindData
: TWin32FindData;
  sSeachMask
: String;
begin
  len
:= Length(ADirectoryName);
 
if (PChar(ADirectoryName)+len-1)^='\' then sSeachMask := ADirectoryName+'*'
  else sSeachMask := ADirectoryName+'
\*';
  h := Windows.FindFirstFile( PChar(sSeachMask), rFindData );
  Result := (h=INVALID_HANDLE_VALUE);
  Windows.FindClose(h);
end;
 
//-------------------------------------------------------------
function SysErrorMessage( ErrorCode: Integer ): string;
var
  Len: Integer;
  Buffer: Array[0..255] of Char;
begin
  Len := FormatMessage(
    FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_ARGUMENT_ARRAY,
    nil, ErrorCode, 0, Buffer, SizeOf(Buffer), nil );
  while (Len>0) and (Buffer[Len-1] in [#0..#32, '
.']) do Dec(Len);
  SetString( Result, Buffer, Len );
end;
 
//-------------------------------------------------------------
procedure _CreateHardlink( AFileName : String; AFileWCName : PWideChar; ALinkName: String; overwrite: Boolean );
var
  aLinkWCFileName, aLinkFullName: Array[0..MAX_PATH] of WChar;
  pwFilePart: LPWSTR;
  hFileSource: THandle;
  rStreamId: WIN32_STREAM_ID;
  cbPathLen, dwStreamHeaderSize, dwBytesWritten: DWORD;
  lpContext: Pointer;
begin
  StringToWidechar( ALinkName, aLinkWCFileName, MAX_PATH );
 
  hFileSource :=
    Windows.CreateFile(
      PChar(AFileName),
      GENERIC_READ or GENERIC_WRITE,
      FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
      nil,
      OPEN_EXISTING,
      0,
      0
    );
 
  if hFileSource=INVALID_HANDLE_VALUE then
    raise Exception.Create('
Cannot open file "'+AFileName+'"');
 
  try
    cbPathLen := Windows.GetFullPathNameW( aLinkWCFileName, MAX_PATH,
      aLinkFullName, pwFilePart );
    if cbPathLen<=0 then
      raise Exception.Create('
Invalid link name "'+ALinkName+'"');
 
    cbPathLen := (cbPathLen+1)*SizeOf(WChar);
 
    lpContext := nil;
 
    rStreamId.dwStreamId := BACKUP_LINK;
    rStreamId.dwStreamAttributes := 0;
    rStreamId.dwStreamNameSize := 0;
    int64rec(rStreamId.Size).hi := 0;
    int64rec(rStreamId.Size).lo := cbPathLen;
    dwStreamHeaderSize := PChar(@rStreamId.cStreamName)-PChar(@rStreamId)
      +LongInt(rStreamId.dwStreamNameSize);
 
    if not BackupWrite(
        hFileSource,
        Pointer(@rStreamId), // buffer to write
        dwStreamHeaderSize, // number of bytes to write
        dwBytesWritten,
        False, // don'
t abort yet
       
False, // don't process security
        lpContext
     
) then RaiseLastOSError;
 
   
if not BackupWrite(
        hFileSource
,
       
Pointer(@aLinkFullName), // buffer to write
        cbPathLen
, // number of bytes to write
        dwBytesWritten
,
       
False, // don't abort yet
       
False, // don't process security
        lpContext
     
) then RaiseLastOSError;
 
   
// free context
   
if not BackupWrite(
        hFileSource
,
       
nil, // buffer to write
       
0, // number of bytes to write
        dwBytesWritten
,
       
True, // abort
       
False, // don't process security
        lpContext
     
) then RaiseLastOSError;
 
 
finally
   
CloseHandle(hFileSource);
 
end;
end;
 
//-------------------------------------------------------------
// ADirName and ADirForLinks must not end with backslach
procedure _CreateHardlinksForSubDirectory
( const ADirName, ADirForLinks: String; options: TOptions );
var
  h
: THandle;
  sExistedFile
, sLinkName : String;
  dwAttributes
: DWORD;
  rFindData
: TWin32FindData;
  awcFileName
: Array[0..MAX_PATH] of WChar;
begin
  dwAttributes
:= GetFileAttributes( PChar(ADirForLinks) );
 
if dwAttributes=FILE_DOES_NOT_EXIST then
   
begin
// WriteLn('Create Directory ',ADirForLinks);
     
if not CreateDir(ADirForLinks) then
       
raise Exception.Create('Cannot create directory "'+ADirForLinks+'".');
   
end
 
else if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY)=0 then
   
raise Exception.Create('File "'+ADirName
     
+'" already exists and it is not a directory.');
  h
:= Windows.FindFirstFile( PChar(ADirName+'\*'), rFindData );
 
if h=INVALID_HANDLE_VALUE then Exit;
 
try
    repeat
     
if (rFindData.cFileName[0]='.') and
         
( (rFindData.cFileName[1]=#0) or ((rFindData.cFileName[1]='.') and
           
(rFindData.cFileName[2]=#0))) then Continue;
      sExistedFile
:= ADirName+'\'+rFindData.cFileName;
      sLinkName := ADirForLinks+'
\'+rFindData.cFileName;
      if (rFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY)=0 then
        begin
 
          awcFileName[
            Windows.MultiByteToWideChar( 0, 0, PChar(sExistedFile),
              MAX_PATH,awcFileName,MAX_PATH)
            ] := #0;
 
          _CreateHardlink( sExistedFile, awcFileName, sLinkName,
            optOverwrite in options );
        end
      else if optRecursive in options then
        begin
          _CreateHardlinksForSubDirectory(sExistedFile,sLinkName,options);
        end;
 
    until not Windows.FindNextFile(h,rFindData);
  finally
    Windows.FindClose(h);
  end;
end;
 
//-------------------------------------------------------------
procedure CreateHardlink( AFileName, ALinkName: String; options: TOptions );
var
  dwAttributes: DWORD;
  aFileSource : Array[0..MAX_PATH] of WChar;
begin
  dwAttributes := Windows.GetFileAttributes(PChar(AFileName));
  if dwAttributes=FILE_DOES_NOT_EXIST then
    raise Exception.Create('
File "'+AFileName+'" does not exist.');
  if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY)<>0 then
    raise Exception.Create('
Cannot create hardlink for directory (file "'
      +AFileName+'"
).');
 
  dwAttributes := Windows.GetFileAttributes(PChar(ALinkName));
  if dwAttributes<>FILE_DOES_NOT_EXIST then
  begin
    if not(optOverwrite in options) then
      raise Exception.Create('
File "'+ALinkName+'" already exists.');
    if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY)<>0 then
      raise Exception.Create('
Cannot overwrite directory "'+AFileName+'".');
  end;
 
  StringToWidechar( AFileName, aFileSource, MAX_PATH );
  _CreateHardlink( AFileName, aFileSource, ALinkName, optOverwrite in options );
 
end;
 
//-------------------------------------------------------------
procedure CreateHardlinksForDirectory( const ADirName, ADirForLinks: String; options: TOptions );
var
  dwAttributes: DWORD;
  len : Integer;
  sDirName, sDirForLinks : String;
begin
  dwAttributes := Windows.GetFileAttributes(PChar(ADirName));
  if dwAttributes=FILE_DOES_NOT_EXIST then
    raise Exception.Create('
Directory "'+ADirName+'" does not exist.');
  if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY)=0 then
    raise Exception.Create('
File "'+ADirName+'" is not a directory.');
  len := Length(ADirName);
  if (PChar(ADirName)+len-1)^='
\' then
    sDirName := Copy(ADirName,1,len-1)
  else
    sDirName := ADirName;
  if (PChar(ADirForLinks)+Length(ADirForLinks)-1)^<>'
\' then
    sDirForLinks := ADirForLinks
  else
    sDirForLinks := Copy(ADirForLinks,1,Length(ADirForLinks)-1);
  _CreateHardlinksForSubDirectory(sDirName,sDirForLinks,options);
end;
 
//-------------------------------------------------------------
procedure CreateHardlinksInDirectory( const AFileName, ADirForLinks: String; options: TOptions );
var
  dwAttributes: DWORD;
  len : Integer;
  sFileName, sDirForLinks, sLinkName : String;
  aFileSource : Array[0..MAX_PATH] of WChar;
begin
  dwAttributes := Windows.GetFileAttributes(PChar(AFileName));
  if dwAttributes=FILE_DOES_NOT_EXIST then
    raise Exception.Create('
File or directory "'+AFileName+'" does not exist.');
  if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY)=0 then
    begin
 
      sLinkName := ADirForLinks+'
\'+SysUtils.ExpandFileName(AFileName);
      dwAttributes := Windows.GetFileAttributes(PChar(sLinkName));
      if dwAttributes<>FILE_DOES_NOT_EXIST then
      begin
        if not(optOverwrite in options) then
          raise Exception.Create('
File "'+sLinkName+'" already exists.');
        if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY)<>0 then
          raise Exception.Create('
Cannot overwrite directory "'+AFileName+'".');
      end;
      StringToWidechar( AFileName, aFileSource, MAX_PATH );
      _CreateHardlink( AFileName, aFileSource, sLinkName,
        optOverwrite in options );
 
    end
  else
    begin
      len := Length(AFileName);
      if (PChar(AFileName)+len-1)^='
\' then
        sFileName := Copy(AFileName,1,len-1)
      else
        sFileName := AFileName;
      if (PChar(ADirForLinks)+Length(ADirForLinks)-1)^<>'
\' then
        sDirForLinks := ADirForLinks
      else
        sDirForLinks := Copy(ADirForLinks,1,Length(ADirForLinks)-1);
      _CreateHardlinksForSubDirectory(sFileName,sDirForLinks,options);
    end;
end;
 
//-------------------------------------------------------------
procedure DeleteDirectoryContent( const ADirName: String );
type
  PDirRef = ^TDirRef;
  PPDirRef = ^PDirRef;
  TDirRef = record
    Next : PDirRef;
    DirName : String;
  end;
var
  h: THandle;
  sFileName : String;
  pSubDirs : PDirRef;
  ppLast : PPDirRef;
  pDir : PDirRef;
  rFindData: TWin32FindData;
begin
  pSubDirs := nil;
  ppLast := @pSubDirs;
  h := Windows.FindFirstFile( PChar(ADirName+'
\*'), rFindData );
  if h=INVALID_HANDLE_VALUE then Exit;
  try
    try
      repeat
        if (rFindData.cFileName[0]='
.') and
          ( (rFindData.cFileName[1]=#0) or ((rFindData.cFileName[1]='
.') and
          (rFindData.cFileName[2]=#0))) then Continue;
        sFileName := ADirName+'
\'+rFindData.cFileName;
        if (rFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY)<>0 then
          begin
            New(pDir);
            with pDir^ do
            begin
              Next := nil;
              DirName := sFileName;
            end;
            ppLast^ := pDir;
            ppLast := @pDir^.Next;
          end
        else if not DeleteFile(sFileName) then
          raise Exception.Create('
Cannot delete file "'+sFileName+'".');
 
      until not Windows.FindNextFile(h,rFindData);
    finally
      Windows.FindClose(h);
    end;
    if pSubDirs<>nil then
    begin
      repeat
        pDir := pSubDirs;
        pSubDirs := pDir^.Next;
        sFileName := pDir^.DirName;
        Dispose(pDir);
        DeleteDirectoryContent(sFileName);
        if not RemoveDir(sFileName) then
          raise Exception.Create('
Cannot delete directory "'+sFileName+'".');
      until pSubDirs=nil;
    end;
  except
    while pSubDirs<>nil do
    begin
      pDir := pSubDirs;
      pSubDirs := pDir^.Next;
      Dispose(pDir);
    end;
    raise;
  end;
end;
 
//-------------------------------------------------------------
const
  FILE_DEVICE_FILE_SYSTEM = $0009;
  // Define the method codes for how buffers are passed for I/O and FS controls
  METHOD_BUFFERED = 0;
  METHOD_IN_DIRECT = 1;
  METHOD_OUT_DIRECT = 2;
  METHOD_NEITHER = 3;
 
  // Define the access check value for any access
  FILE_ANY_ACCESS = 0;
  FILE_READ_DATA = 1;
  FILE_WRITE_DATA = 2;
 
  FSCTL_SET_REPARSE_POINT = (FILE_DEVICE_FILE_SYSTEM shl 16) or
    (FILE_ANY_ACCESS shl 14) or (41 shl 2) or (METHOD_BUFFERED);
  FSCTL_GET_REPARSE_POINT = (FILE_DEVICE_FILE_SYSTEM shl 16) or
    (FILE_ANY_ACCESS shl 14) or (42 shl 2) or (METHOD_BUFFERED);
  FSCTL_DELETE_REPARSE_POINT = (FILE_DEVICE_FILE_SYSTEM shl 16) or
    (FILE_ANY_ACCESS shl 14) or (43 shl 2) or (METHOD_BUFFERED);
 
  FILE_FLAG_OPEN_REPARSE_POINT = $00200000;
 
  FILE_ATTRIBUTE_REPARSE_POINT = $00000400;
 
  IO_REPARSE_TAG_MOUNT_POINT = $A0000003;
 
  REPARSE_MOUNTPOINT_HEADER_SIZE = 8;
 
type
  REPARSE_MOUNTPOINT_DATA_BUFFER = packed record
    ReparseTag : DWORD;
    ReparseDataLength : DWORD;
    Reserved : Word;
    ReparseTargetLength : Word;
    ReparseTargetMaximumLength : Word;
    Reserved1 : Word;
    ReparseTarget : Array [0..0] of WChar;
  end;
  TReparseMountpointDataBuffer = REPARSE_MOUNTPOINT_DATA_BUFFER;
  PReparseMountpointDataBuffer = ^TReparseMountpointDataBuffer;
 
 
//-------------------------------------------------------------
function CreateSymlink( ATargetName, ALinkName: String; const options: TOptions ): Boolean;
const
  pwcNativeFileNamePrefix : PWideChar = '
\??\';
  nNativeFileNamePrefixWCharLength = 4;
  nNativeFileNamePrefixByteLength = nNativeFileNamePrefixWCharLength*2;
var
  hLink : THandle;
  pReparseInfo : PReparseMountpointDataBuffer;
  len, size : Integer;
  pwcLinkFileName : PWideChar;
  pwcTargetNativeFileName : PWideChar;
  pwcTargetFileName : PWideChar;
  pwc : PWideChar;
  pc : PChar;
  dwBytesReturned : DWORD;
  dwAttributes : DWORD;
  bDirectoryCreated : Boolean;
  aTargetFullName : Array [0..MAX_PATH] of Char;
begin
  Result := False;
  pReparseInfo := nil;
  hLink := INVALID_HANDLE_VALUE;
  bDirectoryCreated := False;
 
  len := Length(ALinkName);
  if ((PChar(ALinkName)+len-1)^='
\') and ((PChar(ALinkName)+len-2)^<>':') then
  begin
    Dec(len);
    SetLength(ALinkName,len);
  end;
 
  System.GetMem( pwcLinkFileName, len+len+2 );
  try
    pwcLinkFileName[
      Windows.MultiByteToWideChar(0,0,PChar(ALinkName),len,wcLinkFileName,len)
    ] := #0;
 
    dwAttributes := Windows.getFileAttributesW( pwcLinkFileName );
    if dwAttributes<>FILE_DOES_NOT_EXIST then
    begin
      if not(optOverwrite in options) then
        begin
          if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY)=0 then
            raise Exception.Create('
The file "'+ALinkName+'" already exists');
          if not isDirectoryEmpty(ALinkName) then
            raise Exception.Create(
              '
The directory "'+ALinkName+'" already exists and is not empty');
          dwAttributes := FILE_DOES_NOT_EXIST;
        end
      else if ((dwAttributes and FILE_ATTRIBUTE_DIRECTORY)=0) then
        begin
          if not DeleteFile(ALinkName) then
            raise Exception.Create('
Cannot overwrite file "'+ALinkName+'"');
          dwAttributes := FILE_DOES_NOT_EXIST;
        end
      else if (dwAttributes and FILE_ATTRIBUTE_REPARSE_POINT)
               <>FILE_ATTRIBUTE_REPARSE_POINT then
        if not isDirectoryEmpty(ALinkName) then
          begin
            if not(optDirectory in options) then
              raise Exception.Create('
Cannot overwrite non-empty directory "'
                +ALinkName+'"');
            DeleteDirectoryContent(ALinkName);
          end;
    end;
    if dwAttributes=FILE_DOES_NOT_EXIST then
    begin
      Windows.CreateDirectoryW( pwcLinkFileName, nil );
      bDirectoryCreated := True;
    end;
 
    try
      hLink := Windows.CreateFileW( pwcLinkFileName, GENERIC_WRITE, 0, nil,
          OPEN_EXISTING,
          FILE_FLAG_OPEN_REPARSE_POINT or FILE_FLAG_BACKUP_SEMANTICS, 0 );
 
      if hLink=INVALID_HANDLE_VALUE then RaiseLastOSError;
 
 
      len := Length(ATargetName);
      if ((PChar(ATargetName)+len-1)^='
\')
        and ((PChar(ATargetName)+len-2)^<>'
:') then
      begin
        Dec(len);
        SetLength(ATargetName,len);
      end;
 
      len := Windows.GetFullPathName( PChar(ATargetName), MAX_PATH,
        aTargetFullName, pc );
 
      size := len+len+2
        +nNativeFileNamePrefixByteLength+REPARSE_MOUNTPOINT_HEADER_SIZE+12;
      System.GetMem( pReparseInfo, size );
      FillChar( pReparseInfo^, size, #0 );
 
      pwcTargetNativeFileName := @pReparseInfo^.ReparseTarget;
      System.Move( pwcNativeFileNamePrefix^, pwcTargetNativeFileName^,
        nNativeFileNamePrefixByteLength+2 );
      pwcTargetFileName := pwcTargetNativeFileName +
        nNativeFileNamePrefixWCharLength;
      pwc := pwcTargetFileName + Windows.MultiByteToWideChar(0,0,
        aTargetFullName, len, pwcTargetFileName,len);
      pwc^ := #0;
 
      with pReparseInfo^ do
      begin
        ReparseTag := IO_REPARSE_TAG_MOUNT_POINT;
        ReparseTargetLength := PChar(pwc)-PChar(pwcTargetNativeFileName);
        ReparseTargetMaximumLength := ReparseTargetLength+2;
        ReparseDataLength := ReparseTargetLength + 12;
      end;
 
      dwBytesReturned := 0;
      if not DeviceIoControl( hLink, FSCTL_SET_REPARSE_POINT, pReparseInfo,
              pReparseInfo^.ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE,
              nil, 0, dwBytesReturned, nil ) then RaiseLastOSError;
 
    except
      if bDirectoryCreated then RemoveDirectoryW( pwcLinkFileName );
      raise;
    end;
 
    Result := true;
 
  finally
    if hLink<>INVALID_HANDLE_VALUE then Windows.CloseHandle(hLink);
    if pwcLinkFileName<>nil then System.FreeMem(pwcLinkFileName);
    if pReparseInfo<>nil then System.FreeMem(pReparseInfo);
  end;
 
end;
 
//-------------------------------------------------------------
procedure Help;
begin
  WriteLn;
  WriteLn('
Create link(s) on NTFS.');
  WriteLn;
  WriteLn('
Usage:');
  WriteLn;
  WriteLn('
To create hardlink(s) (works only for files):');
  WriteLn('
xlink [-fr] <existed_file> <link_name>');
  WriteLn;
  WriteLn('
To create symbolic link (works on Windows 2k/XP for directories only):');
  WriteLn('
xlink -s[f|F] <existed_directory> <link_name>');
  WriteLn;
  WriteLn('
Options:');
  WriteLn('
-f Overwrite file with name <link_name> if it exists.');
  WriteLn('
-F Overwrite file/directory with name <link_name> if it exists.');
  WriteLn('
-r Recursive.');
  WriteLn;
  WriteLn('
(c) 2002 Alex Konshin');
  Halt;
end;
//-------------------------------------------------------------
procedure Execute;
var
  iArg : Integer;
  sArg : String;
  ptr : PChar;
  options : TOptions;
  sExistedFileName : String;
  sLink : String;
  dwAttrs : DWORD;
begin
  iArg := 1;
  repeat
    sArg := ParamStr(iArg);
    if sArg='' then Help;    if PChar(sArg)^<>'
-' then Break;
    ptr := PChar(sArg)+1;
    while ptr^<>#0 do
    begin
      case ptr^ of
      '
s','S': Include( options, optSymbolicLink );
      '
h','H': Help;
      '
F': options := options + [optOverwrite,optDirectory];
      '
f': Include( options, optOverwrite );
      '
r','R': Include( options, optRecursive );
      '
d','D': Include( options, optDirectory );
      else
        WriteLn('
Error: Invalid option ''-',ptr^,'''');
        Exit;
      end;
      Inc(ptr);
    end;
    Inc(iArg);
  until iArg<=ParamCount;
 
  if ParamCount<=iArg then Help;
  if ParamCount-iArg>1 then Include( options, optDirectory );
 
  if optSymbolicLink in options then
    begin
      sLink := ParamStr(ParamCount);
      repeat
        sExistedFileName := ParamStr(iArg);
        if not CreateSymlink( sExistedFileName, sLink, options ) then
          WriteLn( '
The symbolic link creation failed.' );
        Inc(iArg);
      until iArg>=ParamCount;
    end
  else if (options*[optRecursive,optDirectory])<>[] then
    begin
 
      sLink := ParamStr(ParamCount);
      repeat
        sExistedFileName := ParamStr(iArg);
        CreateHardlinksInDirectory( sExistedFileName, sLink, options );
 
        Inc(iArg);
      until iArg>=ParamCount;
 
    end
  else
    begin
 
      sExistedFileName := ParamStr(iArg);
      sLink := ParamStr(ParamCount);
      dwAttrs := GetFileAttributes( PChar(sExistedFileName) );
 
      if dwAttrs=FILE_DOES_NOT_EXIST then
      begin
        writeln('
Error: The source file does not exist');
        Exit;
      end;
      if (dwAttrs and FILE_ATTRIBUTE_DIRECTORY)<>0 then
      begin
        writeln('
Error: Cannot create hardlink for directory');
        Exit;
      end;
      CreateHardlink( sExistedFileName, sLink, options );
    end;
 
 
 
end;
 
//=============================================================
begin
  if ParamCount<2 then Help;
  try
    Execute;
  except
    on E:Exception do
    begin
      WriteLn(E.ClassName+'
: '+E.Message);
    end;
  end;
end.