Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
PathUtils.cs908 linesDownload Raw Back to utils
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