/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Management.Automation.Internal;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation
{
///
/// Defines generic utilities and helper methods for PowerShell
///
internal static class PathUtils
{
///
/// THE method for opening a file for writing.
/// Should be used by all cmdlets that write to a file.
///
/// cmdlet that is opening the file (used mainly for error reporting)
/// path to the file (as specified on the command line - this method will resolve the path)
/// encoding (this method will convert the command line strin to an Encoding instance)
/// if true, then we will use default .NET encoding instead of the encoding specified in parameter
///
///
///
/// Result1: opened for writing
/// Result2: (inherits from ) opened for writing
/// Result3: file info that should be used to restore file attributes after done with the file (null is this is not needed)
/// True if wildcard expansion should be bypassed.
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);
}
///
/// THE method for opening a file for writing.
/// Should be used by all cmdlets that write to a file.
///
/// cmdlet that is opening the file (used mainly for error reporting)
/// path to the file (as specified on the command line - this method will resolve the path)
/// encoding (this method will convert the command line strin to an Encoding instance)
/// if true, then we will use default .NET encoding instead of the encoding specified in parameter
///
///
///
/// Result1: opened for writing
/// Result2: (inherits from ) opened for writing
/// Result3: file info that should be used to restore file attributes after done with the file (null is this is not needed)
/// True if wildcard expansion should be bypassed.
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
}
}
///
/// resolve a user provided file name or path (including globbing characters)
/// to a fully qualified file path, using the file system provider
///
///
///
///
internal static string ResolveFilePath(string filePath, PSCmdlet command)
{
return ResolveFilePath(filePath, command, false);
}
///
/// resolve a user provided file name or path (including globbing characters)
/// to a fully qualified file path, using the file system provider
///
///
///
///
///
internal static string ResolveFilePath(string filePath, PSCmdlet command, bool isLiteralPath)
{
string path = null;
try
{
ProviderInfo provider = null;
PSDriveInfo drive = null;
List filePaths = new List();
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";
///
/// retrieve the encoding paramater from the command line
/// it throws if the encoding does not match the known ones
///
/// a System.Text.Encoding object (null if no encoding specified)
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;
}
}
}