Old 22nd December 2010, 17:30   #1
hnedka
Junior Member
 
Join Date: Dec 2010
Posts: 12
Symbolic Links, Junctions, Hard Links

Hi, I have created a set of functions to deal with symbolic links, junctions and hard links. These functions are able to create them, delete them and identify them. Treat it as an alpha, because it has been tested only on latest version of XP and 7 (32-bit) and outside of that, there are probably more bugs. It may also not work on Ansi version of NSIS. Let me know if you find any bugs. First save this to "Junction.nsh":


PHP Code:
misc
!define CreateParentFolder "!insertmacro CreateParentFolder"

!macro CreateParentFolder Path
  Push 
$1  
  
${GetParent"${Path}$1
  CreateDirectory 
"$1"
  
Pop $1
!macroend

info
!define IsLink "!insertmacro IsLink"
!define IsSoftLink "!insertmacro IsSoftLink"
!define IsHardLink "!insertmacro IsHardLink"

Function IsSoftLink
  Exch 
$0
  
${GetFileAttributes"$0" "REPARSE_POINT" $0

  
${If} $!= "1"
    
StrCpy $"0"
  
${EndIf}
  
Exch $0
FunctionEnd

!macro IsSoftLink Path outVar
  Push 
"${Path}"
  
Call IsSoftLink
  Pop 
${outVar}
!
macroend

Function IsHardLink
  Exch 
$1
  System
::Call "kernel32::CreateFileW(w `$1`, i 0x40000000, i 0, i 0, i 3, i 0, i 0) i .r0"
  
  
${If} $"-1"
    
StrCpy $"0"
    
goto is_hard_link_end  
  
${EndIf}
    
  
System::Call "*(&i256 0) i. r1"       
  
System::Call "kernel32::GetFileInformationByHandle(i r0, i r1) i .s"
  
System::Call "kernel32::CloseHandle(i r0) i.r0"
  
Pop $0
  
  
${If} $== "0"
    
goto is_hard_link_end  
  
${EndIf}  
  
  
System::Call "*$1(&i40 0, &i4 .r0)"
  
  
${If} $!= "0"
    
IntOp $$1  
  
${EndIf}    

  
is_hard_link_end:
  
Pop $1
FunctionEnd

!macro IsHardLink Path outVar
  Push 
$0
  Push 
"${Path}"
  
Call IsHardLink
  StrCpy 
${outVar} $0
  Pop 
$0
!macroend

!macro IsLink Path outVar
  
${IsSoftLink"${Path}${outVar}

  ${If} ${
outVar} == 0
    
${IsHardLink"${Path}${outVar}
  ${EndIf} 
!
macroend

files
!define CreateHardLink "!insertmacro CreateHardLink"
!define CreateSymbolicLinkFile "!insertmacro CreateSymbolicLinkFile"
!define CreateLinkFile "!insertmacro CreateLinkFile"
!define DeleteLinkFile "!insertmacro DeleteLinkFile"

!macro CreateSymbolicLinkFile Junction Target outVar
  
${CreateParentFolder"${Junction}"
  
System::Call "kernel32::CreateSymbolicLinkW(w `${Junction}`, w `${Target}`, i 0) i .s"
  
Pop ${outVar}
  
  ${If} ${
outVar} == "error"
    
StrCpy ${outVar"0"
  
${EndIf}
!
macroend

!macro CreateHardLink Junction Target outVar
  
${CreateParentFolder"${Junction}"
  
System::Call "kernel32::CreateHardLinkW(w `${Junction}`, w `${Target}`, i 0) i .s"
  
Pop ${outVar}
!
macroend

!macro CreateLinkFile Junction Target outVar
  
${CreateSymbolicLinkFile"${Junction}" "${Target}${outVar}
  
  ${If} ${
outVar} == 0
    
${CreateHardLink"${Junction}" "${Target}${outVar}    
  ${EndIf}
!
macroend

!macro DeleteLinkFile Path outVar
  
${IsLink"${Path}${outVar}

  ${If} ${
outVar} != 0
    SetFileAttributes 
"${Path}" "NORMAL"
    
System::Call "kernel32::DeleteFileW(w `${Path}`) i.s"
    
Pop ${outVar}
  ${EndIf}
!
macroend

folders
!define CreateJunction "!insertmacro CreateJunction"
!define CreateSymbolicLinkFolder "!insertmacro CreateSymbolicLinkFolder"
!define CreateLinkFolder "!insertmacro CreateLinkFolder"
!define DeleteLinkFolder "!insertmacro DeleteLinkFolder"

Function CreateJunction
  Exch 
$4
  Exch
  Exch 
$5
  Push 
$1
  Push 
$2
  Push 
$3
  Push 
$6
  CreateDirectory 
"$5"
  
System::Call "kernel32::CreateFileW(w `$5`, i 0x40000000, i 0, i 0, i 3, i 0x02200000, i 0) i .r6"

  
${If} $"-1"
    
StrCpy $"0"
    
RMDir "$5" 
    
goto create_junction_end  
  
${EndIf}
  
  
CreateDirectory "$4"  Windows XP requires that the destination exists
  StrCpy 
$"\??\$4"
  
StrLen $$4
  IntOp 
$$2  
  IntOp 
$$2
  IntOp 
$$10
  IntOp 
$$18
  System
::Call "*(i 0xA0000003, &i4 $2, &i2 0, &i2 $0, &i2 $1, &i2 0, &w$1 `$4`, &i2 0)i.r2"
  
System::Call "kernel32::DeviceIoControl(i r6, i 0x900A4, i r2, i r3, i 0, i 0, *i r4r4, i 0) i.r0"
  
System::Call "kernel32::CloseHandle(i r6) i.r1"

  
${If} $== "0"
    
RMDir "$5"  
  
${EndIf}
  
  
create_junction_end:
  
Pop $6
  Pop 
$3
  Pop 
$2
  Pop 
$1
  Pop 
$5
  Pop 
$4        
FunctionEnd

!macro CreateJunction Junction Target outVar
  Push 
$0
  Push 
"${Junction}"
  
Push "${Target}"
  
Call CreateJunction
  StrCpy 
${outVar} $0
  Pop 
$0
!macroend

!macro CreateSymbolicLinkFolder Junction Target outVar
  
${CreateParentFolder"${Junction}"
  
System::Call "kernel32::CreateSymbolicLinkW(w `${Junction}`, w `${Target}`, i 1) i .s"
  
Pop ${outVar}
  
  ${If} ${
outVar} == "error"
    
StrCpy ${outVar"0"
  
${EndIf}
!
macroend

!macro CreateLinkFolder Junction Target outVar
  
${CreateSymbolicLinkFolder"${Junction}" "${Target}${outVar}
  
  ${If} ${
outVar} == 0
    
${CreateJunction"${Junction}" "${Target}${outVar}    
  ${EndIf}  
!
macroend

!macro DeleteLinkFolder Path outVar
  
${IsSoftLink"${Path}${outVar
  
  ${If} ${
outVar} != 0
    SetFileAttributes 
"${Path}" "NORMAL"
    
System::Call "kernel32::RemoveDirectoryW(w `${Path}`) i.s"
    
Pop ${outVar}
  ${EndIf}
!
macroend 
Then you can use these functions:

PHP Code:
!include "Junction.nsh"

checks if target is hard link
${IsHardLink"${Path}${outVar}
checks if target is a soft link (symbolic link or junction)
${
IsSoftLink"${Path}${outVar}
checks if target is either a hard link or a soft link
${IsLink"${Path}${outVar}

creates a hard link (file onlymust be on the same volume, and target must exist)
${
CreateHardLink"${Junction}" "${Target}${outVar}
creates a symbolic link for a file (Vista+, target doesn't need to exist and can be anywhere)
${CreateSymbolicLinkFile} "${Junction}" "${Target}" ${outVar}
; tries to create a symbolic link first and when it fails, then it tries to create a hard link (files only)
${CreateLinkFile} "${Junction}" "${Target}" ${outVar}

; creates a symbolic link for a folder (Vista+, target doesn'
t need to exist)
${
CreateSymbolicLinkFolder"${Junction}" "${Target}${outVar}
creates a junction (folders onlypath must be absolute and target should exist)
${
CreateJunction"${Junction}" "${Target}${outVar}
tries to create symbolic link first and when it failsthen it tries to create a junctions (directories only)
${
CreateLinkFolder"${Junction}" "${Target}${outVar}

checks if a folder is a junction or a symbolic link, and if it isit deletes it
${DeleteLinkFolder"${Path}${outVar}
checks if a file is a symbolic link or a hard link, and if it isit deletes it
${DeleteLinkFile"${Path}${outVar
All of the above returns "0" on fail and anything else (usually 1, but not always) on success.

Some basic differences between these three guys are:
all 3 of them:
- must be located on a NTFS volume (but not necessarily their target)
- system must be Windows 2000 or newer

Hard links:
- only for files
- both the link and the target must be on the same volume and must exist
- path must be absolute
- it is indistinguishable from the original file and doesn't really act like a link, but rather like another copy of the target file (except that when you edit one, both get changed; but you need to delete both to get that file actually deleted)

Junctions:
- only for folders
- in XP and older, when they are deleted in the explorer, the target gets wiped out as well (see wikipedia entry on junctions)
- path must be absolute, target can be anywhere
- on Win XP SP 4, creation fails if the target doesn't exist, on Win 7, it gets created (didn't try anywhere else)

Symbolic Links:
- only Vista and newer
- supports both files and folders
- target can be anywhere and doesn't need to exist
- path can be relative or absolute

more info:
http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx
http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx
http://en.wikipedia.org/wiki/NTFS_junction_point
http://en.wikipedia.org/wiki/NTFS_symbolic_link
hnedka is offline   Reply With Quote
Old 22nd December 2010, 17:36   #2
Anders
Moderator
 
Anders's Avatar
 
Join Date: Jun 2002
Location: ${NSISDIR}
Posts: 5,163
You should create a wiki page for something like this (This stupid forum will break code even when using code tags etc)

IntOp $PostCount $PostCount + 1
Anders is offline   Reply With Quote
Old 22nd December 2010, 17:46   #3
hnedka
Junior Member
 
Join Date: Dec 2010
Posts: 12
I will, thanks for the suggestion. But first I will wait a little bit and fix possible issues/bugs.
hnedka is offline   Reply With Quote
Old 3rd June 2011, 10:08   #4
hnedka
Junior Member
 
Join Date: Dec 2010
Posts: 12
I have (over time) fixed various issues with the script. When I have more time, I will create a wikipage for it:

PHP Code:
!ifndef JUNCTION_INCLUDED
!define JUNCTION_INCLUDED

misc
!define CreateParentFolder "!insertmacro CreateParentFolder"

!macro CreateParentFolder Path
  Push 
$1  
  
${GetParent"${Path}$1
  CreateDirectory 
"$1"
  
Pop $1
!macroend

info
!define IsLink "!insertmacro IsLink"
!define IsSoftLink "!insertmacro IsSoftLink"
!define IsHardLink "!insertmacro IsHardLink"

Function IsSoftLink
  Exch 
$0
  
${GetFileAttributes"$0" "REPARSE_POINT" $0

  
${If} $!= "1"
    
StrCpy $"0"
  
${EndIf}
  
Exch $0
FunctionEnd

!macro IsSoftLink Path outVar
  Push 
"${Path}"
  
Call IsSoftLink
  Pop 
${outVar}
!
macroend

Function IsHardLink
  Exch 
$1
  System
::Call "kernel32::CreateFileW(w `$1`, i 0x40000000, i 0, i 0, i 3, i 0, i 0) i .r0"
  
  
${If} $"-1"
    
StrCpy $"0"
    
goto is_hard_link_end  
  
${EndIf}
    
  
System::Call "*(&i256 0) i. r1"       
  
System::Call "kernel32::GetFileInformationByHandle(i r0, i r1) i .s"
  
System::Call "kernel32::CloseHandle(i r0) i.r0"
  
Pop $0
  
  
${If} $== "0"
    
goto is_hard_link_end  
  
${EndIf}  
  
  
System::Call "*$1(&i40 0, &i4 .r0)"
  
  
${If} $!= "0"
    
IntOp $$1  
  
${EndIf}    

  
is_hard_link_end:
  
Pop $1
FunctionEnd

!macro IsHardLink Path outVar
  Push 
$0
  Push 
"${Path}"
  
Call IsHardLink
  Exch 
$0
  Pop 
${outVar}
!
macroend

!macro IsLink Path outVar
  
${IsSoftLink"${Path}${outVar}

  ${If} ${
outVar} == 0
    
${IsHardLink"${Path}${outVar}
  ${EndIf} 
!
macroend

files
!define CreateHardLink "!insertmacro CreateHardLink"
!define CreateSymbolicLinkFile "!insertmacro CreateSymbolicLinkFile"
!define CreateLinkFile "!insertmacro CreateLinkFile"
!define DeleteLinkFile "!insertmacro DeleteLinkFile"

!macro CreateSymbolicLinkFile Junction Target outVar
  
${CreateParentFolder"${Junction}"
  
System::Call "kernel32::CreateSymbolicLinkW(w `${Junction}`, w `${Target}`, i 0) i .s"
  
Pop ${outVar}
  
  ${If} ${
outVar} == "error"
    
StrCpy ${outVar"0"
  
${EndIf}
!
macroend

!macro CreateHardLink Junction Target outVar
  
${CreateParentFolder"${Junction}"
  
System::Call "kernel32::CreateHardLinkW(w `${Junction}`, w `${Target}`, i 0) i .s"
  
  
Pop ${outVar}
  
  ${If} ${
outVar} == 0  
    StrCpy 
${outVar"1"
  
${Else}
    
StrCpy ${outVar"0"
  
${EndIf}  
!
macroend

!macro CreateLinkFile Junction Target outVar
  
${CreateSymbolicLinkFile"${Junction}" "${Target}${outVar}
  
  ${If} ${
outVar} == 0  
    
${CreateHardLink"${Junction}" "${Target}${outVar}    
  ${EndIf}
!
macroend

!macro DeleteLinkFile Path outVar
  
${IsLink"${Path}${outVar}

  ${If} ${
outVar} != 0
    SetFileAttributes 
"${Path}" "NORMAL"
    
System::Call "kernel32::DeleteFileW(w `${Path}`) i.s"
    
Pop ${outVar}
  ${EndIf}
!
macroend

folders
!define CreateJunction "!insertmacro CreateJunction"
!define CreateSymbolicLinkFolder "!insertmacro CreateSymbolicLinkFolder"
!define CreateLinkFolder "!insertmacro CreateLinkFolder"
!define DeleteLinkFolder "!insertmacro DeleteLinkFolder"

Function CreateJunction
  Exch 
$4
  Exch
  Exch 
$5
  Push 
$1
  Push 
$2
  Push 
$3
  Push 
$6
  CreateDirectory 
"$5"
  
System::Call "kernel32::CreateFileW(w `$5`, i 0x40000000, i 0, i 0, i 3, i 0x02200000, i 0) i .r6"

  
${If} $"-1"
    
StrCpy $"0"
    
RMDir "$5" 
    
goto create_junction_end  
  
${EndIf}
  
  
CreateDirectory "$4"  Windows XP requires that the destination exists
  StrCpy 
$"\??\$4"
  
StrLen $$4
  IntOp 
$$2  
  IntOp 
$$2
  IntOp 
$$10
  IntOp 
$$18
  System
::Call "*(i 0xA0000003, &i4 $2, &i2 0, &i2 $0, &i2 $1, &i2 0, &w$1 `$4`, &i2 0)i.r2"
  
System::Call "kernel32::DeviceIoControl(i r6, i 0x900A4, i r2, i r3, i 0, i 0, *i r4r4, i 0) i.r0"
  
System::Call "kernel32::CloseHandle(i r6) i.r1"

  
${If} $== "0"
    
RMDir "$5"  
  
${EndIf}
  
  
create_junction_end:
  
Pop $6
  Pop 
$3
  Pop 
$2
  Pop 
$1
  Pop 
$5
  Pop 
$4        
FunctionEnd

!macro CreateJunction Junction Target outVar
  Push 
$0
  Push 
"${Junction}"
  
Push "${Target}"
  
Call CreateJunction
  Exch 
$0
  Pop 
${outVar}
!
macroend

!macro CreateSymbolicLinkFolder Junction Target outVar
  
${CreateParentFolder"${Junction}"
  
System::Call "kernel32::CreateSymbolicLinkW(w `${Junction}`, w `${Target}`, i 1) i .s"
  
Pop ${outVar}
  
  ${If} ${
outVar} == "error"
    
StrCpy ${outVar"0"
  
${EndIf}
!
macroend

!macro CreateLinkFolder Junction Target outVar
  
${CreateSymbolicLinkFolder"${Junction}" "${Target}${outVar}
  
  ${If} ${
outVar} == 0
    
${CreateJunction"${Junction}" "${Target}${outVar}    
  ${EndIf}  
!
macroend

!macro DeleteLinkFolder Path outVar
  
${IsSoftLink"${Path}${outVar
  
  ${If} ${
outVar} != 0
    SetFileAttributes 
"${Path}" "NORMAL"
    
System::Call "kernel32::RemoveDirectoryW(w `${Path}`) i.s"
    
Pop ${outVar}
  ${EndIf}
!
macroend

turn off NSIS warnings
Section 
"-JunctionRemoveWarnings"
  
Return
  
Call CreateJunction
  Call IsHardLink
  Call IsSoftLink
SectionEnd

!endif 
hnedka is offline   Reply With Quote
Old 8th September 2011, 14:52   #5
David Pi
Junior Member
 
Join Date: Sep 2011
Posts: 5
Smile

Thank you very much. It's very helpful to me.
David Pi is offline   Reply With Quote
Old 24th September 2011, 10:10   #6
David Pi
Junior Member
 
Join Date: Sep 2011
Posts: 5
PHP Code:
!macro CreateHardLink Junction Target outVar
  
${CreateParentFolder"${Junction}"
  
System::Call "kernel32::CreateHardLinkW(w `${Junction}`, w `${Target}`, i 0) i .s"
  
  
Pop ${outVar}
  
  ${If} ${
outVar} == 0  
    StrCpy 
${outVar"1"
  
${Else}
    
StrCpy ${outVar"0"
  
${EndIf}  
!
macroend 
It seems the macro CreateHardLink has make a mistake.
The system call return "1" on success, return "0" on fail. But the ${ourVar} was transfered to the opposite... Finally it return "0" on sucess.
David Pi is offline   Reply With Quote
Old 6th December 2012, 09:42   #7
PoRtAbLe_StEaLtH
Junior Member
 
Join Date: Jul 2011
Posts: 49
very useful, thanks for sharing.
i was just thinking about this after SetEnvironment failed in results.
PoRtAbLe_StEaLtH is offline   Reply With Quote
Old 18th February 2013, 08:37   #8
majikaz
Junior Member
 
Join Date: Feb 2013
Posts: 2
Thumbs up

Excellent resource.. I was stuck trying to figure out the problem of creating junctions from within my nsis installer. I only found this thread after kindly being pointed in the right direction.

You should defo. make a wiki page for this one.

A BIG help,

Thanks
majikaz is offline   Reply With Quote
Old 11th July 2013, 15:03   #9
PoRtAbLe_StEaLtH
Junior Member
 
Join Date: Jul 2011
Posts: 49
Error

n/m.. error was with me..
PoRtAbLe_StEaLtH is offline   Reply With Quote
Reply
Go Back   Winamp & Shoutcast Forums > Developer Center > NSIS Discussion

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump