1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
|
########################################
# Private functions.
########################################
function ConvertFrom-LongFormPath {
[CmdletBinding()]
param([string]$Path)
if ($Path) {
if ($Path.StartsWith('\\?\UNC')) {
# E.g. \\?\UNC\server\share -> \\server\share
return $Path.Substring(1, '\?\UNC'.Length)
} elseif ($Path.StartsWith('\\?\')) {
# E.g. \\?\C:\directory -> C:\directory
return $Path.Substring('\\?\'.Length)
}
}
return $Path
}
function ConvertTo-LongFormPath {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Path)
[string]$longFormPath = Get-FullNormalizedPath -Path $Path
if ($longFormPath -and !$longFormPath.StartsWith('\\?')) {
if ($longFormPath.StartsWith('\\')) {
# E.g. \\server\share -> \\?\UNC\server\share
return "\\?\UNC$($longFormPath.Substring(1))"
} else {
# E.g. C:\directory -> \\?\C:\directory
return "\\?\$longFormPath"
}
}
return $longFormPath
}
# TODO: ADD A SWITCH TO EXCLUDE FILES, A SWITCH TO EXCLUDE DIRECTORIES, AND A SWITCH NOT TO FOLLOW REPARSE POINTS.
function Get-DirectoryChildItem {
[CmdletBinding()]
param(
[string]$Path,
[ValidateNotNullOrEmpty()]
[Parameter()]
[string]$Filter = "*",
[switch]$Force,
[VstsTaskSdk.FS.FindFlags]$Flags = [VstsTaskSdk.FS.FindFlags]::LargeFetch,
[VstsTaskSdk.FS.FindInfoLevel]$InfoLevel = [VstsTaskSdk.FS.FindInfoLevel]::Basic,
[switch]$Recurse)
$stackOfDirectoryQueues = New-Object System.Collections.Stack
while ($true) {
$directoryQueue = New-Object System.Collections.Queue
$fileQueue = New-Object System.Collections.Queue
$findData = New-Object VstsTaskSdk.FS.FindData
$longFormPath = (ConvertTo-LongFormPath $Path)
$handle = $null
try {
$handle = [VstsTaskSdk.FS.NativeMethods]::FindFirstFileEx(
[System.IO.Path]::Combine($longFormPath, $Filter),
$InfoLevel,
$findData,
[VstsTaskSdk.FS.FindSearchOps]::NameMatch,
[System.IntPtr]::Zero,
$Flags)
if (!$handle.IsInvalid) {
while ($true) {
if ($findData.fileName -notin '.', '..') {
$attributes = [VstsTaskSdk.FS.Attributes]$findData.fileAttributes
# If the item is hidden, check if $Force is specified.
if ($Force -or !$attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Hidden)) {
# Create the item.
$item = New-Object -TypeName psobject -Property @{
'Attributes' = $attributes
'FullName' = (ConvertFrom-LongFormPath -Path ([System.IO.Path]::Combine($Path, $findData.fileName)))
'Name' = $findData.fileName
}
# Output directories immediately.
if ($item.Attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Directory)) {
$item
# Append to the directory queue if recursive and default filter.
if ($Recurse -and $Filter -eq '*') {
$directoryQueue.Enqueue($item)
}
} else {
# Hold the files until all directories have been output.
$fileQueue.Enqueue($item)
}
}
}
if (!([VstsTaskSdk.FS.NativeMethods]::FindNextFile($handle, $findData))) { break }
if ($handle.IsInvalid) {
throw (New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList @(
[System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
Get-LocString -Key PSLIB_EnumeratingSubdirectoriesFailedForPath0 -ArgumentList $Path
))
}
}
}
} finally {
if ($handle -ne $null) { $handle.Dispose() }
}
# If recursive and non-default filter, queue child directories.
if ($Recurse -and $Filter -ne '*') {
$findData = New-Object VstsTaskSdk.FS.FindData
$handle = $null
try {
$handle = [VstsTaskSdk.FS.NativeMethods]::FindFirstFileEx(
[System.IO.Path]::Combine($longFormPath, '*'),
[VstsTaskSdk.FS.FindInfoLevel]::Basic,
$findData,
[VstsTaskSdk.FS.FindSearchOps]::NameMatch,
[System.IntPtr]::Zero,
$Flags)
if (!$handle.IsInvalid) {
while ($true) {
if ($findData.fileName -notin '.', '..') {
$attributes = [VstsTaskSdk.FS.Attributes]$findData.fileAttributes
# If the item is hidden, check if $Force is specified.
if ($Force -or !$attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Hidden)) {
# Collect directories only.
if ($attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Directory)) {
# Create the item.
$item = New-Object -TypeName psobject -Property @{
'Attributes' = $attributes
'FullName' = (ConvertFrom-LongFormPath -Path ([System.IO.Path]::Combine($Path, $findData.fileName)))
'Name' = $findData.fileName
}
$directoryQueue.Enqueue($item)
}
}
}
if (!([VstsTaskSdk.FS.NativeMethods]::FindNextFile($handle, $findData))) { break }
if ($handle.IsInvalid) {
throw (New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList @(
[System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
Get-LocString -Key PSLIB_EnumeratingSubdirectoriesFailedForPath0 -ArgumentList $Path
))
}
}
}
} finally {
if ($handle -ne $null) { $handle.Dispose() }
}
}
# Output the files.
$fileQueue
# Push the directory queue onto the stack if any directories were found.
if ($directoryQueue.Count) { $stackOfDirectoryQueues.Push($directoryQueue) }
# Break out of the loop if no more directory queues to process.
if (!$stackOfDirectoryQueues.Count) { break }
# Get the next path.
$directoryQueue = $stackOfDirectoryQueues.Peek()
$Path = $directoryQueue.Dequeue().FullName
# Pop the directory queue if it's empty.
if (!$directoryQueue.Count) { $null = $stackOfDirectoryQueues.Pop() }
}
}
function Get-FullNormalizedPath {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Path)
[string]$outPath = $Path
[uint32]$bufferSize = [VstsTaskSdk.FS.NativeMethods]::GetFullPathName($Path, 0, $null, $null)
[int]$lastWin32Error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
if ($bufferSize -gt 0) {
$absolutePath = New-Object System.Text.StringBuilder([int]$bufferSize)
[uint32]$length = [VstsTaskSdk.FS.NativeMethods]::GetFullPathName($Path, $bufferSize, $absolutePath, $null)
$lastWin32Error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
if ($length -gt 0) {
$outPath = $absolutePath.ToString()
} else {
throw (New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList @(
$lastWin32Error
Get-LocString -Key PSLIB_PathLengthNotReturnedFor0 -ArgumentList $Path
))
}
} else {
throw (New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList @(
$lastWin32Error
Get-LocString -Key PSLIB_PathLengthNotReturnedFor0 -ArgumentList $Path
))
}
if ($outPath.EndsWith('\') -and !$outPath.EndsWith(':\')) {
$outPath = $outPath.TrimEnd('\')
}
$outPath
}
########################################
# Types.
########################################
# If the type has already been loaded once, then it is not loaded again.
Write-Verbose "Adding long path native methods."
Add-Type -Debug:$false -TypeDefinition @'
namespace VstsTaskSdk.FS
{
using System;
using System.Runtime.InteropServices;
public static class NativeMethods
{
private const string Kernel32Dll = "kernel32.dll";
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FindClose(IntPtr hFindFile);
// HANDLE WINAPI FindFirstFile(
// _In_ LPCTSTR lpFileName,
// _Out_ LPWIN32_FIND_DATA lpFindFileData
// );
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern SafeFindHandle FindFirstFile(
[MarshalAs(UnmanagedType.LPTStr)]
string fileName,
[In, Out] FindData findFileData
);
//HANDLE WINAPI FindFirstFileEx(
// _In_ LPCTSTR lpFileName,
// _In_ FINDEX_INFO_LEVELS fInfoLevelId,
// _Out_ LPVOID lpFindFileData,
// _In_ FINDEX_SEARCH_OPS fSearchOp,
// _Reserved_ LPVOID lpSearchFilter,
// _In_ DWORD dwAdditionalFlags
//);
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern SafeFindHandle FindFirstFileEx(
[MarshalAs(UnmanagedType.LPTStr)]
string fileName,
[In] FindInfoLevel fInfoLevelId,
[In, Out] FindData lpFindFileData,
[In] FindSearchOps fSearchOp,
IntPtr lpSearchFilter,
[In] FindFlags dwAdditionalFlags
);
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FindNextFile(SafeFindHandle hFindFile, [In, Out] FindData lpFindFileData);
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern int GetFileAttributes(string lpFileName);
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern uint GetFullPathName(
[MarshalAs(UnmanagedType.LPTStr)]
string lpFileName,
uint nBufferLength,
[Out]
System.Text.StringBuilder lpBuffer,
System.Text.StringBuilder lpFilePart
);
}
//for mapping to the WIN32_FIND_DATA native structure
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public sealed class FindData
{
// NOTE:
// Although it may seem correct to Marshal the string members of this class as UnmanagedType.LPWStr, they
// must explicitly remain UnmanagedType.ByValTStr with the size constraints noted. Otherwise we end up with
// COM Interop exceptions while trying to marshal the data across the PInvoke boundaries.
public int fileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME creationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME lastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME lastWriteTime;
public int nFileSizeHigh;
public int nFileSizeLow;
public int dwReserved0;
public int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string fileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string alternateFileName;
}
//A Win32 safe find handle in which a return value of -1 indicates it's invalid
public sealed class SafeFindHandle : Microsoft.Win32.SafeHandles.SafeHandleMinusOneIsInvalid
{
public SafeFindHandle()
: base(true)
{
return;
}
[System.Runtime.ConstrainedExecution.ReliabilityContract(System.Runtime.ConstrainedExecution.Consistency.WillNotCorruptState, System.Runtime.ConstrainedExecution.Cer.Success)]
protected override bool ReleaseHandle()
{
return NativeMethods.FindClose(handle);
}
}
// TODO: UPDATE THIS BASED ON: https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx
[Flags]
public enum Attributes : uint
{
None = 0x00000000,
Readonly = 0x00000001,
Hidden = 0x00000002,
System = 0x00000004,
Directory = 0x00000010,
Archive = 0x00000020,
Device = 0x00000040,
Normal = 0x00000080,
Temporary = 0x00000100,
SparseFile = 0x00000200,
ReparsePoint = 0x00000400,
Compressed = 0x00000800,
Offline = 0x00001000,
NotContentIndexed = 0x00002000,
Encrypted = 0x00004000,
Write_Through = 0x80000000,
Overlapped = 0x40000000,
NoBuffering = 0x20000000,
RandomAccess = 0x10000000,
SequentialScan = 0x08000000,
DeleteOnClose = 0x04000000,
BackupSemantics = 0x02000000,
PosixSemantics = 0x01000000,
OpenReparsePoint = 0x00200000,
OpenNoRecall = 0x00100000,
FirstPipeInstance = 0x00080000
}
[Flags]
public enum FindFlags
{
None = 0,
CaseSensitive = 1,
LargeFetch = 2,
}
public enum FindInfoLevel
{
Standard = 0,
Basic = 1,
}
public enum FindSearchOps
{
NameMatch = 0,
LimitToDirectories = 1,
LimitToDevices = 2,
}
}
'@
|