added powershell symlink util helper (#27153)
* Added symbolic link util for powershell * updated module_util license to BSDpull/4420/head
parent
e16e6313c7
commit
1bc4940ee1
|
@ -0,0 +1,506 @@
|
|||
# Copyright (c) 2017 Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
Function Load-LinkUtils() {
|
||||
Add-Type -TypeDefinition @'
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ansible
|
||||
{
|
||||
public enum LinkType
|
||||
{
|
||||
SymbolicLink,
|
||||
JunctionPoint,
|
||||
HardLink
|
||||
}
|
||||
|
||||
public class LinkUtilWin32Exception : System.ComponentModel.Win32Exception
|
||||
{
|
||||
private string _msg;
|
||||
|
||||
public LinkUtilWin32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
|
||||
|
||||
public LinkUtilWin32Exception(int errorCode, string message) : base(errorCode)
|
||||
{
|
||||
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
|
||||
}
|
||||
|
||||
public override string Message { get { return _msg; } }
|
||||
public static explicit operator LinkUtilWin32Exception(string message) { return new LinkUtilWin32Exception(message); }
|
||||
}
|
||||
|
||||
public class LinkInfo
|
||||
{
|
||||
public LinkType Type { get; internal set; }
|
||||
public string PrintName { get; internal set; }
|
||||
public string SubstituteName { get; internal set; }
|
||||
public string AbsolutePath { get; internal set; }
|
||||
public string TargetPath { get; internal set; }
|
||||
public string[] HardTargets { get; internal set; }
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct LUID
|
||||
{
|
||||
public UInt32 LowPart;
|
||||
public Int32 HighPart;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TOKEN_PRIVILEGES
|
||||
{
|
||||
public UInt32 PrivilegeCount;
|
||||
public LUID Luid;
|
||||
public UInt32 Attributes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct REPARSE_DATA_BUFFER
|
||||
{
|
||||
public UInt32 ReparseTag;
|
||||
public UInt16 ReparseDataLength;
|
||||
public UInt16 Reserved;
|
||||
public UInt16 SubstituteNameOffset;
|
||||
public UInt16 SubstituteNameLength;
|
||||
public UInt16 PrintNameOffset;
|
||||
public UInt16 PrintNameLength;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = LinkUtil.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)]
|
||||
public char[] PathBuffer;
|
||||
}
|
||||
|
||||
public class LinkUtil
|
||||
{
|
||||
public const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 1024 * 16;
|
||||
|
||||
private const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
|
||||
private const int TOKEN_QUERY = 0x00000008;
|
||||
private const int SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||
|
||||
private const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
|
||||
private const UInt32 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
|
||||
|
||||
private const UInt32 FSCTL_GET_REPARSE_POINT = 0x000900A8;
|
||||
private const UInt32 FSCTL_SET_REPARSE_POINT = 0x000900A4;
|
||||
private const UInt32 FILE_DEVICE_FILE_SYSTEM = 0x00090000;
|
||||
|
||||
private const UInt32 IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;
|
||||
private const UInt32 IO_REPARSE_TAG_SYMLINK = 0xA000000C;
|
||||
|
||||
private const UInt32 SYMLINK_FLAG_RELATIVE = 0x00000001;
|
||||
|
||||
private const Int64 INVALID_HANDLE_VALUE = -1;
|
||||
|
||||
private const UInt32 SIZE_OF_WCHAR = 2;
|
||||
|
||||
private const UInt32 SYMBOLIC_LINK_FLAG_FILE = 0x00000000;
|
||||
private const UInt32 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x00000001;
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool CloseHandle(
|
||||
IntPtr hObject);
|
||||
|
||||
[DllImport("advapi32.dll")]
|
||||
private static extern bool OpenProcessToken(
|
||||
IntPtr ProcessHandle,
|
||||
UInt32 DesiredAccess,
|
||||
out IntPtr TokenHandle);
|
||||
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern bool LookupPrivilegeValue(
|
||||
string lpSystemName,
|
||||
string lpName,
|
||||
[MarshalAs(UnmanagedType.Struct)] out LUID lpLuid);
|
||||
|
||||
[DllImport("advapi32.dll")]
|
||||
private static extern bool AdjustTokenPrivileges(
|
||||
IntPtr TokenHandle,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
|
||||
ref TOKEN_PRIVILEGES NewState,
|
||||
UInt32 BufferLength,
|
||||
IntPtr PreviousState,
|
||||
IntPtr ReturnLength);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||
private static extern SafeFileHandle CreateFile(
|
||||
string lpFileName,
|
||||
[MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
|
||||
IntPtr lpSecurityAttributes,
|
||||
[MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
|
||||
UInt32 dwFlagsAndAttributes,
|
||||
IntPtr hTemplateFile);
|
||||
|
||||
// Used by GetReparsePointInfo()
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool DeviceIoControl(
|
||||
SafeFileHandle hDevice,
|
||||
UInt32 dwIoControlCode,
|
||||
IntPtr lpInBuffer,
|
||||
UInt32 nInBufferSize,
|
||||
out REPARSE_DATA_BUFFER lpOutBuffer,
|
||||
UInt32 nOutBufferSize,
|
||||
out UInt32 lpBytesReturned,
|
||||
IntPtr lpOverlapped);
|
||||
|
||||
// Used by CreateJunctionPoint()
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool DeviceIoControl(
|
||||
SafeFileHandle hDevice,
|
||||
UInt32 dwIoControlCode,
|
||||
REPARSE_DATA_BUFFER lpInBuffer,
|
||||
UInt32 nInBufferSize,
|
||||
IntPtr lpOutBuffer,
|
||||
UInt32 nOutBufferSize,
|
||||
out UInt32 lpBytesReturned,
|
||||
IntPtr lpOverlapped);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool GetVolumePathName(
|
||||
string lpszFileName,
|
||||
StringBuilder lpszVolumePathName,
|
||||
ref UInt32 cchBufferLength);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern IntPtr FindFirstFileNameW(
|
||||
string lpFileName,
|
||||
UInt32 dwFlags,
|
||||
ref UInt32 StringLength,
|
||||
StringBuilder LinkName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool FindNextFileNameW(
|
||||
IntPtr hFindStream,
|
||||
ref UInt32 StringLength,
|
||||
StringBuilder LinkName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool FindClose(
|
||||
IntPtr hFindFile);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool RemoveDirectory(
|
||||
string lpPathName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool DeleteFile(
|
||||
string lpFileName);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool CreateSymbolicLink(
|
||||
string lpSymlinkFileName,
|
||||
string lpTargetFileName,
|
||||
UInt32 dwFlags);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool CreateHardLink(
|
||||
string lpFileName,
|
||||
string lpExistingFileName,
|
||||
IntPtr lpSecurityAttributes);
|
||||
|
||||
public static void EnablePrivilege(string privilege)
|
||||
{
|
||||
TOKEN_PRIVILEGES tkpPrivileges;
|
||||
|
||||
IntPtr hToken;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken))
|
||||
throw new LinkUtilWin32Exception("OpenProcessToken failed");
|
||||
|
||||
try
|
||||
{
|
||||
LUID luid;
|
||||
if (!LookupPrivilegeValue(null, privilege, out luid))
|
||||
throw new LinkUtilWin32Exception(String.Format("LookupPrivilegeValue({0}) failed", privilege));
|
||||
|
||||
tkpPrivileges.PrivilegeCount = 1;
|
||||
tkpPrivileges.Luid = luid;
|
||||
tkpPrivileges.Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
if (!AdjustTokenPrivileges(hToken, false, ref tkpPrivileges, 0, IntPtr.Zero, IntPtr.Zero))
|
||||
throw new LinkUtilWin32Exception(String.Format("AdjustTokenPrivileges({0}) failed", privilege));
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(hToken);
|
||||
}
|
||||
}
|
||||
|
||||
public static LinkInfo GetLinkInfo(string linkPath)
|
||||
{
|
||||
FileAttributes attr = File.GetAttributes(linkPath);
|
||||
if (attr.HasFlag(FileAttributes.ReparsePoint))
|
||||
return GetReparsePointInfo(linkPath);
|
||||
|
||||
if (!attr.HasFlag(FileAttributes.Directory))
|
||||
return GetHardLinkInfo(linkPath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DeleteLink(string linkPath)
|
||||
{
|
||||
bool success;
|
||||
FileAttributes attr = File.GetAttributes(linkPath);
|
||||
if (attr.HasFlag(FileAttributes.Directory))
|
||||
{
|
||||
success = RemoveDirectory(linkPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = DeleteFile(linkPath);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
throw new LinkUtilWin32Exception(String.Format("Failed to delete link at {0}", linkPath));
|
||||
}
|
||||
|
||||
public static void CreateLink(string linkPath, String linkTarget, LinkType linkType)
|
||||
{
|
||||
switch (linkType)
|
||||
{
|
||||
case LinkType.SymbolicLink:
|
||||
UInt32 linkFlags;
|
||||
FileAttributes attr = File.GetAttributes(linkTarget);
|
||||
if (attr.HasFlag(FileAttributes.Directory))
|
||||
linkFlags = SYMBOLIC_LINK_FLAG_DIRECTORY;
|
||||
else
|
||||
linkFlags = SYMBOLIC_LINK_FLAG_FILE;
|
||||
|
||||
if (!CreateSymbolicLink(linkPath, linkTarget, linkFlags))
|
||||
throw new LinkUtilWin32Exception(String.Format("CreateSymbolicLink({0}, {1}, {2}) failed", linkPath, linkTarget, linkFlags));
|
||||
break;
|
||||
case LinkType.JunctionPoint:
|
||||
CreateJunctionPoint(linkPath, linkTarget);
|
||||
break;
|
||||
case LinkType.HardLink:
|
||||
if (!CreateHardLink(linkPath, linkTarget, IntPtr.Zero))
|
||||
throw new LinkUtilWin32Exception(String.Format("CreateHardLink({0}, {1}) failed", linkPath, linkTarget));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static LinkInfo GetHardLinkInfo(string linkPath)
|
||||
{
|
||||
UInt32 maxPath = 260;
|
||||
List<string> result = new List<string>();
|
||||
|
||||
StringBuilder sb = new StringBuilder((int)maxPath);
|
||||
UInt32 stringLength = maxPath;
|
||||
if (!GetVolumePathName(linkPath, sb, ref stringLength))
|
||||
throw new LinkUtilWin32Exception("GetVolumePathName() failed");
|
||||
string volume = sb.ToString();
|
||||
|
||||
stringLength = maxPath;
|
||||
IntPtr findHandle = FindFirstFileNameW(linkPath, 0, ref stringLength, sb);
|
||||
if (findHandle.ToInt64() != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
string hardLinkPath = sb.ToString();
|
||||
if (hardLinkPath.StartsWith("\\"))
|
||||
hardLinkPath = hardLinkPath.Substring(1, hardLinkPath.Length - 1);
|
||||
|
||||
result.Add(Path.Combine(volume, hardLinkPath));
|
||||
stringLength = maxPath;
|
||||
|
||||
} while (FindNextFileNameW(findHandle, ref stringLength, sb));
|
||||
}
|
||||
finally
|
||||
{
|
||||
FindClose(findHandle);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Count > 1)
|
||||
return new LinkInfo
|
||||
{
|
||||
Type = LinkType.HardLink,
|
||||
HardTargets = result.ToArray()
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static LinkInfo GetReparsePointInfo(string linkPath)
|
||||
{
|
||||
SafeFileHandle fileHandle = CreateFile(
|
||||
linkPath,
|
||||
FileAccess.Read,
|
||||
FileShare.None,
|
||||
IntPtr.Zero,
|
||||
FileMode.Open,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
|
||||
IntPtr.Zero);
|
||||
|
||||
if (fileHandle.IsInvalid)
|
||||
throw new LinkUtilWin32Exception(String.Format("CreateFile({0}) failed", linkPath));
|
||||
|
||||
REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER();
|
||||
UInt32 bytesReturned;
|
||||
try
|
||||
{
|
||||
if (!DeviceIoControl(
|
||||
fileHandle,
|
||||
FSCTL_GET_REPARSE_POINT,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
out buffer,
|
||||
MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
|
||||
out bytesReturned,
|
||||
IntPtr.Zero))
|
||||
throw new LinkUtilWin32Exception(String.Format("DeviceIoControl() failed for file at {0}", linkPath));
|
||||
}
|
||||
finally
|
||||
{
|
||||
fileHandle.Dispose();
|
||||
}
|
||||
|
||||
bool isRelative = false;
|
||||
int pathOffset = 0;
|
||||
LinkType linkType;
|
||||
if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK)
|
||||
{
|
||||
UInt32 bufferFlags = Convert.ToUInt32(buffer.PathBuffer[0]) + Convert.ToUInt32(buffer.PathBuffer[1]);
|
||||
if (bufferFlags == SYMLINK_FLAG_RELATIVE)
|
||||
isRelative = true;
|
||||
pathOffset = 2;
|
||||
linkType = LinkType.SymbolicLink;
|
||||
}
|
||||
else if (buffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
|
||||
{
|
||||
linkType = LinkType.JunctionPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
string errorMessage = String.Format("Invalid Reparse Tag: {0}", buffer.ReparseTag.ToString());
|
||||
throw new Exception(errorMessage);
|
||||
}
|
||||
|
||||
string printName = new string(buffer.PathBuffer, (int)(buffer.PrintNameOffset / SIZE_OF_WCHAR) + pathOffset, (int)(buffer.PrintNameLength / SIZE_OF_WCHAR));
|
||||
string substituteName = new string(buffer.PathBuffer, (int)(buffer.SubstituteNameOffset / SIZE_OF_WCHAR) + pathOffset, (int)(buffer.SubstituteNameLength / SIZE_OF_WCHAR));
|
||||
|
||||
// TODO: should we check for \?\UNC\server for convert it to the NT style \\server path
|
||||
// Remove the leading Windows object directory \?\ from the path if present
|
||||
string targetPath = substituteName;
|
||||
if (targetPath.StartsWith("\\??\\"))
|
||||
targetPath = targetPath.Substring(4, targetPath.Length - 4);
|
||||
|
||||
string absolutePath = targetPath;
|
||||
if (isRelative)
|
||||
absolutePath = Path.GetFullPath(Path.Combine(new FileInfo(linkPath).Directory.FullName, targetPath));
|
||||
|
||||
return new LinkInfo
|
||||
{
|
||||
Type = linkType,
|
||||
PrintName = printName,
|
||||
SubstituteName = substituteName,
|
||||
AbsolutePath = absolutePath,
|
||||
TargetPath = targetPath
|
||||
};
|
||||
}
|
||||
|
||||
private static void CreateJunctionPoint(string linkPath, string linkTarget)
|
||||
{
|
||||
// We need to create the link as a dir beforehand
|
||||
Directory.CreateDirectory(linkPath);
|
||||
SafeFileHandle fileHandle = CreateFile(
|
||||
linkPath,
|
||||
FileAccess.Write,
|
||||
FileShare.Read | FileShare.Write | FileShare.None,
|
||||
IntPtr.Zero,
|
||||
FileMode.Open,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
|
||||
IntPtr.Zero);
|
||||
|
||||
if (fileHandle.IsInvalid)
|
||||
throw new LinkUtilWin32Exception(String.Format("CreateFile({0}) failed", linkPath));
|
||||
|
||||
try
|
||||
{
|
||||
string substituteName = "\\??\\" + Path.GetFullPath(linkTarget);
|
||||
string printName = linkTarget;
|
||||
|
||||
REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER();
|
||||
buffer.SubstituteNameOffset = 0;
|
||||
buffer.SubstituteNameLength = (UInt16)(substituteName.Length * SIZE_OF_WCHAR);
|
||||
buffer.PrintNameOffset = (UInt16)(buffer.SubstituteNameLength + 2);
|
||||
buffer.PrintNameLength = (UInt16)(printName.Length * SIZE_OF_WCHAR);
|
||||
|
||||
buffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
|
||||
buffer.ReparseDataLength = (UInt16)(buffer.SubstituteNameLength + buffer.PrintNameLength + 12);
|
||||
buffer.PathBuffer = new char[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
|
||||
byte[] unicodeBytes = Encoding.Unicode.GetBytes(substituteName + "\0" + printName);
|
||||
char[] pathBuffer = Encoding.Unicode.GetChars(unicodeBytes);
|
||||
Array.Copy(pathBuffer, buffer.PathBuffer, pathBuffer.Length);
|
||||
|
||||
UInt32 bytesReturned;
|
||||
if (!DeviceIoControl(
|
||||
fileHandle,
|
||||
FSCTL_SET_REPARSE_POINT,
|
||||
buffer,
|
||||
(UInt32)(buffer.ReparseDataLength + 8),
|
||||
IntPtr.Zero, 0,
|
||||
out bytesReturned,
|
||||
IntPtr.Zero))
|
||||
throw new LinkUtilWin32Exception(String.Format("DeviceIoControl() failed to create junction point at {0} to {1}", linkPath, linkTarget));
|
||||
}
|
||||
finally
|
||||
{
|
||||
fileHandle.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
[Ansible.LinkUtil]::EnablePrivilege("SeBackupPrivilege")
|
||||
}
|
||||
|
||||
Function Get-Link($link_path) {
|
||||
$link_info = [Ansible.LinkUtil]::GetLinkInfo($link_path)
|
||||
return $link_info
|
||||
}
|
||||
|
||||
Function Remove-Link($link_path) {
|
||||
[Ansible.LinkUtil]::DeleteLink($link_path)
|
||||
}
|
||||
|
||||
Function New-Link($link_path, $link_target, $link_type) {
|
||||
if (-not (Test-Path -Path $link_target)) {
|
||||
throw "link_target '$link_target' does not exist, cannot create link"
|
||||
}
|
||||
|
||||
switch($link_type) {
|
||||
"link" {
|
||||
$type = [Ansible.LinkType]::SymbolicLink
|
||||
}
|
||||
"junction" {
|
||||
if (Test-Path -Path $link_target -PathType Leaf) {
|
||||
throw "cannot set the target for a junction point to a file"
|
||||
}
|
||||
$type = [Ansible.LinkType]::JunctionPoint
|
||||
}
|
||||
"hard" {
|
||||
if (Test-Path -Path $link_target -PathType Container) {
|
||||
throw "cannot set the target for a hard link to a directory"
|
||||
}
|
||||
$type = [Ansible.LinkType]::HardLink
|
||||
}
|
||||
default { throw "invalid link_type option $($link_type): expecting link, junction, hard" }
|
||||
}
|
||||
[Ansible.LinkUtil]::CreateLink($link_path, $link_target, $type)
|
||||
}
|
||||
|
||||
# this line must stay at the bottom to ensure all defined module parts are exported
|
||||
Export-ModuleMember -Alias * -Function * -Cmdlet *
|
|
@ -0,0 +1,167 @@
|
|||
#!powershell
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.Legacy
|
||||
#Requires -Module Ansible.ModuleUtils.LinkUtil
|
||||
#Requires -Module Ansible.ModuleUtils.CommandUtil
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$params = Parse-Args $args;
|
||||
$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true
|
||||
|
||||
$folder_target = "$path\folder"
|
||||
$file_target = "$path\file"
|
||||
$symlink_file_path = "$path\file-symlink"
|
||||
$symlink_folder_path = "$path\folder-symlink"
|
||||
$hardlink_path = "$path\hardlink"
|
||||
$hardlink_path_2 = "$path\hardlink2"
|
||||
$junction_point_path = "$path\junction"
|
||||
|
||||
if (Test-Path -Path $path) {
|
||||
Remove-Item -Path $path -Force -Recurse | Out-Null
|
||||
}
|
||||
New-Item -Path $path -ItemType Directory | Out-Null
|
||||
New-Item -Path $folder_target -ItemType Directory | Out-Null
|
||||
New-Item -Path $file_target -ItemType File | Out-Null
|
||||
Set-Content -Path $file_target -Value "a"
|
||||
|
||||
Function Assert-Equals($actual, $expected) {
|
||||
if ($actual -ne $expected) {
|
||||
Fail-Json @{} "actual != expected`nActual: $actual`nExpected: $expected"
|
||||
}
|
||||
}
|
||||
|
||||
Function Assert-True($expression, $message) {
|
||||
if ($expression -ne $true) {
|
||||
Fail-Json @{} $message
|
||||
}
|
||||
}
|
||||
|
||||
# need to manually set this
|
||||
Load-LinkUtils
|
||||
|
||||
# path is not a link
|
||||
$no_link_result = Get-Link -link_path $path
|
||||
Assert-True -expression ($no_link_result -eq $null) -message "did not return null result for a non link"
|
||||
|
||||
# fail to create hard link pointed to a directory
|
||||
try {
|
||||
New-Link -link_path "$path\folder-hard" -link_target $folder_target -link_type "hard"
|
||||
Assert-True -expression $false -message "creation of hard link should have failed if target was a directory"
|
||||
} catch {
|
||||
Assert-Equals -actual $_.Exception.Message -expected "cannot set the target for a hard link to a directory"
|
||||
}
|
||||
|
||||
# fail to create a junction point pointed to a file
|
||||
try {
|
||||
New-Link -link_path "$path\junction-fail" -link_target $file_target -link_type "junction"
|
||||
Assert-True -expression $false -message "creation of junction point should have failed if target was a file"
|
||||
} catch {
|
||||
Assert-Equals -actual $_.Exception.Message -expected "cannot set the target for a junction point to a file"
|
||||
}
|
||||
|
||||
# fail to create a symbolic link with non-existent target
|
||||
try {
|
||||
New-Link -link_path "$path\symlink-fail" -link_target "$path\fake-folder" -link_type "link"
|
||||
Assert-True -expression $false -message "creation of symbolic link should have failed if target did not exist"
|
||||
} catch {
|
||||
Assert-Equals -actual $_.Exception.Message -expected "link_target '$path\fake-folder' does not exist, cannot create link"
|
||||
}
|
||||
|
||||
# create recursive symlink
|
||||
Run-Command -command "cmd.exe /c mklink /D symlink-rel folder" -working_directory $path | Out-Null
|
||||
$rel_link_result = Get-Link -link_path "$path\symlink-rel"
|
||||
Assert-Equals -actual $rel_link_result.Type -expected "SymbolicLink"
|
||||
Assert-Equals -actual $rel_link_result.SubstituteName -expected "folder"
|
||||
Assert-Equals -actual $rel_link_result.PrintName -expected "folder"
|
||||
Assert-Equals -actual $rel_link_result.TargetPath -expected "folder"
|
||||
Assert-Equals -actual $rel_link_result.AbsolutePath -expected $folder_target
|
||||
Assert-Equals -actual $rel_link_result.HardTargets -expected $null
|
||||
|
||||
# create a symbolic file test
|
||||
New-Link -link_path $symlink_file_path -link_target $file_target -link_type "link"
|
||||
$file_link_result = Get-Link -link_path $symlink_file_path
|
||||
Assert-Equals -actual $file_link_result.Type -expected "SymbolicLink"
|
||||
Assert-Equals -actual $file_link_result.SubstituteName -expected "\??\$file_target"
|
||||
Assert-Equals -actual $file_link_result.PrintName -expected $file_target
|
||||
Assert-Equals -actual $file_link_result.TargetPath -expected $file_target
|
||||
Assert-Equals -actual $file_link_result.AbsolutePath -expected $file_target
|
||||
Assert-Equals -actual $file_link_result.HardTargets -expected $null
|
||||
|
||||
# create a symbolic link folder test
|
||||
New-Link -link_path $symlink_folder_path -link_target $folder_target -link_type "link"
|
||||
$folder_link_result = Get-Link -link_path $symlink_folder_path
|
||||
Assert-Equals -actual $folder_link_result.Type -expected "SymbolicLink"
|
||||
Assert-Equals -actual $folder_link_result.SubstituteName -expected "\??\$folder_target"
|
||||
Assert-Equals -actual $folder_link_result.PrintName -expected $folder_target
|
||||
Assert-Equals -actual $folder_link_result.TargetPath -expected $folder_target
|
||||
Assert-Equals -actual $folder_link_result.AbsolutePath -expected $folder_target
|
||||
Assert-Equals -actual $folder_link_result.HardTargets -expected $null
|
||||
|
||||
# create a junction point test
|
||||
New-Link -link_path $junction_point_path -link_target $folder_target -link_type "junction"
|
||||
$junction_point_result = Get-Link -link_path $junction_point_path
|
||||
Assert-Equals -actual $junction_point_result.Type -expected "JunctionPoint"
|
||||
Assert-Equals -actual $junction_point_result.SubstituteName -expected "\??\$folder_target"
|
||||
Assert-Equals -actual $junction_point_result.PrintName -expected $folder_target
|
||||
Assert-Equals -actual $junction_point_result.TargetPath -expected $folder_target
|
||||
Assert-Equals -actual $junction_point_result.AbsolutePath -expected $folder_target
|
||||
Assert-Equals -actual $junction_point_result.HardTargets -expected $null
|
||||
|
||||
# create a hard link test
|
||||
New-Link -link_path $hardlink_path -link_target $file_target -link_type "hard"
|
||||
$hardlink_result = Get-Link -link_path $hardlink_path
|
||||
Assert-Equals -actual $hardlink_result.Type -expected "HardLink"
|
||||
Assert-Equals -actual $hardlink_result.SubstituteName -expected $null
|
||||
Assert-Equals -actual $hardlink_result.PrintName -expected $null
|
||||
Assert-Equals -actual $hardlink_result.TargetPath -expected $null
|
||||
Assert-Equals -actual $hardlink_result.AbsolutePath -expected $null
|
||||
if ($hardlink_result.HardTargets[0] -ne $hardlink_path -and $hardlink_result.HardTargets[1] -ne $hardlink_path) {
|
||||
Assert-True -expression $false -message "file $hardlink_path is not a target of the hard link"
|
||||
}
|
||||
if ($hardlink_result.HardTargets[0] -ne $file_target -and $hardlink_result.HardTargets[1] -ne $file_target) {
|
||||
Assert-True -expression $false -message "file $file_target is not a target of the hard link"
|
||||
}
|
||||
Assert-equals -actual (Get-Content -Path $hardlink_path -Raw) -expected (Get-Content -Path $file_target -Raw)
|
||||
|
||||
# create a new hard link and verify targets go to 3
|
||||
New-Link -link_path $hardlink_path_2 -link_target $file_target -link_type "hard"
|
||||
$hardlink_result_2 = Get-Link -link_path $hardlink_path
|
||||
Assert-True -expression ($hardlink_result_2.HardTargets.Count -eq 3) -message "did not return 3 targets for the hard link, actual $($hardlink_result_2.Targets.Count)"
|
||||
|
||||
# check if broken symbolic link still works
|
||||
Remove-Item -Path $folder_target -Force | Out-Null
|
||||
$broken_link_result = Get-Link -link_path $symlink_folder_path
|
||||
Assert-Equals -actual $broken_link_result.Type -expected "SymbolicLink"
|
||||
Assert-Equals -actual $broken_link_result.SubstituteName -expected "\??\$folder_target"
|
||||
Assert-Equals -actual $broken_link_result.PrintName -expected $folder_target
|
||||
Assert-Equals -actual $broken_link_result.TargetPath -expected $folder_target
|
||||
Assert-Equals -actual $broken_link_result.AbsolutePath -expected $folder_target
|
||||
Assert-Equals -actual $broken_link_result.HardTargets -expected $null
|
||||
|
||||
# check if broken junction point still works
|
||||
$broken_junction_result = Get-Link -link_path $junction_point_path
|
||||
Assert-Equals -actual $broken_junction_result.Type -expected "JunctionPoint"
|
||||
Assert-Equals -actual $broken_junction_result.SubstituteName -expected "\??\$folder_target"
|
||||
Assert-Equals -actual $broken_junction_result.PrintName -expected $folder_target
|
||||
Assert-Equals -actual $broken_junction_result.TargetPath -expected $folder_target
|
||||
Assert-Equals -actual $broken_junction_result.AbsolutePath -expected $folder_target
|
||||
Assert-Equals -actual $broken_junction_result.HardTargets -expected $null
|
||||
|
||||
# delete file symbolic link
|
||||
Remove-Link -link_path $symlink_file_path
|
||||
Assert-True -expression (-not (Test-Path -Path $symlink_file_path)) -message "failed to delete file symbolic link"
|
||||
|
||||
# delete folder symbolic link
|
||||
Remove-Link -link_path $symlink_folder_path
|
||||
Assert-True -expression (-not (Test-Path -Path $symlink_folder_path)) -message "failed to delete folder symbolic link"
|
||||
|
||||
# delete junction point
|
||||
Remove-Link -link_path $junction_point_path
|
||||
Assert-True -expression (-not (Test-Path -Path $junction_point_path)) -message "failed to delete junction point"
|
||||
|
||||
# delete hard link
|
||||
Remove-Link -link_path $hardlink_path
|
||||
Assert-True -expression (-not (Test-Path -Path $hardlink_path)) -message "failed to delete hard link"
|
||||
|
||||
Exit-Json @{ data = "success" }
|
|
@ -76,6 +76,15 @@
|
|||
that:
|
||||
- argv_test.data == 'success'
|
||||
|
||||
- name: call module with symbolic link tests
|
||||
symbolic_link_test:
|
||||
path: C:\ansible testing
|
||||
register: symbolic_link
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- symbolic_link.data == 'success'
|
||||
|
||||
- name: remove testing folder
|
||||
win_file:
|
||||
path: C:\ansible testing
|
||||
|
|
Loading…
Reference in New Issue