added powershell symlink util helper (#27153)

* Added symbolic link util for powershell

* updated module_util license to BSD
pull/32955/head
Jordan Borean 7 years ago committed by GitHub
parent e16e6313c7
commit 1bc4940ee1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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…
Cancel
Save