MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Buffers;5using System.Collections.Generic;6using System.Diagnostics.CodeAnalysis;7using System.Globalization;8using System.IO;9using System.Management.Automation.Internal;10using System.Management.Automation.Runspaces;11using System.Runtime.CompilerServices;12using System.Text;13using Microsoft.PowerShell.Commands;14using Dbg = System.Management.Automation.Diagnostics;15 16namespace System.Management.Automation17{18 /// <summary>19 /// Defines generic utilities and helper methods for PowerShell.20 /// </summary>21 internal static class PathUtils22 {23 /// <summary>24 /// THE method for opening a file for writing.25 /// Should be used by all cmdlets that write to a file.26 /// </summary>27 /// <param name="cmdlet">Cmdlet that is opening the file (used mainly for error reporting).</param>28 /// <param name="filePath">Path to the file (as specified on the command line - this method will resolve the path).</param>29 /// <param name="encoding">Encoding (this method will convert the command line string to an Encoding instance).</param>30 /// <param name="defaultEncoding">If <see langword="true"/>, then we will use default .NET encoding instead of the encoding specified in <paramref name="encoding"/> parameter.</param>31 /// <param name="Append"></param>32 /// <param name="Force"></param>33 /// <param name="NoClobber"></param>34 /// <param name="fileStream">Result1: <see cref="FileStream"/> opened for writing.</param>35 /// <param name="streamWriter">Result2: <see cref="StreamWriter"/> (inherits from <see cref="TextWriter"/>) opened for writing.</param>36 /// <param name="readOnlyFileInfo">Result3: file info that should be used to restore file attributes after done with the file (<see langword="null"/> is this is not needed).</param>37 /// <param name="isLiteralPath">True if wildcard expansion should be bypassed.</param>38 internal static void MasterStreamOpen(39 PSCmdlet cmdlet,40 string filePath,41 string encoding,42 bool defaultEncoding,43 bool Append,44 bool Force,45 bool NoClobber,46 out FileStream fileStream,47 out StreamWriter streamWriter,48 out FileInfo readOnlyFileInfo,49 bool isLiteralPath50 )51 {52 Encoding resolvedEncoding = EncodingConversion.Convert(cmdlet, encoding);53 54 MasterStreamOpen(cmdlet, filePath, resolvedEncoding, defaultEncoding, Append, Force, NoClobber, out fileStream, out streamWriter, out readOnlyFileInfo, isLiteralPath);55 }56 57 /// <summary>58 /// THE method for opening a file for writing.59 /// Should be used by all cmdlets that write to a file.60 /// </summary>61 /// <param name="cmdlet">Cmdlet that is opening the file (used mainly for error reporting).</param>62 /// <param name="filePath">Path to the file (as specified on the command line - this method will resolve the path).</param>63 /// <param name="resolvedEncoding">Encoding (this method will convert the command line string to an Encoding instance).</param>64 /// <param name="defaultEncoding">If <see langword="true"/>, then we will use default .NET encoding instead of the encoding specified in <paramref name="encoding"/> parameter.</param>65 /// <param name="Append"></param>66 /// <param name="Force"></param>67 /// <param name="NoClobber"></param>68 /// <param name="fileStream">Result1: <see cref="FileStream"/> opened for writing.</param>69 /// <param name="streamWriter">Result2: <see cref="StreamWriter"/> (inherits from <see cref="TextWriter"/>) opened for writing.</param>70 /// <param name="readOnlyFileInfo">Result3: file info that should be used to restore file attributes after done with the file (<see langword="null"/> is this is not needed).</param>71 /// <param name="isLiteralPath">True if wildcard expansion should be bypassed.</param>72 internal static void MasterStreamOpen(73 PSCmdlet cmdlet,74 string filePath,75 Encoding resolvedEncoding,76 bool defaultEncoding,77 bool Append,78 bool Force,79 bool NoClobber,80 out FileStream fileStream,81 out StreamWriter streamWriter,82 out FileInfo readOnlyFileInfo,83 bool isLiteralPath)84 {85 fileStream = null;86 streamWriter = null;87 readOnlyFileInfo = null;88 string resolvedPath = ResolveFilePath(filePath, cmdlet, isLiteralPath);89 try90 {91 MasterStreamOpenImpl(92 resolvedPath,93 resolvedEncoding,94 defaultEncoding,95 Append,96 Force,97 NoClobber,98 out fileStream,99 out streamWriter,100 out readOnlyFileInfo);101 }102 // These are the known exceptions for File.Load and StreamWriter.ctor103 catch (ArgumentException e)104 {105 // NOTE: this call will throw106 ReportFileOpenFailure(cmdlet, resolvedPath, e);107 }108 catch (IOException e)109 {110 if (NoClobber && File.Exists(resolvedPath))111 {112 // This probably happened because the file already exists113 ErrorRecord errorRecord = new(114 e, "NoClobber", ErrorCategory.ResourceExists, resolvedPath);115 errorRecord.ErrorDetails = new ErrorDetails(116 cmdlet,117 "PathUtilsStrings",118 "UtilityFileExistsNoClobber",119 filePath,120 "NoClobber"); // prevents localization121 122 // NOTE: this call will throw123 cmdlet.ThrowTerminatingError(errorRecord);124 }125 // NOTE: this call will throw126 ReportFileOpenFailure(cmdlet, resolvedPath, e);127 }128 catch (UnauthorizedAccessException e)129 {130 // NOTE: this call will throw131 ReportFileOpenFailure(cmdlet, resolvedPath, e);132 }133 catch (NotSupportedException e)134 {135 // NOTE: this call will throw136 ReportFileOpenFailure(cmdlet, resolvedPath, e);137 }138 catch (System.Security.SecurityException e)139 {140 // NOTE: this call will throw141 ReportFileOpenFailure(cmdlet, resolvedPath, e);142 }143 }144 145 /// <summary>146 /// THE method for opening a file for writing.147 /// Should be used by all cmdlets that write to a file.148 /// </summary>149 /// <param name="filePath">Path to the file (as specified on the command line - this method will resolve the path).</param>150 /// <param name="resolvedEncoding">Encoding (this method will convert the command line string to an Encoding instance).</param>151 /// <param name="defaultEncoding">If <see langword="true"/>, then we will use default .NET encoding instead of the encoding specified in <paramref name="encoding"/> parameter.</param>152 /// <param name="Append"></param>153 /// <param name="Force"></param>154 /// <param name="NoClobber"></param>155 /// <param name="fileStream">Result1: <see cref="FileStream"/> opened for writing.</param>156 /// <param name="streamWriter">Result2: <see cref="StreamWriter"/> (inherits from <see cref="TextWriter"/>) opened for writing.</param>157 /// <param name="readOnlyFileInfo">Result3: file info that should be used to restore file attributes after done with the file (<see langword="null"/> is this is not needed).</param>158 /// <param name="isLiteralPath">True if wildcard expansion should be bypassed.</param>159 internal static void MasterStreamOpen(160 string filePath,161 Encoding resolvedEncoding,162 bool defaultEncoding,163 bool Append,164 bool Force,165 bool NoClobber,166 out FileStream fileStream,167 out StreamWriter streamWriter,168 out FileInfo readOnlyFileInfo,169 bool isLiteralPath)170 {171 fileStream = null;172 streamWriter = null;173 readOnlyFileInfo = null;174 string resolvedPath = ResolveFilePath(filePath, isLiteralPath);175 try176 {177 MasterStreamOpenImpl(178 resolvedPath,179 resolvedEncoding,180 defaultEncoding,181 Append,182 Force,183 NoClobber,184 out fileStream,185 out streamWriter,186 out readOnlyFileInfo);187 }188 // These are the known exceptions for File.Load and StreamWriter.ctor189 catch (ArgumentException e)190 {191 AddFileOpenErrorRecord(e);192 throw;193 }194 catch (IOException e)195 {196 if (NoClobber && File.Exists(resolvedPath))197 {198 string msg = StringUtil.Format(199 PathUtilsStrings.UtilityFileExistsNoClobber,200 filePath,201 "NoClobber");202 203 // This probably happened because the file already exists204 ErrorRecord errorRecord = new ErrorRecord(205 e, "NoClobber", ErrorCategory.ResourceExists, resolvedPath);206 errorRecord.ErrorDetails = new ErrorDetails(msg);207 208 e.Data[typeof(ErrorRecord)] = errorRecord;209 throw;210 }211 212 AddFileOpenErrorRecord(e);213 throw;214 }215 catch (UnauthorizedAccessException e)216 {217 AddFileOpenErrorRecord(e);218 throw;219 }220 catch (NotSupportedException e)221 {222 AddFileOpenErrorRecord(e);223 throw;224 }225 catch (System.Security.SecurityException e)226 {227 AddFileOpenErrorRecord(e);228 throw;229 }230 231 static void AddFileOpenErrorRecord(Exception e)232 {233 ErrorRecord errorRecord = new ErrorRecord(234 e,235 "FileOpenFailure",236 ErrorCategory.OpenError,237 null);238 239 e.Data[typeof(ErrorRecord)] = errorRecord;240 }241 }242 243 /// <summary>244 /// THE method for opening a file for writing.245 /// Should be used by all cmdlets that write to a file.246 /// </summary>247 /// <param name="resolvedPath">Path to the file (as specified on the command line - this method will resolve the path).</param>248 /// <param name="resolvedEncoding">Encoding (this method will convert the command line string to an Encoding instance).</param>249 /// <param name="defaultEncoding">If <see langword="true"/>, then we will use default .NET encoding instead of the encoding specified in <paramref name="encoding"/> parameter.</param>250 /// <param name="Append"></param>251 /// <param name="Force"></param>252 /// <param name="NoClobber"></param>253 /// <param name="fileStream">Result1: <see cref="FileStream"/> opened for writing.</param>254 /// <param name="streamWriter">Result2: <see cref="StreamWriter"/> (inherits from <see cref="TextWriter"/>) opened for writing.</param>255 /// <param name="readOnlyFileInfo">Result3: file info that should be used to restore file attributes after done with the file (<see langword="null"/> is this is not needed).</param>256 internal static void MasterStreamOpenImpl(257 string resolvedPath,258 Encoding resolvedEncoding,259 bool defaultEncoding,260 bool Append,261 bool Force,262 bool NoClobber,263 out FileStream fileStream,264 out StreamWriter streamWriter,265 out FileInfo readOnlyFileInfo)266 {267 fileStream = null;268 streamWriter = null;269 readOnlyFileInfo = null;270 271 // variable to track file open mode272 // this is controlled by append/force parameters273 FileMode mode = FileMode.Create;274 if (Append)275 {276 mode = FileMode.Append;277 }278 else if (NoClobber)279 {280 // throw IOException if file exists281 mode = FileMode.CreateNew;282 }283 284 if (Force && (Append || !NoClobber))285 {286 if (File.Exists(resolvedPath))287 {288 FileInfo fInfo = new FileInfo(resolvedPath);289 if ((fInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)290 {291 // remember to reset the read-only attribute later292 readOnlyFileInfo = fInfo;293 // Clear the read-only attribute294 fInfo.Attributes &= ~(FileAttributes.ReadOnly);295 }296 }297 }298 299 // if the user knows what he/she is doing and uses "-Force" switch,300 // then we let more than 1 process write to the same file at the same time301 FileShare fileShare = Force ? FileShare.ReadWrite : FileShare.Read;302 303 // mode is controlled by force and ShouldContinue()304 fileStream = new FileStream(resolvedPath, mode, FileAccess.Write, fileShare);305 306 // create stream writer307 // NTRAID#Windows Out Of Band Releases-931008-2006/03/27308 // For some reason, calling this without specifying309 // the encoding is different from passing Encoding.Default.310 if (defaultEncoding)311 streamWriter = new StreamWriter(fileStream);312 else313 streamWriter = new StreamWriter(fileStream, resolvedEncoding);314 }315 316 internal static void ReportFileOpenFailure(Cmdlet cmdlet, string filePath, Exception e)317 {318 ErrorRecord errorRecord = new ErrorRecord(319 e,320 "FileOpenFailure",321 ErrorCategory.OpenError,322 null);323 324 cmdlet.ThrowTerminatingError(errorRecord);325 }326 327 internal static void ReportFileOpenFailure(string filePath, Exception e)328 {329 ErrorRecord errorRecord = new ErrorRecord(330 e,331 "FileOpenFailure",332 ErrorCategory.OpenError,333 null);334 335 throw new RuntimeException(336 e.Message,337 errorRecord.Exception,338 errorRecord);339 }340 341 internal static StreamReader OpenStreamReader(PSCmdlet command, string filePath, Encoding encoding, bool isLiteralPath)342 {343 FileStream fileStream = OpenFileStream(filePath, command, isLiteralPath);344 return new StreamReader(fileStream, encoding);345 }346 347 internal static FileStream OpenFileStream(string filePath, PSCmdlet command, bool isLiteralPath)348 {349 string resolvedPath = PathUtils.ResolveFilePath(filePath, command, isLiteralPath);350 351 try352 {353 return new FileStream(resolvedPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);354 }355 // These are the known exceptions for FileStream.ctor356 catch (ArgumentException e)357 {358 PathUtils.ReportFileOpenFailure(command, filePath, e);359 return null; // the line above will throw - silencing the compiler360 }361 catch (IOException e)362 {363 PathUtils.ReportFileOpenFailure(command, filePath, e);364 return null; // the line above will throw - silencing the compiler365 }366 catch (UnauthorizedAccessException e)367 {368 PathUtils.ReportFileOpenFailure(command, filePath, e);369 return null; // the line above will throw - silencing the compiler370 }371 catch (NotSupportedException e)372 {373 PathUtils.ReportFileOpenFailure(command, filePath, e);374 return null; // the line above will throw - silencing the compiler375 }376 catch (System.Management.Automation.DriveNotFoundException e)377 {378 PathUtils.ReportFileOpenFailure(command, filePath, e);379 return null; // the line above will throw - silencing the compiler380 }381 }382 383 /// <summary>384 /// Resolve a user provided file name or path (including globbing characters)385 /// to a fully qualified file path, using the file system provider.386 /// </summary>387 /// <param name="filePath"></param>388 /// <param name="command"></param>389 /// <returns></returns>390 internal static string ResolveFilePath(string filePath, PSCmdlet command)391 {392 return ResolveFilePath(filePath, command, false);393 }394 395 /// <summary>396 /// Resolve a user provided file name or path (including globbing characters)397 /// to a fully qualified file path, using the file system provider.398 /// </summary>399 /// <param name="filePath"></param>400 /// <param name="command"></param>401 /// <param name="isLiteralPath"></param>402 /// <returns></returns>403 internal static string ResolveFilePath(string filePath, PSCmdlet command, bool isLiteralPath)404 {405 string path = null;406 407 try408 {409 ProviderInfo provider = null;410 PSDriveInfo drive = null;411 List<string> filePaths = new List<string>();412 413 if (isLiteralPath)414 {415 filePaths.Add(command.SessionState.Path.GetUnresolvedProviderPathFromPSPath(filePath, out provider, out drive));416 }417 else418 {419 filePaths.AddRange(command.SessionState.Path.GetResolvedProviderPathFromPSPath(filePath, out provider));420 }421 422 if (!provider.NameEquals(command.Context.ProviderNames.FileSystem))423 {424 ReportWrongProviderType(command, provider.FullName);425 }426 427 if (filePaths.Count > 1)428 {429 ReportMultipleFilesNotSupported(command);430 }431 432 if (filePaths.Count == 0)433 {434 ReportWildcardingFailure(command, filePath);435 }436 437 path = filePaths[0];438 }439 catch (ItemNotFoundException)440 {441 path = null;442 }443 444 if (string.IsNullOrEmpty(path))445 {446 CmdletProviderContext cmdletProviderContext = new CmdletProviderContext(command);447 ProviderInfo provider = null;448 PSDriveInfo drive = null;449 path =450 command.SessionState.Path.GetUnresolvedProviderPathFromPSPath(451 filePath, cmdletProviderContext, out provider, out drive);452 cmdletProviderContext.ThrowFirstErrorOrDoNothing();453 if (!provider.NameEquals(command.Context.ProviderNames.FileSystem))454 {455 ReportWrongProviderType(command, provider.FullName);456 }457 }458 459 return path;460 }461 462 /// <summary>463 /// Resolve a user provided file name or path (including globbing characters)464 /// to a fully qualified file path, using the file system provider.465 /// </summary>466 /// <param name="filePath"></param>467 /// <param name="isLiteralPath"></param>468 /// <returns></returns>469 internal static string ResolveFilePath(string filePath, bool isLiteralPath)470 {471 string path = null;472 473 SessionState sessionState = LocalPipeline.GetExecutionContextFromTLS()?.EngineSessionState?.PublicSessionState;474 if (sessionState is null)475 {476 return null;477 }478 479 try480 {481 ProviderInfo provider = null;482 PSDriveInfo drive = null;483 List<string> filePaths = new();484 485 if (isLiteralPath)486 {487 filePaths.Add(sessionState.Path.GetUnresolvedProviderPathFromPSPath(filePath, out provider, out drive));488 }489 else490 {491 filePaths.AddRange(sessionState.Path.GetResolvedProviderPathFromPSPath(filePath, out provider));492 }493 494 if (!provider.NameEquals(FileSystemProvider.ProviderName))495 {496 ReportWrongProviderType(provider.FullName);497 }498 499 if (filePaths.Count > 1)500 {501 ReportMultipleFilesNotSupported();502 }503 504 if (filePaths.Count == 0)505 {506 ReportWildcardingFailure(filePath);507 }508 509 path = filePaths[0];510 }511 catch (ItemNotFoundException)512 {513 path = null;514 }515 516 return path;517 }518 519 internal static void ReportWrongProviderType(Cmdlet cmdlet, string providerId)520 {521 string msg = StringUtil.Format(PathUtilsStrings.OutFile_ReadWriteFileNotFileSystemProvider, providerId);522 523 ErrorRecord errorRecord = new ErrorRecord(524 PSTraceSource.NewInvalidOperationException(),525 "ReadWriteFileNotFileSystemProvider",526 ErrorCategory.InvalidArgument,527 null);528 529 errorRecord.ErrorDetails = new ErrorDetails(msg);530 cmdlet.ThrowTerminatingError(errorRecord);531 }532 533 internal static void ReportWrongProviderType(string providerId)534 {535 string msg = StringUtil.Format(PathUtilsStrings.OutFile_ReadWriteFileNotFileSystemProvider, providerId);536 537 PSInvalidOperationException exception = PSTraceSource.NewInvalidOperationException();538 539 ErrorRecord errorRecord = new(540 exception,541 "ReadWriteFileNotFileSystemProvider",542 ErrorCategory.InvalidArgument,543 null);544 545 errorRecord.ErrorDetails = new ErrorDetails(msg);546 exception.Data[typeof(ErrorRecord)] = errorRecord;547 throw exception;548 }549 550 internal static void ReportMultipleFilesNotSupported(Cmdlet cmdlet)551 {552 string msg = StringUtil.Format(PathUtilsStrings.OutFile_MultipleFilesNotSupported);553 554 ErrorRecord errorRecord = new ErrorRecord(555 PSTraceSource.NewInvalidOperationException(),556 "ReadWriteMultipleFilesNotSupported",557 ErrorCategory.InvalidArgument,558 null);559 560 errorRecord.ErrorDetails = new ErrorDetails(msg);561 cmdlet.ThrowTerminatingError(errorRecord);562 }563 564 internal static void ReportMultipleFilesNotSupported()565 {566 string msg = StringUtil.Format(PathUtilsStrings.OutFile_MultipleFilesNotSupported);567 568 PSInvalidOperationException exception = PSTraceSource.NewInvalidOperationException();569 570 ErrorRecord errorRecord = new(571 exception,572 "ReadWriteMultipleFilesNotSupported",573 ErrorCategory.InvalidArgument,574 null);575 576 errorRecord.ErrorDetails = new ErrorDetails(msg);577 exception.Data[typeof(ErrorRecord)] = errorRecord;578 throw exception;579 }580 581 internal static void ReportWildcardingFailure(Cmdlet cmdlet, string filePath)582 {583 string msg = StringUtil.Format(PathUtilsStrings.OutFile_DidNotResolveFile, filePath);584 585 ErrorRecord errorRecord = new ErrorRecord(586 new FileNotFoundException(),587 "FileOpenFailure",588 ErrorCategory.OpenError,589 filePath);590 591 errorRecord.ErrorDetails = new ErrorDetails(msg);592 cmdlet.ThrowTerminatingError(errorRecord);593 }594 595 internal static void ReportWildcardingFailure(string filePath)596 {597 string msg = StringUtil.Format(PathUtilsStrings.OutFile_DidNotResolveFile, filePath);598 599 FileNotFoundException exception = new();600 ErrorRecord errorRecord = new(601 exception,602 "FileOpenFailure",603 ErrorCategory.OpenError,604 filePath);605 606 errorRecord.ErrorDetails = new ErrorDetails(msg);607 exception.Data[typeof(ErrorRecord)] = errorRecord;608 throw exception;609 }610 611 internal static DirectoryInfo CreateModuleDirectory(PSCmdlet cmdlet, string moduleNameOrPath, bool force)612 {613 Dbg.Assert(cmdlet != null, "Caller should verify cmdlet != null");614 Dbg.Assert(!string.IsNullOrEmpty(moduleNameOrPath), "Caller should verify !string.IsNullOrEmpty(moduleNameOrPath)");615 616 DirectoryInfo directoryInfo = null;617 try618 {619 // Even if 'moduleNameOrPath' is a rooted path, 'ResolveRootedFilePath' may return null when the path doesn't exist yet,620 // or when it contains wildcards but cannot be resolved to a single path.621 string rootedPath = ModuleCmdletBase.ResolveRootedFilePath(moduleNameOrPath, cmdlet.Context);622 if (string.IsNullOrEmpty(rootedPath) && moduleNameOrPath.StartsWith('.'))623 {624 PathInfo currentPath = cmdlet.CurrentProviderLocation(cmdlet.Context.ProviderNames.FileSystem);625 rootedPath = Path.Combine(currentPath.ProviderPath, moduleNameOrPath);626 }627 628 if (string.IsNullOrEmpty(rootedPath))629 {630 if (Path.IsPathRooted(moduleNameOrPath))631 {632 rootedPath = moduleNameOrPath;633 }634 else635 {636 string personalModuleRoot = ModuleIntrinsics.GetPersonalModulePath();637 if (string.IsNullOrEmpty(personalModuleRoot))638 {639 cmdlet.ThrowTerminatingError(640 new ErrorRecord(641 new ArgumentException(StringUtil.Format(PathUtilsStrings.ExportPSSession_ErrorModuleNameOrPath, moduleNameOrPath)),642 "ExportPSSession_ErrorModuleNameOrPath",643 ErrorCategory.InvalidArgument,644 cmdlet));645 }646 647 rootedPath = Path.Combine(personalModuleRoot, moduleNameOrPath);648 }649 }650 651 directoryInfo = new DirectoryInfo(rootedPath);652 if (directoryInfo.Exists)653 {654 if (!force)655 {656 string errorMessage = string.Format(657 CultureInfo.InvariantCulture, // directory name should be treated as culture-invariant658 PathUtilsStrings.ExportPSSession_ErrorDirectoryExists,659 directoryInfo.FullName);660 ErrorDetails details = new ErrorDetails(errorMessage);661 ErrorRecord errorRecord = new ErrorRecord(662 new ArgumentException(details.Message),663 "ExportProxyCommand_OutputDirectoryExists",664 ErrorCategory.ResourceExists,665 directoryInfo);666 cmdlet.ThrowTerminatingError(errorRecord);667 }668 }669 else670 {671 directoryInfo.Create();672 }673 }674 catch (Exception e)675 {676 string errorMessage = string.Format(677 CultureInfo.InvariantCulture, // directory name should be treated as culture-invariant678 PathUtilsStrings.ExportPSSession_CannotCreateOutputDirectory,679 moduleNameOrPath,680 e.Message);681 ErrorDetails details = new ErrorDetails(errorMessage);682 ErrorRecord errorRecord = new ErrorRecord(683 new ArgumentException(details.Message, e),684 "ExportProxyCommand_CannotCreateOutputDirectory",685 ErrorCategory.ResourceExists,686 moduleNameOrPath);687 cmdlet.ThrowTerminatingError(errorRecord);688 }689 690 return directoryInfo;691 }692 693 internal static DirectoryInfo CreateTemporaryDirectory()694 {695 DirectoryInfo temporaryDirectory = new DirectoryInfo(Path.GetTempPath());696 DirectoryInfo moduleDirectory;697 do698 {699 moduleDirectory = new DirectoryInfo(700 Path.Combine(701 temporaryDirectory.FullName,702 string.Format(703 null,704 "tmp_{0}",705 Path.GetRandomFileName())));706 } while (moduleDirectory.Exists);707 708 Directory.CreateDirectory(moduleDirectory.FullName);709 return new DirectoryInfo(moduleDirectory.FullName);710 }711 712 internal static bool TryDeleteFile(string filepath)713 {714 if (IO.File.Exists(filepath))715 {716 try717 {718 IO.File.Delete(filepath);719 return true;720 }721 catch (IOException)722 {723 // file is in use on Windows724 }725 catch (UnauthorizedAccessException)726 {727 // user does not have permissions728 }729 }730 731 return false;732 }733 734 #region Helpers for long paths from .Net Runtime735 736 // Code here is copied from .NET's internal path helper implementation:737 // https://github.com/dotnet/runtime/blob/dcce0f56e10f5ac9539354b049341a2d7c0cdebf/src/libraries/System.Private.CoreLib/src/System/IO/PathInternal.Windows.cs738 // It has been left as a verbatim copy.739 740#nullable enable741 742 /// <summary>743 /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative,744 /// AND the path is more than 259 characters. (> MAX_PATH + null). This will also insert the extended745 /// prefix if the path ends with a period or a space. Trailing periods and spaces are normally eaten746 /// away from paths during normalization, but if we see such a path at this point it should be747 /// normalized and has retained the final characters. (Typically from one of the *Info classes).748 /// </summary>749 /// <param name="path">File path.</param>750 /// <returns>File path (with extended prefix if the path is long path).</returns>751 [return: NotNullIfNotNull(nameof(path))]752 internal static string? EnsureExtendedPrefixIfNeeded(string? path)753 {754 if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path)))755 {756 return EnsureExtendedPrefix(path);757 }758 else759 {760 return path;761 }762 }763 764 internal static string EnsureExtendedPrefix(string path)765 {766 if (IsPartiallyQualified(path) || IsDevice(path))767 return path;768 769 // Given \\server\share in longpath becomes \\?\UNC\server\share770 if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase))771 return path.Insert(2, UncDevicePrefixToInsert);772 773 return ExtendedDevicePathPrefix + path;774 }775 776 private const string ExtendedDevicePathPrefix = @"\\?\";777 private const string UncPathPrefix = @"\\";778 private const string UncDevicePrefixToInsert = @"?\UNC\";779 private const string UncExtendedPathPrefix = @"\\?\UNC\";780 private const string DevicePathPrefix = @"\\.\";781 private const int MaxShortPath = 260;782 783 // \\?\, \\.\, \??\784 private const int DevicePrefixLength = 4;785 786 private static bool EndsWithPeriodOrSpace(string? path)787 {788 if (string.IsNullOrEmpty(path))789 {790 return false;791 }792 793 char c = path[path.Length - 1];794 return c == ' ' || c == '.';795 }796 797 /// <summary>798 /// Returns true if the given character is a valid drive letter799 /// </summary>800 private static bool IsValidDriveChar(char value)801 {802 return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'));803 }804 805 private static bool IsDevice(string path)806 {807 return IsExtended(path)808 ||809 (810 path.Length >= DevicePrefixLength811 && IsDirectorySeparator(path[0])812 && IsDirectorySeparator(path[1])813 && (path[2] == '.' || path[2] == '?')814 && IsDirectorySeparator(path[3])815 );816 }817 818 private static bool IsExtended(string path)819 {820 return path.Length >= DevicePrefixLength821 && path[0] == '\\'822 && (path[1] == '\\' || path[1] == '?')823 && path[2] == '?'824 && path[3] == '\\';825 }826 827 /// <summary>828 /// Returns true if the path specified is relative to the current drive or working directory.829 /// Returns false if the path is fixed to a specific drive or UNC path. This method does no830 /// validation of the path (URIs will be returned as relative as a result).831 /// </summary>832 /// <remarks>833 /// Handles paths that use the alternate directory separator. It is a frequent mistake to834 /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.835 /// "C:a" is drive relative- meaning that it will be resolved against the current directory836 /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory837 /// will not be used to modify the path).838 /// </remarks>839 private static bool IsPartiallyQualified(string path)840 {841 if (path.Length < 2)842 {843 // It isn't fixed, it must be relative. There is no way to specify a fixed844 // path with one character (or less).845 return true;846 }847 848 if (IsDirectorySeparator(path[0]))849 {850 // There is no valid way to specify a relative path with two initial slashes or851 // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\852 return !(path[1] == '?' || IsDirectorySeparator(path[1]));853 }854 855 // The only way to specify a fixed path that doesn't begin with two slashes856 // is the drive, colon, slash format- i.e. C:\857 return !((path.Length >= 3)858 && (path[1] == Path.VolumeSeparatorChar)859 && IsDirectorySeparator(path[2])860 // To match old behavior we'll check the drive character for validity as the path is technically861 // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.862 && IsValidDriveChar(path[0]));863 }864 /// <summary>865 /// True if the given character is a directory separator.866 /// </summary>867 [MethodImpl(MethodImplOptions.AggressiveInlining)]868 private static bool IsDirectorySeparator(char c)869 {870 return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;871 }872 873 #endregion874 875 #region Helpers for checking invalid paths using SearchValues876 877 /// <summary>878 /// Contains characters that are invalid in file names.879 /// </summary>880 private static readonly SearchValues<char> s_invalidFileNameChars881 = SearchValues.Create(Path.GetInvalidFileNameChars());882 883 /// <summary>884 /// Contains characters that are invalid in path names.885 /// </summary>886 private static readonly SearchValues<char> s_invalidPathChars887 = SearchValues.Create(Path.GetInvalidPathChars());888 889 /// <summary>890 /// Checks if the specified filename contains any characters that are invalid in file names.891 /// </summary>892 /// <param name="filename">The path to check.</param>893 /// <returns>True if the filename contains invalid file name characters, otherwise false.</returns>894 internal static bool ContainsInvalidFileNameChars(ReadOnlySpan<char> filename)895 => filename.ContainsAny(s_invalidFileNameChars);896 897 /// <summary>898 /// Checks if the specified path contains any characters that are invalid in path names.899 /// </summary>900 /// <param name="path">The path to check.</param>901 /// <returns>True if the path contains invalid path characters, otherwise false.</returns>902 internal static bool ContainsInvalidPathChars(ReadOnlySpan<char> path)903 => path.ContainsAny(s_invalidPathChars);904 905 #endregion906 }907}908 