mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-15 14:20:03 +00:00
This commit uses psl-monad branch source-depot 7209cb9 And corresponds to [SD:688741]
524 lines
22 KiB
C#
524 lines
22 KiB
C#
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
using System.Management.Automation.Internal;
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// Defines generic utilities and helper methods for PowerShell
|
|
/// </summary>
|
|
internal static class PathUtils
|
|
{
|
|
/// <summary>
|
|
/// THE method for opening a file for writing.
|
|
/// Should be used by all cmdlets that write to a file.
|
|
/// </summary>
|
|
/// <param name="cmdlet">cmdlet that is opening the file (used mainly for error reporting)</param>
|
|
/// <param name="filePath">path to the file (as specified on the command line - this method will resolve the path)</param>
|
|
/// <param name="encoding">encoding (this method will convert the command line strin to an Encoding instance)</param>
|
|
/// <param name="defaultEncoding">if <c>true</c>, then we will use default .NET encoding instead of the encoding specified in <paramref name="encoding"/> parameter</param>
|
|
/// <param name="Append"></param>
|
|
/// <param name="Force"></param>
|
|
/// <param name="NoClobber"></param>
|
|
/// <param name="fileStream">Result1: <see cref="FileStream"/> opened for writing</param>
|
|
/// <param name="streamWriter">Result2: <see cref="StreamWriter"/> (inherits from <see cref="TextWriter"/>) opened for writing</param>
|
|
/// <param name="readOnlyFileInfo">Result3: file info that should be used to restore file attributes after done with the file (<c>null</c> is this is not needed)</param>
|
|
/// <param name="isLiteralPath">True if wildcard expansion should be bypassed.</param>
|
|
internal static void MasterStreamOpen(
|
|
PSCmdlet cmdlet,
|
|
string filePath,
|
|
string encoding,
|
|
bool defaultEncoding,
|
|
bool Append,
|
|
bool Force,
|
|
bool NoClobber,
|
|
out FileStream fileStream,
|
|
out StreamWriter streamWriter,
|
|
out FileInfo readOnlyFileInfo,
|
|
bool isLiteralPath
|
|
)
|
|
{
|
|
Encoding resolvedEncoding = EncodingConversion.Convert(cmdlet, encoding);
|
|
|
|
MasterStreamOpen(cmdlet, filePath, resolvedEncoding, defaultEncoding, Append, Force, NoClobber, out fileStream, out streamWriter, out readOnlyFileInfo, isLiteralPath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// THE method for opening a file for writing.
|
|
/// Should be used by all cmdlets that write to a file.
|
|
/// </summary>
|
|
/// <param name="cmdlet">cmdlet that is opening the file (used mainly for error reporting)</param>
|
|
/// <param name="filePath">path to the file (as specified on the command line - this method will resolve the path)</param>
|
|
/// <param name="resolvedEncoding">encoding (this method will convert the command line strin to an Encoding instance)</param>
|
|
/// <param name="defaultEncoding">if <c>true</c>, then we will use default .NET encoding instead of the encoding specified in <paramref name="encoding"/> parameter</param>
|
|
/// <param name="Append"></param>
|
|
/// <param name="Force"></param>
|
|
/// <param name="NoClobber"></param>
|
|
/// <param name="fileStream">Result1: <see cref="FileStream"/> opened for writing</param>
|
|
/// <param name="streamWriter">Result2: <see cref="StreamWriter"/> (inherits from <see cref="TextWriter"/>) opened for writing</param>
|
|
/// <param name="readOnlyFileInfo">Result3: file info that should be used to restore file attributes after done with the file (<c>null</c> is this is not needed)</param>
|
|
/// <param name="isLiteralPath">True if wildcard expansion should be bypassed.</param>
|
|
internal static void MasterStreamOpen(
|
|
PSCmdlet cmdlet,
|
|
string filePath,
|
|
Encoding resolvedEncoding,
|
|
bool defaultEncoding,
|
|
bool Append,
|
|
bool Force,
|
|
bool NoClobber,
|
|
out FileStream fileStream,
|
|
out StreamWriter streamWriter,
|
|
out FileInfo readOnlyFileInfo,
|
|
bool isLiteralPath)
|
|
{
|
|
fileStream = null;
|
|
streamWriter = null;
|
|
readOnlyFileInfo = null;
|
|
|
|
// resolve the path and the encoding
|
|
string resolvedPath = ResolveFilePath(filePath, cmdlet, isLiteralPath);
|
|
|
|
try
|
|
{
|
|
// variable to track file open mode
|
|
// this is controlled by append/force parameters
|
|
FileMode mode = FileMode.Create;
|
|
if (Append)
|
|
{
|
|
mode = FileMode.Append;
|
|
}
|
|
else if (NoClobber)
|
|
{
|
|
// throw IOException if file exists
|
|
mode = FileMode.CreateNew;
|
|
}
|
|
|
|
if (Force && (Append || !NoClobber))
|
|
{
|
|
if (File.Exists(resolvedPath))
|
|
{
|
|
FileInfo fInfo = new FileInfo(resolvedPath);
|
|
if ((fInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
|
|
{
|
|
// remember to reset the read-only attribute later
|
|
readOnlyFileInfo = fInfo;
|
|
// Clear the read-only attribute
|
|
fInfo.Attributes &= ~(FileAttributes.ReadOnly);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the user knows what he/she is doing and uses "-Force" switch,
|
|
// then we let more than 1 process write to the same file at the same time
|
|
FileShare fileShare = Force ? FileShare.ReadWrite : FileShare.Read;
|
|
|
|
// mode is controlled by force and ShouldContinue()
|
|
fileStream = new FileStream(resolvedPath, mode, FileAccess.Write, fileShare);
|
|
|
|
// create stream writer
|
|
// NTRAID#Windows Out Of Band Releases-931008-2006/03/27
|
|
// For some reason, calling this without specifying
|
|
// the encoding is different from passing Encoding.Default.
|
|
if (defaultEncoding)
|
|
streamWriter = new StreamWriter(fileStream);
|
|
else
|
|
streamWriter = new StreamWriter(fileStream, resolvedEncoding);
|
|
}
|
|
// These are the known exceptions for File.Load and StreamWriter.ctor
|
|
catch (ArgumentException e)
|
|
{
|
|
// NOTE: this call will throw
|
|
ReportFileOpenFailure(cmdlet, resolvedPath, e);
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
if (NoClobber && File.Exists(resolvedPath))
|
|
{
|
|
// This probably happened because the file already exists
|
|
ErrorRecord errorRecord = new ErrorRecord(
|
|
e, "NoClobber", ErrorCategory.ResourceExists, resolvedPath);
|
|
errorRecord.ErrorDetails = new ErrorDetails(
|
|
cmdlet,
|
|
"PathUtilsStrings",
|
|
"UtilityFileExistsNoClobber",
|
|
filePath,
|
|
"NoClobber"); // prevents localization
|
|
|
|
// NOTE: this call will throw
|
|
cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
// NOTE: this call will throw
|
|
ReportFileOpenFailure(cmdlet, resolvedPath, e);
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
// NOTE: this call will throw
|
|
ReportFileOpenFailure(cmdlet, resolvedPath, e);
|
|
}
|
|
catch (NotSupportedException e)
|
|
{
|
|
// NOTE: this call will throw
|
|
ReportFileOpenFailure(cmdlet, resolvedPath, e);
|
|
}
|
|
catch (System.Security.SecurityException e)
|
|
{
|
|
// NOTE: this call will throw
|
|
ReportFileOpenFailure(cmdlet, resolvedPath, e);
|
|
}
|
|
} // void MasterStreamOpen
|
|
|
|
|
|
|
|
internal static void ReportFileOpenFailure(Cmdlet cmdlet, string filePath, Exception e)
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(
|
|
e,
|
|
"FileOpenFailure",
|
|
ErrorCategory.OpenError,
|
|
null);
|
|
|
|
cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
|
|
internal static StreamReader OpenStreamReader(PSCmdlet command, string filePath, string encoding, bool isLiteralPath)
|
|
{
|
|
FileStream fileStream = OpenFileStream(filePath, command, isLiteralPath);
|
|
if (encoding == null)
|
|
{
|
|
return new StreamReader(fileStream);
|
|
}
|
|
else
|
|
{
|
|
return new StreamReader(fileStream, EncodingConversion.Convert(command, encoding));
|
|
}
|
|
}
|
|
|
|
internal static FileStream OpenFileStream(string filePath, PSCmdlet command, bool isLiteralPath)
|
|
{
|
|
string resolvedPath = PathUtils.ResolveFilePath(filePath, command, isLiteralPath);
|
|
|
|
try
|
|
{
|
|
return new FileStream(resolvedPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
}
|
|
// These are the known exceptions for FileStream.ctor
|
|
catch (ArgumentException e)
|
|
{
|
|
PathUtils.ReportFileOpenFailure(command, filePath, e);
|
|
return null; // the line above will throw - silencing the compiler
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
PathUtils.ReportFileOpenFailure(command, filePath, e);
|
|
return null; // the line above will throw - silencing the compiler
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
PathUtils.ReportFileOpenFailure(command, filePath, e);
|
|
return null; // the line above will throw - silencing the compiler
|
|
}
|
|
catch (NotSupportedException e)
|
|
{
|
|
PathUtils.ReportFileOpenFailure(command, filePath, e);
|
|
return null; // the line above will throw - silencing the compiler
|
|
}
|
|
catch (System.Management.Automation.DriveNotFoundException e)
|
|
{
|
|
PathUtils.ReportFileOpenFailure(command, filePath, e);
|
|
return null; // the line above will throw - silencing the compiler
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// resolve a user provided file name or path (including globbing characters)
|
|
/// to a fully qualified file path, using the file system provider
|
|
/// </summary>
|
|
/// <param name="filePath"></param>
|
|
/// <param name="command"></param>
|
|
/// <returns></returns>
|
|
internal static string ResolveFilePath(string filePath, PSCmdlet command)
|
|
{
|
|
return ResolveFilePath(filePath, command, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// resolve a user provided file name or path (including globbing characters)
|
|
/// to a fully qualified file path, using the file system provider
|
|
/// </summary>
|
|
/// <param name="filePath"></param>
|
|
/// <param name="command"></param>
|
|
/// <param name="isLiteralPath"></param>
|
|
/// <returns></returns>
|
|
internal static string ResolveFilePath(string filePath, PSCmdlet command, bool isLiteralPath)
|
|
{
|
|
string path = null;
|
|
|
|
try
|
|
{
|
|
ProviderInfo provider = null;
|
|
PSDriveInfo drive = null;
|
|
List<string> filePaths = new List<string>();
|
|
|
|
if (isLiteralPath)
|
|
{
|
|
filePaths.Add(command.SessionState.Path.GetUnresolvedProviderPathFromPSPath(filePath, out provider, out drive));
|
|
}
|
|
else
|
|
{
|
|
filePaths.AddRange(command.SessionState.Path.GetResolvedProviderPathFromPSPath(filePath, out provider));
|
|
}
|
|
|
|
if (!provider.NameEquals(command.Context.ProviderNames.FileSystem))
|
|
{
|
|
ReportWrongProviderType(command, provider.FullName);
|
|
}
|
|
|
|
if (filePaths.Count > 1)
|
|
{
|
|
ReportMultipleFilesNotSupported(command);
|
|
}
|
|
if (filePaths.Count == 0)
|
|
{
|
|
ReportWildcardingFailure(command, filePath);
|
|
}
|
|
|
|
path = filePaths[0];
|
|
}
|
|
catch (ItemNotFoundException)
|
|
{
|
|
path = null;
|
|
}
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
CmdletProviderContext cmdletProviderContext = new CmdletProviderContext(command);
|
|
ProviderInfo provider = null;
|
|
PSDriveInfo drive = null;
|
|
path =
|
|
command.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
|
|
filePath, cmdletProviderContext, out provider, out drive);
|
|
cmdletProviderContext.ThrowFirstErrorOrDoNothing();
|
|
if (!provider.NameEquals(command.Context.ProviderNames.FileSystem))
|
|
{
|
|
ReportWrongProviderType(command, provider.FullName);
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
internal static void ReportWrongProviderType(Cmdlet cmdlet, string providerId)
|
|
{
|
|
string msg = StringUtil.Format(PathUtilsStrings.OutFile_ReadWriteFileNotFileSystemProvider, providerId);
|
|
|
|
ErrorRecord errorRecord = new ErrorRecord (
|
|
PSTraceSource.NewInvalidOperationException(),
|
|
"ReadWriteFileNotFileSystemProvider",
|
|
ErrorCategory.InvalidArgument,
|
|
null);
|
|
|
|
errorRecord.ErrorDetails = new ErrorDetails (msg);
|
|
cmdlet.ThrowTerminatingError (errorRecord);
|
|
}
|
|
|
|
internal static void ReportMultipleFilesNotSupported(Cmdlet cmdlet)
|
|
{
|
|
string msg = StringUtil.Format(PathUtilsStrings.OutFile_MultipleFilesNotSupported);
|
|
|
|
ErrorRecord errorRecord = new ErrorRecord (
|
|
PSTraceSource.NewInvalidOperationException(),
|
|
"ReadWriteMultipleFilesNotSupported",
|
|
ErrorCategory.InvalidArgument,
|
|
null);
|
|
|
|
errorRecord.ErrorDetails = new ErrorDetails (msg);
|
|
cmdlet.ThrowTerminatingError (errorRecord);
|
|
}
|
|
|
|
internal static void ReportWildcardingFailure(Cmdlet cmdlet, string filePath)
|
|
{
|
|
string msg = StringUtil.Format(PathUtilsStrings.OutFile_DidNotResolveFile, filePath);
|
|
|
|
ErrorRecord errorRecord = new ErrorRecord (
|
|
new FileNotFoundException(),
|
|
"FileOpenFailure",
|
|
ErrorCategory.OpenError,
|
|
filePath);
|
|
|
|
errorRecord.ErrorDetails = new ErrorDetails (msg);
|
|
cmdlet.ThrowTerminatingError (errorRecord);
|
|
}
|
|
|
|
internal static DirectoryInfo CreateModuleDirectory(PSCmdlet cmdlet, string moduleNameOrPath, bool force)
|
|
{
|
|
Dbg.Assert(cmdlet != null, "Caller should verify cmdlet != null");
|
|
Dbg.Assert(!string.IsNullOrEmpty(moduleNameOrPath), "Caller should verify !string.IsNullOrEmpty(moduleNameOrPath)");
|
|
|
|
DirectoryInfo directoryInfo = null;
|
|
try
|
|
{
|
|
string rootedPath = Microsoft.PowerShell.Commands.ModuleCmdletBase.ResolveRootedFilePath(moduleNameOrPath, cmdlet.Context);
|
|
if (string.IsNullOrEmpty(rootedPath) && moduleNameOrPath.StartsWith(".", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
PathInfo currentPath = cmdlet.CurrentProviderLocation(cmdlet.Context.ProviderNames.FileSystem);
|
|
rootedPath = Path.Combine(currentPath.ProviderPath, moduleNameOrPath);
|
|
}
|
|
if (string.IsNullOrEmpty(rootedPath))
|
|
{
|
|
string personalModuleRoot = ModuleIntrinsics.GetPersonalModulePath();
|
|
rootedPath = Path.Combine(personalModuleRoot, moduleNameOrPath);
|
|
}
|
|
|
|
directoryInfo = new DirectoryInfo(rootedPath);
|
|
if (directoryInfo.Exists)
|
|
{
|
|
if (!force)
|
|
{
|
|
string errorMessage = string.Format(
|
|
CultureInfo.InvariantCulture, // directory name should be treated as culture-invariant
|
|
PathUtilsStrings.ExportPSSession_ErrorDirectoryExists,
|
|
directoryInfo.FullName);
|
|
ErrorDetails details = new ErrorDetails(errorMessage);
|
|
ErrorRecord errorRecord = new ErrorRecord(
|
|
new ArgumentException(details.Message),
|
|
"ExportProxyCommand_OutputDirectoryExists",
|
|
ErrorCategory.ResourceExists,
|
|
directoryInfo);
|
|
cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
directoryInfo.Create();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
CommandProcessorBase.CheckForSevereException(e);
|
|
|
|
string errorMessage = string.Format(
|
|
CultureInfo.InvariantCulture, // directory name should be treated as culture-invariant
|
|
PathUtilsStrings.ExportPSSession_CannotCreateOutputDirectory,
|
|
moduleNameOrPath,
|
|
e.Message);
|
|
ErrorDetails details = new ErrorDetails(errorMessage);
|
|
ErrorRecord errorRecord = new ErrorRecord(
|
|
new ArgumentException(details.Message, e),
|
|
"ExportProxyCommand_CannotCreateOutputDirectory",
|
|
ErrorCategory.ResourceExists,
|
|
moduleNameOrPath);
|
|
cmdlet.ThrowTerminatingError(errorRecord);
|
|
}
|
|
|
|
return directoryInfo;
|
|
}
|
|
|
|
internal static DirectoryInfo CreateTemporaryDirectory()
|
|
{
|
|
DirectoryInfo temporaryDirectory = new DirectoryInfo(Path.GetTempPath());
|
|
DirectoryInfo moduleDirectory;
|
|
do
|
|
{
|
|
moduleDirectory = new DirectoryInfo(
|
|
Path.Combine(
|
|
temporaryDirectory.FullName,
|
|
string.Format(
|
|
null,
|
|
"tmp_{0}",
|
|
Path.GetRandomFileName())));
|
|
} while (moduleDirectory.Exists);
|
|
|
|
Directory.CreateDirectory(moduleDirectory.FullName);
|
|
return new DirectoryInfo(moduleDirectory.FullName);
|
|
}
|
|
|
|
}
|
|
|
|
internal static class EncodingConversion
|
|
{
|
|
internal const string Unknown = "unknown";
|
|
internal const string String = "string";
|
|
internal const string Unicode = "unicode";
|
|
internal const string BigEndianUnicode = "bigendianunicode";
|
|
internal const string Ascii = "ascii";
|
|
internal const string Utf8 = "utf8";
|
|
internal const string Utf7 = "utf7";
|
|
internal const string Utf32 = "utf32";
|
|
internal const string Default = "default";
|
|
internal const string OEM = "oem";
|
|
|
|
/// <summary>
|
|
/// retrieve the encoding paramater from the command line
|
|
/// it throws if the encoding does not match the known ones
|
|
/// </summary>
|
|
/// <returns>a System.Text.Encoding object (null if no encoding specified)</returns>
|
|
internal static Encoding Convert (Cmdlet cmdlet, string encoding)
|
|
{
|
|
if (string.IsNullOrEmpty(encoding))
|
|
{
|
|
// no parameter passed, default to Unicode (OS preferred)
|
|
return System.Text.Encoding.Unicode;
|
|
}
|
|
|
|
// Default to unicode (this matches Get-Content)
|
|
if (string.Equals(encoding, Unknown, StringComparison.OrdinalIgnoreCase))
|
|
return System.Text.Encoding.Unicode;
|
|
|
|
if (string.Equals(encoding, String, StringComparison.OrdinalIgnoreCase))
|
|
return System.Text.Encoding.Unicode;
|
|
|
|
// these are the encodings the CLR supports
|
|
if (string.Equals (encoding, Unicode, StringComparison.OrdinalIgnoreCase))
|
|
return System.Text.Encoding.Unicode;
|
|
|
|
if (string.Equals (encoding, BigEndianUnicode, StringComparison.OrdinalIgnoreCase))
|
|
return System.Text.Encoding.BigEndianUnicode;
|
|
|
|
if (string.Equals (encoding, Utf8, StringComparison.OrdinalIgnoreCase))
|
|
return System.Text.Encoding.UTF8;
|
|
|
|
if (string.Equals(encoding, Ascii, StringComparison.OrdinalIgnoreCase))
|
|
return System.Text.Encoding.ASCII;
|
|
|
|
if (string.Equals (encoding, Utf7, StringComparison.OrdinalIgnoreCase))
|
|
return System.Text.Encoding.UTF7;
|
|
|
|
if (string.Equals (encoding, Utf32, StringComparison.OrdinalIgnoreCase))
|
|
return System.Text.Encoding.UTF32;
|
|
|
|
if (string.Equals(encoding, Default, StringComparison.OrdinalIgnoreCase))
|
|
return ClrFacade.GetDefaultEncoding();
|
|
|
|
if (string.Equals(encoding, OEM, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return ClrFacade.GetOEMEncoding();
|
|
}
|
|
|
|
// error condition: unknown encoding value
|
|
string validEncodingValues = string.Join(
|
|
", ",
|
|
new string[] { Unknown, String, Unicode, BigEndianUnicode, Ascii, Utf8, Utf7, Utf32, Default, OEM });
|
|
string msg = StringUtil.Format(PathUtilsStrings.OutFile_WriteToFileEncodingUnknown,
|
|
encoding, validEncodingValues);
|
|
|
|
ErrorRecord errorRecord = new ErrorRecord (
|
|
PSTraceSource.NewArgumentException("Encoding"),
|
|
"WriteToFileEncodingUnknown",
|
|
ErrorCategory.InvalidArgument,
|
|
null);
|
|
|
|
errorRecord.ErrorDetails = new ErrorDetails (msg);
|
|
cmdlet.ThrowTerminatingError(errorRecord);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|