Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
9834 lines (8611 sloc) 388 KB
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Management.Automation.Provider;
using System.Management.Automation.Runspaces;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.AccessControl;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using Microsoft.Win32.SafeHandles;
using Dbg = System.Management.Automation;
namespace Microsoft.PowerShell.Commands
{
#region FileSystemProvider
/// <summary>
/// Defines the implementation of a File System Provider. This provider
/// allows for stateless namespace navigation of the file system.
/// </summary>
[CmdletProvider(FileSystemProvider.ProviderName, ProviderCapabilities.Credentials | ProviderCapabilities.Filter | ProviderCapabilities.ShouldProcess)]
[OutputType(typeof(FileSecurity), ProviderCmdlet = ProviderCmdlet.SetAcl)]
[OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)]
[OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)]
[OutputType(typeof(byte), typeof(string), ProviderCmdlet = ProviderCmdlet.GetContent)]
[OutputType(typeof(FileInfo), ProviderCmdlet = ProviderCmdlet.GetItem)]
[OutputType(typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetChildItem)]
[OutputType(typeof(FileSecurity), typeof(DirectorySecurity), ProviderCmdlet = ProviderCmdlet.GetAcl)]
[OutputType(typeof(bool), typeof(string), typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItem)]
[OutputType(typeof(bool), typeof(string), typeof(DateTime), typeof(System.IO.FileInfo), typeof(System.IO.DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItemProperty)]
[OutputType(typeof(string), typeof(System.IO.FileInfo), ProviderCmdlet = ProviderCmdlet.NewItem)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This coupling is required")]
public sealed partial class FileSystemProvider : NavigationCmdletProvider,
IContentCmdletProvider,
IPropertyCmdletProvider,
ISecurityDescriptorCmdletProvider,
ICmdletProviderSupportsHelp
{
// 4MB gives the best results without spiking the resources on the remote connection for file transfers between pssessions.
// NOTE: The script used to copy file data from session (PSCopyFromSessionHelper) has a
// maximum fragment size value for security. If FILETRANSFERSIZE changes make sure the
// copy script will accomodate the new value.
private const int FILETRANSFERSIZE = 4 * 1024 * 1024;
// The name of the key in an exception's Data dictionary when attempting
// to copy an item onto itself.
private const string SelfCopyDataKey = "SelfCopy";
/// <summary>
/// An instance of the PSTraceSource class used for trace output
/// using "FileSystemProvider" as the category.
/// </summary>
[Dbg.TraceSourceAttribute("FileSystemProvider", "The namespace navigation provider for the file system")]
private static Dbg.PSTraceSource s_tracer =
Dbg.PSTraceSource.GetTracer("FileSystemProvider", "The namespace navigation provider for the file system");
/// <summary>
/// Gets the name of the provider.
/// </summary>
public const string ProviderName = "FileSystem";
/// <summary>
/// Initializes a new instance of the FileSystemProvider class. Since this
/// object needs to be stateless, the constructor does nothing.
/// </summary>
public FileSystemProvider()
{
}
private Collection<WildcardPattern> _excludeMatcher = null;
private static System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions
{
MatchType = MatchType.Win32,
MatchCasing = MatchCasing.CaseInsensitive,
AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior
};
/// <summary>
/// Converts all / in the path to \
/// </summary>
/// <param name="path">
/// The path to normalize.
/// </param>
/// <returns>
/// The path with all / normalized to \
/// </returns>
private static string NormalizePath(string path)
{
return GetCorrectCasedPath(path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator));
}
/// <summary>
/// Get the correct casing for a path. This method assumes it's being called by NormalizePath()
/// so that the path is already normalized.
/// </summary>
/// <param name="path">
/// The path to retrieve.
/// </param>
/// <returns>
/// The path with accurate casing if item exists, otherwise it returns path that was passed in.
/// </returns>
private static string GetCorrectCasedPath(string path)
{
// Only apply to directories where there are issues with some tools if the casing
// doesn't match the source like git
if (Directory.Exists(path))
{
string exactPath = string.Empty;
int itemsToSkip = 0;
if (Utils.PathIsUnc(path))
{
// With the Split method, a UNC path like \\server\share, we need to skip
// trying to enumerate the server and share, so skip the first two empty
// strings, then server, and finally share name.
itemsToSkip = 4;
}
foreach (string item in path.Split(StringLiterals.DefaultPathSeparator))
{
if (itemsToSkip-- > 0)
{
// This handles the UNC server and share and 8.3 short path syntax
exactPath += item + StringLiterals.DefaultPathSeparator;
continue;
}
else if (string.IsNullOrEmpty(exactPath))
{
// This handles the drive letter or / root path start
exactPath = item + StringLiterals.DefaultPathSeparator;
}
else if (string.IsNullOrEmpty(item))
{
// This handles the trailing slash case
if (!exactPath.EndsWith(StringLiterals.DefaultPathSeparator))
{
exactPath += StringLiterals.DefaultPathSeparator;
}
break;
}
else if (item.Contains('~'))
{
// This handles short path names
exactPath += StringLiterals.DefaultPathSeparator + item;
}
else
{
// Use GetFileSystemEntries to get the correct casing of this element
try
{
var entries = Directory.GetFileSystemEntries(exactPath, item);
if (entries.Length > 0)
{
exactPath = entries.First();
}
else
{
// If previous call didn't return anything, something failed so we just return the path we were given
return path;
}
}
catch
{
// If we can't enumerate, we stop and just return the original path
return path;
}
}
}
return exactPath;
}
else
{
return path;
}
}
/// <summary>
/// Checks if the item exist at the specified path. if it exists then creates
/// appropriate directoryinfo or fileinfo object.
/// </summary>
/// <param name="path">
/// Refers to the item for which we are checking for existence and creating filesysteminfo object.
/// </param>
/// <param name="isContainer">
/// Return true if path points to a directory else returns false.
/// </param>
/// <returns>FileInfo or DirectoryInfo object.</returns>
/// <exception cref="System.ArgumentNullException">
/// The path is null.
/// </exception>
/// <exception cref="System.IO.IOException">
/// I/O error occurs.
/// </exception>
/// <exception cref="System.UnauthorizedAccessException">
/// An I/O error or a specific type of security error.
/// </exception>
private static FileSystemInfo GetFileSystemInfo(string path, out bool isContainer)
{
// We use 'FileInfo.Attributes' (not 'FileInfo.Exist')
// because we want to get exceptions
// like UnauthorizedAccessException or IOException.
FileSystemInfo fsinfo = new FileInfo(path);
var attr = fsinfo.Attributes;
var exists = (int)attr != -1;
isContainer = exists && attr.HasFlag(FileAttributes.Directory);
if (exists)
{
if (isContainer)
{
return new DirectoryInfo(path);
}
else
{
return fsinfo;
}
}
return null;
}
/// <summary>
/// Overrides the method of CmdletProvider, considering the additional
/// dynamic parameters of FileSystemProvider.
/// </summary>
/// <returns>
/// whether the filter or attribute filter is set.
/// </returns>
internal override bool IsFilterSet()
{
bool attributeFilterSet = false;
GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
if (fspDynamicParam != null)
{
attributeFilterSet = (
(fspDynamicParam.Attributes != null)
|| (fspDynamicParam.Directory)
|| (fspDynamicParam.File)
|| (fspDynamicParam.Hidden)
|| (fspDynamicParam.ReadOnly)
|| (fspDynamicParam.System));
}
return (attributeFilterSet || base.IsFilterSet());
}
/// <summary>
/// Gets the dynamic parameters for get-childnames on the
/// FileSystemProvider.
/// We currently only support one dynamic parameter,
/// "Attributes" that returns an enum evaluator for the
/// given expression.
/// </summary>
/// <param name="path">
/// If the path was specified on the command line, this is the path
/// to the item for which to get the dynamic parameters.
/// </param>
/// <returns>
/// An object that has properties and fields decorated with
/// parsing attributes similar to a cmdlet class.
/// </returns>
protected override object GetChildNamesDynamicParameters(string path)
{
return new GetChildDynamicParameters();
}
/// <summary>
/// Gets the dynamic parameters for get-childitems on the
/// FileSystemProvider.
/// We currently only support one dynamic parameter,
/// "Attributes" that returns an enum evaluator for the
/// given expression.
/// </summary>
/// <param name="path">
/// If the path was specified on the command line, this is the path
/// to the item for which to get the dynamic parameters.
/// </param>
/// <param name="recurse">
/// Ignored.
/// </param>
/// <returns>
/// An object that has properties and fields decorated with
/// parsing attributes similar to a cmdlet class.
/// </returns>
protected override object GetChildItemsDynamicParameters(string path, bool recurse)
{
return new GetChildDynamicParameters();
}
/// <summary>
/// Gets the dynamic parameters for Copy-Item on the FileSystemProvider.
/// </summary>
/// <param name="path">Source for the copy operation.</param>
/// <param name="destination">Destination for the copy operation.</param>
/// <param name="recurse">Whether to recurse.</param>
/// <returns></returns>
protected override object CopyItemDynamicParameters(string path, string destination, bool recurse)
{
return new CopyItemDynamicParameters();
}
#region ICmdletProviderSupportsHelp members
/// <summary>
/// Implementation of ICmdletProviderSupportsHelp interface.
/// Gets provider-specific help content for the corresponding cmdlet.
/// </summary>
/// <param name="helpItemName">
/// Name of command that the help is requested for.
/// </param>
/// <param name="path">
/// Not used here.
/// </param>
/// <returns>
/// The MAML help XML that should be presented to the user.
/// </returns>
public string GetHelpMaml(string helpItemName, string path)
{
// Get the verb and noun from helpItemName
//
string verb = null;
string noun = null;
XmlReader reader = null;
try
{
if (!string.IsNullOrEmpty(helpItemName))
{
CmdletInfo.SplitCmdletName(helpItemName, out verb, out noun);
}
else
{
return string.Empty;
}
if (string.IsNullOrEmpty(verb) || string.IsNullOrEmpty(noun))
{
return string.Empty;
}
// Load the help file from the current UI culture subfolder
XmlDocument document = new XmlDocument();
CultureInfo currentUICulture = CultureInfo.CurrentUICulture;
string fullHelpPath = Path.Combine(
string.IsNullOrEmpty(this.ProviderInfo.ApplicationBase) ? string.Empty : this.ProviderInfo.ApplicationBase,
currentUICulture.ToString(),
string.IsNullOrEmpty(this.ProviderInfo.HelpFile) ? string.Empty : this.ProviderInfo.HelpFile);
XmlReaderSettings settings = new XmlReaderSettings();
settings.XmlResolver = null;
reader = XmlReader.Create(fullHelpPath, settings);
document.Load(reader);
// Add "msh" and "command" namespaces from the MAML schema
XmlNamespaceManager nsMgr = new XmlNamespaceManager(document.NameTable);
nsMgr.AddNamespace("msh", HelpCommentsParser.mshURI);
nsMgr.AddNamespace("command", HelpCommentsParser.commandURI);
// Compose XPath query to select the appropriate node based on the cmdlet
string xpathQuery = string.Format(
CultureInfo.InvariantCulture,
HelpCommentsParser.ProviderHelpCommandXPath,
"[@id='FileSystem']",
verb,
noun);
// Execute the XPath query and return its MAML snippet
XmlNode result = document.SelectSingleNode(xpathQuery, nsMgr);
if (result != null)
{
return result.OuterXml;
}
}
catch (XmlException)
{
return string.Empty;
}
catch (PathTooLongException)
{
return string.Empty;
}
catch (IOException)
{
return string.Empty;
}
catch (UnauthorizedAccessException)
{
return string.Empty;
}
catch (NotSupportedException)
{
return string.Empty;
}
catch (SecurityException)
{
return string.Empty;
}
catch (XPathException)
{
return string.Empty;
}
finally
{
if (reader != null)
{
((IDisposable)reader).Dispose();
}
}
return string.Empty;
}
#endregion
#region CmdletProvider members
/// <summary>
/// Starts the File System provider. This method sets the Home for the
/// provider to providerInfo.Home if specified, and %USERPROFILE%
/// otherwise.
/// </summary>
/// <param name="providerInfo">
/// The ProviderInfo object that holds the provider's configuration.
/// </param>
/// <returns>
/// The updated ProviderInfo object that holds the provider's configuration.
/// </returns>
protected override ProviderInfo Start(ProviderInfo providerInfo)
{
// Set the home folder for the user
if (providerInfo != null && string.IsNullOrEmpty(providerInfo.Home))
{
// %USERPROFILE% - indicate where a user's home directory is located in the file system.
string homeDirectory = Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home);
if (!string.IsNullOrEmpty(homeDirectory))
{
if (Directory.Exists(homeDirectory))
{
s_tracer.WriteLine("Home = {0}", homeDirectory);
providerInfo.Home = homeDirectory;
}
else
{
s_tracer.WriteLine("Not setting home directory {0} - does not exist", homeDirectory);
}
}
}
// OneDrive placeholder support (issue #8315)
// make it so OneDrive placeholders are perceived as such with *all* their attributes accessible
#if !UNIX
// The placeholder mode management APIs Rtl(Set|Query)(Process|Thread)PlaceholderCompatibilityMode
// are only supported starting with Windows 10 version 1803 (build 17134)
Version minBuildForPlaceHolderAPIs = new Version(10, 0, 17134, 0);
if (Environment.OSVersion.Version >= minBuildForPlaceHolderAPIs)
{
// let's be safe, don't change the PlaceHolderCompatibilityMode if the current one is not what we expect
if (NativeMethods.PHCM_DISGUISE_PLACEHOLDER == NativeMethods.RtlQueryProcessPlaceholderCompatibilityMode())
{
NativeMethods.RtlSetProcessPlaceholderCompatibilityMode(NativeMethods.PHCM_EXPOSE_PLACEHOLDERS);
}
}
#endif
return providerInfo;
}
#endregion CmdletProvider members
#region DriveCmdletProvider members
/// <summary>
/// Determines if the specified drive can be mounted.
/// </summary>
/// <param name="drive">
/// The drive that is going to be mounted.
/// </param>
/// <returns>
/// The same drive that was passed in, if the drive can be mounted.
/// null if the drive cannot be mounted.
/// </returns>
/// <exception cref="System.ArgumentNullException">
/// drive is null.
/// </exception>
/// <exception cref="System.ArgumentException">
/// drive root is null or empty.
/// </exception>
protected override PSDriveInfo NewDrive(PSDriveInfo drive)
{
// verify parameters
if (drive == null)
{
throw PSTraceSource.NewArgumentNullException("drive");
}
if (string.IsNullOrEmpty(drive.Root))
{
throw PSTraceSource.NewArgumentException("drive.Root");
}
// -Persist switch parameter is supported only for Network paths.
if (drive.Persist && !PathIsNetworkPath(drive.Root))
{
ErrorRecord er = new ErrorRecord(new NotSupportedException(FileSystemProviderStrings.PersistNotSupported), "DriveRootNotNetworkPath", ErrorCategory.InvalidArgument, drive);
ThrowTerminatingError(er);
}
if (IsNetworkMappedDrive(drive))
{
// MapNetworkDrive facilitates to map the newly
// created PS Drive to a network share.
this.MapNetworkDrive(drive);
}
// The drive is valid if the item exists or the
// drive is not a fixed drive. We want to allow
// a drive to exist for floppies and other such\
// removable media, even if the media isn't in place.
bool driveIsFixed = true;
PSDriveInfo result = null;
try
{
// See if the drive is a fixed drive.
string pathRoot = Path.GetPathRoot(drive.Root);
DriveInfo driveInfo = new DriveInfo(pathRoot);
if (driveInfo.DriveType != DriveType.Fixed)
{
driveIsFixed = false;
}
// The current drive is a network drive.
if (driveInfo.DriveType == DriveType.Network)
{
drive.IsNetworkDrive = true;
}
}
catch (ArgumentException) // swallow ArgumentException incl. ArgumentNullException
{
}
bool validDrive = true;
if (driveIsFixed)
{
// Since the drive is fixed, ensure the root is valid.
validDrive = Directory.Exists(drive.Root);
}
if (validDrive)
{
result = drive;
}
else
{
string error = StringUtil.Format(FileSystemProviderStrings.DriveRootError, drive.Root);
Exception e = new IOException(error);
WriteError(new ErrorRecord(e, "DriveRootError", ErrorCategory.ReadError, drive));
}
drive.Trace();
return result;
}
/// <summary>
/// MapNetworkDrive facilitates to map the newly created PS Drive to a network share.
/// </summary>
/// <param name="drive">The PSDrive info that would be used to create a new PS drive.</param>
private void MapNetworkDrive(PSDriveInfo drive)
{
// Porting note: mapped network drives are only supported on Windows
if (Platform.IsWindows)
{
WinMapNetworkDrive(drive);
}
else
{
throw new PlatformNotSupportedException();
}
}
private void WinMapNetworkDrive(PSDriveInfo drive)
{
if (drive != null && !string.IsNullOrEmpty(drive.Root))
{
const int CONNECT_UPDATE_PROFILE = 0x00000001;
const int CONNECT_NOPERSIST = 0x00000000;
const int RESOURCE_GLOBALNET = 0x00000002;
const int RESOURCETYPE_ANY = 0x00000000;
const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
// By default the connection is not persisted.
int CONNECT_TYPE = CONNECT_NOPERSIST;
string driveName = null;
byte[] passwd = null;
string userName = null;
if (drive.Persist)
{
if (IsSupportedDriveForPersistence(drive))
{
CONNECT_TYPE = CONNECT_UPDATE_PROFILE;
driveName = drive.Name + ":";
drive.DisplayRoot = drive.Root;
}
else
{
// error.
ErrorRecord er = new ErrorRecord(new InvalidOperationException(FileSystemProviderStrings.InvalidDriveName), "DriveNameNotSupportedForPersistence", ErrorCategory.InvalidOperation, drive);
ThrowTerminatingError(er);
}
}
// If alternate credentials is supplied then use them to get connected to network share.
if (drive.Credential != null && !drive.Credential.Equals(PSCredential.Empty))
{
userName = drive.Credential.UserName;
passwd = SecureStringHelper.GetData(drive.Credential.Password);
}
try
{
NetResource resource = new NetResource();
resource.Comment = null;
resource.DisplayType = RESOURCEDISPLAYTYPE_GENERIC;
resource.LocalName = driveName;
resource.Provider = null;
resource.RemoteName = drive.Root;
resource.Scope = RESOURCE_GLOBALNET;
resource.Type = RESOURCETYPE_ANY;
resource.Usage = RESOURCEUSAGE_CONNECTABLE;
int code = NativeMethods.WNetAddConnection2(ref resource, passwd, userName, CONNECT_TYPE);
if (code != 0)
{
ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(code), "CouldNotMapNetworkDrive", ErrorCategory.InvalidOperation, drive);
ThrowTerminatingError(er);
}
if (CONNECT_TYPE == CONNECT_UPDATE_PROFILE)
{
// Update the current PSDrive to be a persisted drive.
drive.IsNetworkDrive = true;
// PsDrive.Root is updated to the name of the Drive for
// drives targeting network path and being persisted.
drive.Root = driveName + @"\";
}
}
finally
{
// Clear the password in the memory.
if (passwd != null)
{
Array.Clear(passwd, 0, passwd.Length - 1);
}
}
}
}
/// <summary>
/// ShouldMapNetworkDrive is a helper function used to detect if the
/// requested PSDrive to be created has to be mapped to a network drive.
/// </summary>
/// <param name="drive"></param>
/// <returns></returns>
private bool IsNetworkMappedDrive(PSDriveInfo drive)
{
bool shouldMapNetworkDrive = (drive != null && !string.IsNullOrEmpty(drive.Root) && PathIsNetworkPath(drive.Root)) &&
(drive.Persist || (drive.Credential != null && !drive.Credential.Equals(PSCredential.Empty)));
return shouldMapNetworkDrive;
}
/// <summary>
/// RemoveDrive facilitates to remove network mapped persisted PSDrvie.
/// </summary>
/// <param name="drive">
/// PSDrive info.
/// </param>
/// <returns>PSDrive info.
/// </returns>
protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
{
#if UNIX
return drive;
#else
return WinRemoveDrive(drive);
#endif
}
private PSDriveInfo WinRemoveDrive(PSDriveInfo drive)
{
if (IsNetworkMappedDrive(drive))
{
const int CONNECT_UPDATE_PROFILE = 0x00000001;
int flags = 0;
string driveName;
if (drive.IsNetworkDrive)
{
// Here we are removing only persisted network drives.
flags = CONNECT_UPDATE_PROFILE;
driveName = drive.Name + ":";
}
else
{
// OSGTFS: 608188 PSDrive leaves a connection open after the drive is removed
// if a drive is not persisted or networkdrive, we need to use the actual root to remove the drive.
driveName = drive.Root;
}
// You need to actually remove the drive.
int code = NativeMethods.WNetCancelConnection2(driveName, flags, true);
if (code != 0)
{
ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(code), "CouldRemoveNetworkDrive", ErrorCategory.InvalidOperation, drive);
ThrowTerminatingError(er);
}
}
return drive;
}
/// <summary>
/// IsSupportedDriveForPersistence is a helper method used to
/// check if the psdrive can be persisted or not.
/// </summary>
/// <param name="drive">
/// PS Drive Info.
/// </param>
/// <returns>True if the drive can be persisted or else false.</returns>
private bool IsSupportedDriveForPersistence(PSDriveInfo drive)
{
bool isSupportedDriveForPersistence = false;
if (drive != null && !string.IsNullOrEmpty(drive.Name) && drive.Name.Length == 1)
{
char driveChar = Convert.ToChar(drive.Name, CultureInfo.InvariantCulture);
if (char.ToUpperInvariant(driveChar) >= 'A' && char.ToUpperInvariant(driveChar) <= 'Z')
{
isSupportedDriveForPersistence = true;
}
}
return isSupportedDriveForPersistence;
}
/// <summary>
/// Return the UNC path for a given network drive
/// using the Windows API.
/// </summary>
/// <param name="driveName"></param>
/// <returns></returns>
internal static string GetUNCForNetworkDrive(string driveName)
{
#if UNIX
return driveName;
#else
return WinGetUNCForNetworkDrive(driveName);
#endif
}
private static string WinGetUNCForNetworkDrive(string driveName)
{
string uncPath = null;
if (!string.IsNullOrEmpty(driveName) && driveName.Length == 1)
{
// By default buffer size is set to 300 which would generally be sufficient in most of the cases.
int bufferSize = 300;
#if DEBUG
// In Debug mode buffer size is initially set to 3 and if additional buffer is required, the
// required buffer size is allocated and the WNetGetConnection API is executed with the newly
// allocated buffer size.
bufferSize = 3;
#endif
StringBuilder uncBuffer = new StringBuilder(bufferSize);
driveName += ':';
// Call the windows API
int errorCode = NativeMethods.WNetGetConnection(driveName, uncBuffer, ref bufferSize);
// error code 234 is returned whenever the required buffer size is greater
// than the specified buffer size.
if (errorCode == 234)
{
uncBuffer = new StringBuilder(bufferSize);
errorCode = NativeMethods.WNetGetConnection(driveName, uncBuffer, ref bufferSize);
}
if (errorCode != 0)
{
throw new System.ComponentModel.Win32Exception(errorCode);
}
uncPath = uncBuffer.ToString();
}
return uncPath;
}
/// <summary>
/// Get the substituted path of a NetWork type MS-DOS device that is created by 'subst' command.
/// When a MS-DOS device is of NetWork type, it could be:
/// 1. Substitute a path in a drive that maps to a network location. For example:
/// net use z: \\scratch2\scratch\
/// subst y: z:\abc\
/// 2. Substitute a network location directly. For example:
/// subst y: \\scratch2\scratch\
/// </summary>
/// <param name="driveName"></param>
/// <returns></returns>
internal static string GetSubstitutedPathForNetworkDosDevice(string driveName)
{
#if UNIX
throw new PlatformNotSupportedException();
#else
return WinGetSubstitutedPathForNetworkDosDevice(driveName);
#endif
}
private static string WinGetSubstitutedPathForNetworkDosDevice(string driveName)
{
string associatedPath = null;
if (!string.IsNullOrEmpty(driveName) && driveName.Length == 1)
{
// By default buffer size is set to 300 which would generally be sufficient in most of the cases.
int bufferSize = 300;
var pathInfo = new StringBuilder(bufferSize);
driveName += ':';
// Call the windows API
while (true)
{
pathInfo.EnsureCapacity(bufferSize);
int retValue = NativeMethods.QueryDosDevice(driveName, pathInfo, bufferSize);
if (retValue > 0)
{
// If the drive letter is a substed path, the result will be in the format of
// - "\??\C:\RealPath" for local path
// - "\??\UNC\RealPath" for network path
associatedPath = pathInfo.ToString();
if (associatedPath.StartsWith("\\??\\", StringComparison.OrdinalIgnoreCase))
{
associatedPath = associatedPath.Remove(0, 4);
if (associatedPath.StartsWith("UNC", StringComparison.OrdinalIgnoreCase))
{
associatedPath = associatedPath.Remove(0, 3);
associatedPath = "\\" + associatedPath;
}
else if (associatedPath.EndsWith(':'))
{
// The substed path is the root path of a drive. For example: subst Y: C:\
associatedPath += Path.DirectorySeparatorChar;
}
}
else
{
// The drive name is not a substed path, then we return the root path of the drive
associatedPath = driveName + "\\";
}
break;
}
// Windows API call failed
int errorCode = Marshal.GetLastWin32Error();
if (errorCode != 122)
{
// ERROR_INSUFFICIENT_BUFFER = 122
// For an error other than "insufficient buffer", throw it
throw new Win32Exception((int)errorCode);
}
// We got the "insufficient buffer" error. In this case we extend
// the buffer size, unless it's unreasonably too large.
if (bufferSize >= 32767)
{
// "The Windows API has many functions that also have Unicode versions to permit
// an extended-length path for a maximum total path length of 32,767 characters"
// See https://msdn.microsoft.com/library/aa365247.aspx#maxpath
string errorMsg = StringUtil.Format(FileSystemProviderStrings.SubstitutePathTooLong, driveName);
throw new InvalidOperationException(errorMsg);
}
// Extend the buffer size and try again.
bufferSize *= 10;
if (bufferSize > 32767)
{
bufferSize = 32767;
}
}
}
return associatedPath;
}
/// <summary>
/// Get the root path for a network drive or MS-DOS device.
/// </summary>
/// <param name="driveInfo"></param>
/// <returns></returns>
internal static string GetRootPathForNetworkDriveOrDosDevice(DriveInfo driveInfo)
{
Dbg.Diagnostics.Assert(driveInfo.DriveType == DriveType.Network, "Caller should make sure it is a network drive.");
string driveName = driveInfo.Name.Substring(0, 1);
string rootPath = null;
try
{
rootPath = GetUNCForNetworkDrive(driveName);
}
catch (Win32Exception)
{
if (driveInfo.IsReady)
{
// The drive is ready but we failed to find the UNC path based on the drive name.
// In this case, it's possibly a MS-DOS device created by 'subst' command that
// - substitutes a network location directly, or
// - substitutes a path in a drive that maps to a network location
rootPath = GetSubstitutedPathForNetworkDosDevice(driveName);
}
else
{
throw;
}
}
return rootPath;
}
/// <summary>
/// Returns a collection of all logical drives in the system.
/// </summary>
/// <returns>
/// A collection of PSDriveInfo objects, one for each logical drive returned from
/// System.Environment.GetLogicalDrives().
/// </returns>
protected override Collection<PSDriveInfo> InitializeDefaultDrives()
{
Collection<PSDriveInfo> results = new Collection<PSDriveInfo>();
DriveInfo[] logicalDrives = DriveInfo.GetDrives();
if (logicalDrives != null)
{
foreach (DriveInfo newDrive in logicalDrives)
{
// Making sure to obey the StopProcessing.
if (Stopping)
{
results.Clear();
break;
}
// cover everything by the try-catch block, because some of the
// DriveInfo properties may throw exceptions
try
{
string newDriveName = newDrive.Name.Substring(0, 1);
string description = string.Empty;
string root = newDrive.Name;
string displayRoot = null;
if (newDrive.DriveType == DriveType.Fixed)
{
try
{
description = newDrive.VolumeLabel;
}
// trying to read the volume label may cause an
// IOException or SecurityException. Just default
// to an empty description.
catch (IOException)
{
}
catch (System.Security.SecurityException)
{
}
catch (System.UnauthorizedAccessException)
{
}
}
if (newDrive.DriveType == DriveType.Network)
{
// Platform notes: This is important because certain mount
// points on non-Windows are enumerated as drives by .NET, but
// the platform itself then has no real network drive support
// as required by this context. Solution: check for network
// drive support before using it.
#if UNIX
continue;
#else
displayRoot = GetRootPathForNetworkDriveOrDosDevice(newDrive);
#endif
}
if (newDrive.DriveType == DriveType.Fixed)
{
if (!newDrive.RootDirectory.Exists)
{
continue;
}
root = newDrive.RootDirectory.FullName;
}
#if UNIX
// Porting notes: On platforms with single root filesystems, ensure
// that we add a filesystem with the root "/" to the initial drive list,
// otherwise path handling will not work correctly because there
// is no : available to separate the filesystems from each other
if (root != StringLiterals.DefaultPathSeparatorString
&& newDriveName == StringLiterals.DefaultPathSeparatorString)
{
root = StringLiterals.DefaultPathSeparatorString;
}
#endif
// Porting notes: On non-windows platforms .net can report two
// drives with the same root, make sure to only add one of those
bool skipDuplicate = false;
foreach (PSDriveInfo driveInfo in results)
{
if (driveInfo.Root == root)
{
skipDuplicate = true;
break;
}
}
if (skipDuplicate)
{
continue;
}
// Create a new VirtualDrive for each logical drive
PSDriveInfo newPSDriveInfo =
new PSDriveInfo(
newDriveName,
ProviderInfo,
root,
description,
null,
displayRoot);
// The network drive is detected when PowerShell is launched.
// Hence it has been persisted during one of the earlier sessions,
if (newDrive.DriveType == DriveType.Network)
{
newPSDriveInfo.IsNetworkDrive = true;
}
if (newDrive.DriveType != DriveType.Fixed)
{
newPSDriveInfo.IsAutoMounted = true;
}
// Porting notes: on the non-Windows platforms, the drive never
// uses : as a separator between drive and path
if (!Platform.IsWindows)
{
newPSDriveInfo.VolumeSeparatedByColon = false;
}
results.Add(newPSDriveInfo);
}
// If there are issues accessing properties of the DriveInfo, do
// not add the drive
catch (IOException)
{
}
catch (System.Security.SecurityException)
{
}
catch (System.UnauthorizedAccessException)
{
}
}
}
results.Add(
new PSDriveInfo(
DriveNames.TempDrive,
ProviderInfo,
Path.GetTempPath(),
SessionStateStrings.TempDriveDescription,
credential: null,
displayRoot: null)
);
return results;
}
#endregion DriveCmdletProvider methods
#region ItemCmdletProvider methods
/// <summary>
/// Retrieves the dynamic parameters required for the Get-Item cmdlet.
/// </summary>
/// <param name="path">The path of the file to process.</param>
/// <returns>An instance of the FileSystemProviderGetItemDynamicParameters class that represents the dynamic parameters.</returns>
protected override object GetItemDynamicParameters(string path)
{
return new FileSystemProviderGetItemDynamicParameters();
}
/// <summary>
/// Determines if the specified path is syntactically and semantically valid.
/// An example path looks like this
/// C:\WINNT\Media\chimes.wav.
/// </summary>
/// <param name="path">
/// The fully qualified path to validate.
/// </param>
/// <returns>
/// True if the path is valid, false otherwise.
/// </returns>
protected override bool IsValidPath(string path)
{
// Path passed should be fully qualified path.
if (string.IsNullOrEmpty(path))
{
return false;
}
// Normalize the path
path = NormalizePath(path);
path = EnsureDriveIsRooted(path);
#if !UNIX
// Remove alternate data stream references
// See if they've used the inline stream syntax. They have more than one colon.
int firstColon = path.IndexOf(':');
int secondColon = path.IndexOf(':', firstColon + 1);
if (secondColon > 0)
{
path = path.Substring(0, secondColon);
}
#endif
// Make sure the path is either drive rooted or UNC Path
if (!IsAbsolutePath(path) && !Utils.PathIsUnc(path))
{
return false;
}
// Exceptions should only deal with exceptional circumstances,
// but unfortunately, FileInfo offers no Try() methods that
// let us check if we _could_ open the file.
try
{
FileInfo testFile = new FileInfo(path);
}
catch (Exception e)
{
if ((e is ArgumentNullException) ||
(e is ArgumentException) ||
(e is System.Security.SecurityException) ||
(e is UnauthorizedAccessException) ||
(e is PathTooLongException) ||
(e is NotSupportedException))
{
return false;
}
else
{
throw;
}
}
return true;
}
/// <summary>
/// Gets the item at the specified path.
/// </summary>
/// <param name="path">
/// A fully qualified path representing a file or directory in the
/// file system.
/// </param>
/// <returns>
/// Nothing. FileInfo and DirectoryInfo objects are written to the
/// context's pipeline.
/// </returns>
/// <exception cref="System.ArgumentException">
/// path is null or empty.
/// </exception>
protected override void GetItem(string path)
{
// Validate the argument
bool isContainer = false;
if (string.IsNullOrEmpty(path))
{
// The parameter was null, throw an exception
throw PSTraceSource.NewArgumentException("path");
}
try
{
#if !UNIX
bool retrieveStreams = false;
FileSystemProviderGetItemDynamicParameters dynamicParameters = null;
if (DynamicParameters != null)
{
dynamicParameters = DynamicParameters as FileSystemProviderGetItemDynamicParameters;
if (dynamicParameters != null)
{
if ((dynamicParameters.Stream != null) && (dynamicParameters.Stream.Length > 0))
{
retrieveStreams = true;
}
else
{
// See if they've used the inline stream syntax. They have more than one colon.
int firstColon = path.IndexOf(':');
int secondColon = path.IndexOf(':', firstColon + 1);
if (secondColon > 0)
{
string streamName = path.Substring(secondColon + 1);
path = path.Remove(secondColon);
retrieveStreams = true;
dynamicParameters = new FileSystemProviderGetItemDynamicParameters();
dynamicParameters.Stream = new string[] { streamName };
}
}
}
}
#endif
FileSystemInfo result = GetFileSystemItem(path, ref isContainer, false);
if (result != null)
{
#if !UNIX
// If we want to retrieve the file streams, retrieve them.
if (retrieveStreams)
{
if (!isContainer)
{
foreach (string desiredStream in dynamicParameters.Stream)
{
// See that it matches the name specified
WildcardPattern p = WildcardPattern.Get(desiredStream, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);
bool foundStream = false;
foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(result.FullName))
{
if (!p.IsMatch(stream.Stream)) { continue; }
string outputPath = result.FullName + ":" + stream.Stream;
WriteItemObject(stream, outputPath, isContainer);
foundStream = true;
}
if ((!WildcardPattern.ContainsWildcardCharacters(desiredStream)) && (!foundStream))
{
string errorMessage = StringUtil.Format(
FileSystemProviderStrings.AlternateDataStreamNotFound, desiredStream, result.FullName);
Exception e = new FileNotFoundException(errorMessage, result.FullName);
WriteError(new ErrorRecord(
e,
"AlternateDataStreamNotFound",
ErrorCategory.ObjectNotFound,
path));
}
}
}
}
else
#endif
{
// Otherwise, return the item itself.
WriteItemObject(result, result.FullName, isContainer);
}
}
else
{
string error = StringUtil.Format(FileSystemProviderStrings.ItemNotFound, path);
Exception e = new IOException(error);
WriteError(new ErrorRecord(
e,
"ItemNotFound",
ErrorCategory.ObjectNotFound,
path));
}
}
catch (IOException ioError)
{
// IOException contains specific message about the error occured and so no need for errordetails.
ErrorRecord er = new ErrorRecord(ioError, "GetItemIOError", ErrorCategory.ReadError, path);
WriteError(er);
}
catch (UnauthorizedAccessException accessException)
{
WriteError(new ErrorRecord(accessException, "GetItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
}
}
private FileSystemInfo GetFileSystemItem(string path, ref bool isContainer, bool showHidden)
{
path = NormalizePath(path);
FileInfo result = new FileInfo(path);
// FileInfo.Exists is always false for a directory path, so we check the attribute for existence.
var attributes = result.Attributes;
if ((int)attributes == -1) { /* Path doesn't exist. */ return null; }
bool hidden = attributes.HasFlag(FileAttributes.Hidden);
isContainer = attributes.HasFlag(FileAttributes.Directory);
FlagsExpression<FileAttributes> evaluator = null;
FlagsExpression<FileAttributes> switchEvaluator = null;
GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
if (fspDynamicParam != null)
{
evaluator = fspDynamicParam.Attributes;
switchEvaluator = FormatAttributeSwitchParameters();
}
bool filterHidden = false; // "Hidden" is specified somewhere in the expression
bool switchFilterHidden = false; // "Hidden" is specified somewhere in the parameters
if (evaluator != null)
{
filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden);
}
if (switchEvaluator != null)
{
switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
}
// if "Hidden" is specified in the attribute filter dynamic parameters
// also return the object
if (!isContainer)
{
if (!hidden || Force || showHidden || filterHidden || switchFilterHidden)
{
s_tracer.WriteLine("Got file info: {0}", result);
return result;
}
}
else
{
// Check to see if the path is the root of a file system drive.
// Since all root paths are hidden we need to show the directory
// anyway
bool isRootPath =
string.Compare(
Path.GetPathRoot(path),
result.FullName,
StringComparison.OrdinalIgnoreCase) == 0;
// if "Hidden" is specified in the attribute filter dynamic parameters
// also return the object
if (isRootPath || !hidden || Force || showHidden || filterHidden || switchFilterHidden)
{
s_tracer.WriteLine("Got directory info: {0}", result);
return new DirectoryInfo(path);
}
}
return null;
}
/// <summary>
/// Invokes the item at the path using ShellExecute semantics.
/// </summary>
/// <param name="path">
/// The item to invoke.
/// </param>
/// <exception cref="System.ArgumentException">
/// path is null or empty.
/// </exception>
protected override void InvokeDefaultAction(string path)
{
if (string.IsNullOrEmpty(path))
{
throw PSTraceSource.NewArgumentException("path");
}
path = NormalizePath(path);
string action = FileSystemProviderStrings.InvokeItemAction;
string resource = StringUtil.Format(FileSystemProviderStrings.InvokeItemResourceFileTemplate, path);
if (ShouldProcess(resource, action))
{
var invokeProcess = new System.Diagnostics.Process();
invokeProcess.StartInfo.FileName = path;
#if UNIX
bool useShellExecute = false;
if (Directory.Exists(path))
{
// Path points to a directory. We have to use xdg-open/open on Linux/macOS.
useShellExecute = true;
}
else
{
try
{
// Try Process.Start first. This works for executables on Win/Unix platforms
invokeProcess.Start();
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 13)
{
// Error code 13 -- Permission denied
// The file is possibly not an executable. We try xdg-open/open on Linux/macOS.
useShellExecute = true;
}
}
if (useShellExecute)
{
invokeProcess.StartInfo.UseShellExecute = true;
invokeProcess.Start();
}
#else
// Use ShellExecute when it's not a headless SKU
invokeProcess.StartInfo.UseShellExecute = Platform.IsWindowsDesktop;
invokeProcess.Start();
#endif
}
}
#endregion ItemCmdletProvider members
#region ContainerCmdletProvider members
#region GetChildItems
/// <summary>
/// Gets the child items of a given directory.
/// </summary>
/// <param name="path">
/// The full path of the directory to enumerate.
/// </param>
/// <param name="recurse">
/// If true, recursively enumerates the child items as well.
/// </param>
/// <param name="depth">
/// Limits the depth of recursion; uint.MaxValue performs full recursion.
/// </param>
/// <returns>
/// Nothing. FileInfo and DirectoryInfo objects that match the filter are written to the
/// context's pipeline.
/// </returns>
/// <exception cref="System.ArgumentException">
/// path is null or empty.
/// </exception>
protected override void GetChildItems(
string path,
bool recurse,
uint depth)
{
GetPathItems(path, recurse, depth, false, ReturnContainers.ReturnMatchingContainers);
}
#endregion GetChildItems
#region GetChildNames
/// <summary>
/// Gets the path names for all children of the specified
/// directory that match the given filter.
/// </summary>
/// <param name="path">
/// The full path of the directory to enumerate.
/// </param>
/// <param name="returnContainers">
/// Determines if all containers should be returned or only those containers that match the
/// filter(s).
/// </param>
/// <returns>
/// Nothing. Child names are written to the context's pipeline.
/// </returns>
/// <exception cref="System.ArgumentException">
/// path is null or empty.
/// </exception>
protected override void GetChildNames(
string path,
ReturnContainers returnContainers)
{
GetPathItems(path, false, uint.MaxValue, true, returnContainers);
}
#endregion GetChildNames
/// <summary>
/// Gets a new provider-specific path and filter (if any) that corresponds to the given
/// path.
/// </summary>
/// <param name="path">
/// The path to the item. Unlike most other provider APIs, this path is likely to
/// contain PowerShell wildcards.
/// </param>
/// <param name="filter">
/// The provider-specific filter currently applied.
/// </param>
/// <param name="updatedPath">
/// The new path to the item.
/// </param>
/// <param name="updatedFilter">
/// The new filter.
/// </param>
/// <returns>
/// True if the path or filter were altered. False otherwise.
/// </returns>
/// <remarks>
/// Makes no attempt to filter if the user has already specified a filter, or
/// if the path contains directory separators. Those are not supported by the
/// FileSystem filter.
/// </remarks>
protected override bool ConvertPath(
string path,
string filter,
ref string updatedPath,
ref string updatedFilter)
{
// Don't handle full paths, paths that the user is already trying to
// filter, or paths they are trying to escape.
if ((!string.IsNullOrEmpty(filter)) ||
(path.Contains(StringLiterals.DefaultPathSeparator, StringComparison.Ordinal)) ||
(path.Contains(StringLiterals.AlternatePathSeparator, StringComparison.Ordinal)) ||
(path.Contains(StringLiterals.EscapeCharacter)))
{
return false;
}
// We can never actually modify the PowerShell path, as the
// Win32 filtering support returns items that match the short
// filename OR long filename.
//
// This creates tons of seemingly incorrect matches, such as:
//
// *~*: Matches any file with a long filename
// *n*: Matches all files with a long filename, but have been
// mapped to a [6][~n].[3] disambiguation bucket
// *.abc: Matches all files that have an extension that begins
// with ABC, since their extension is truncated in the
// short filename
// *.*: Matches all files and directories, even if they don't
// have a dot in their name
// Our algorithm here is pretty simple. The filesystem can handle
// * and ? in PowerShell wildcards, just not character ranges [a-z].
// We replace character ranges with the single-character wildcard, '?'.
updatedPath = path;
updatedFilter = System.Text.RegularExpressions.Regex.Replace(path, "\\[.*?\\]", "?");
return true;
}
private void GetPathItems(
string path,
bool recurse,
uint depth,
bool nameOnly,
ReturnContainers returnContainers)
{
// Verify parameters
if (string.IsNullOrEmpty(path))
{
throw PSTraceSource.NewArgumentException("path");
}
path = NormalizePath(path);
var fsinfo = GetFileSystemInfo(path, out bool isDirectory);
if (fsinfo != null)
{
if (isDirectory)
{
InodeTracker tracker = null;
if (recurse)
{
GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
if (fspDynamicParam != null && fspDynamicParam.FollowSymlink)
{
tracker = new InodeTracker(fsinfo.FullName);
}
}
// Enumerate the directory
Dir((DirectoryInfo)fsinfo, recurse, depth, nameOnly, returnContainers, tracker);
}
else
{
FlagsExpression<FileAttributes> evaluator = null;
FlagsExpression<FileAttributes> switchEvaluator = null;
GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
if (fspDynamicParam != null)
{
evaluator = fspDynamicParam.Attributes;
switchEvaluator = FormatAttributeSwitchParameters();
}
bool attributeFilter = true;
bool switchAttributeFilter = true;
bool filterHidden = false; // "Hidden" is specified somewhere in the expression
bool switchFilterHidden = false; // "Hidden" is specified somewhere in the parameters
if (evaluator != null)
{
attributeFilter = evaluator.Evaluate(fsinfo.Attributes); // expressions
filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden);
}
if (switchEvaluator != null)
{
switchAttributeFilter = switchEvaluator.Evaluate(fsinfo.Attributes); // switch parameters
switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
}
bool hidden = (fsinfo.Attributes & FileAttributes.Hidden) != 0;
// if "Hidden" is explicitly specified anywhere in the attribute filter, then override
// default hidden attribute filter.
if ((attributeFilter && switchAttributeFilter)
&& (filterHidden || switchFilterHidden || Force || !hidden))
{
if (nameOnly)
{
WriteItemObject(
fsinfo.Name,
fsinfo.FullName,
false);
}
else
{
WriteItemObject(fsinfo, path, false);
}
}
}
}
else
{
string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
Exception e = new IOException(error);
WriteError(new ErrorRecord(
e,
"ItemDoesNotExist",
ErrorCategory.ObjectNotFound,
path));
return;
}
}
private void Dir(
DirectoryInfo directory,
bool recurse,
uint depth,
bool nameOnly,
ReturnContainers returnContainers,
InodeTracker tracker) // tracker will be non-null only if the user invoked the -FollowSymLinks and -Recurse switch parameters.
{
List<IEnumerable<FileSystemInfo>> target = new List<IEnumerable<FileSystemInfo>>();
try
{
if (Filter != null &&
Filter.Length > 0)
{
if (returnContainers == ReturnContainers.ReturnAllContainers)
{
// Don't filter directories
target.Add(directory.EnumerateDirectories());
}
else
{
// Filter the directories
target.Add(directory.EnumerateDirectories(Filter, _enumerationOptions));
}
// Making sure to obey the StopProcessing.
if (Stopping)
{
return;
}
// Use the specified filter when retrieving the
// children
target.Add(directory.EnumerateFiles(Filter, _enumerationOptions));
}
else
{
target.Add(directory.EnumerateDirectories());
// Making sure to obey the StopProcessing.
if (Stopping)
{
return;
}
// Don't use a filter to retrieve the children
target.Add(directory.EnumerateFiles());
}
FlagsExpression<FileAttributes> evaluator = null;
FlagsExpression<FileAttributes> switchEvaluator = null;
GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
if (fspDynamicParam != null)
{
evaluator = fspDynamicParam.Attributes;
switchEvaluator = FormatAttributeSwitchParameters();
}
// Write out the items
foreach (IEnumerable<FileSystemInfo> childList in target)
{
// On some systems, this is already sorted. For consistency, always sort again.
IEnumerable<FileSystemInfo> sortedChildList = childList.OrderBy(c => c.Name, StringComparer.CurrentCultureIgnoreCase);
foreach (FileSystemInfo filesystemInfo in sortedChildList)
{
// Making sure to obey the StopProcessing.
if (Stopping)
{
return;
}
try
{
bool attributeFilter = true;
bool switchAttributeFilter = true;
// 'Hidden' is specified somewhere in the expression
bool filterHidden = false;
// 'Hidden' is specified somewhere in the parameters
bool switchFilterHidden = false;
if (evaluator != null)
{
attributeFilter = evaluator.Evaluate(filesystemInfo.Attributes);
filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden);
}
if (switchEvaluator != null)
{
switchAttributeFilter = switchEvaluator.Evaluate(filesystemInfo.Attributes);
switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
}
bool hidden = false;
if (!Force)
{
hidden = (filesystemInfo.Attributes & FileAttributes.Hidden) != 0;
}
// If 'Hidden' is explicitly specified anywhere in the attribute filter, then override
// default hidden attribute filter.
// If specification is to return all containers, then do not do attribute filter on
// the containers.
bool attributeSatisfy =
((attributeFilter && switchAttributeFilter) ||
((returnContainers == ReturnContainers.ReturnAllContainers) &&
((filesystemInfo.Attributes & FileAttributes.Directory) != 0)));
if (attributeSatisfy && (filterHidden || switchFilterHidden || Force || !hidden))
{
if (nameOnly)
{
WriteItemObject(
filesystemInfo.Name,
filesystemInfo.FullName,
false);
}
else
{
if (filesystemInfo is FileInfo)
{
WriteItemObject(filesystemInfo, filesystemInfo.FullName, false);
}
else
{
WriteItemObject(filesystemInfo, filesystemInfo.FullName, true);
}
}
}
}
catch (System.IO.FileNotFoundException ex)
{
WriteError(new ErrorRecord(ex, "DirIOError", ErrorCategory.ReadError, directory.FullName));
}
catch (UnauthorizedAccessException ex)
{
WriteError(new ErrorRecord(ex, "DirUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory.FullName));
}
}
}
bool isFilterHiddenSpecified = false; // "Hidden" is specified somewhere in the expression
bool isSwitchFilterHiddenSpecified = false; // "Hidden" is specified somewhere in the parameters
if (evaluator != null)
{
isFilterHiddenSpecified = evaluator.ExistsInExpression(FileAttributes.Hidden);
}
if (switchEvaluator != null)
{
isSwitchFilterHiddenSpecified = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
}
// Recurse into the directories
// Grab all the directories to recurse
// into separately from the ones that will get written
// out.
if (recurse)
{
// Limiter for recursion
if (depth > 0) // this includes special case 'depth == uint.MaxValue' for unlimited recursion
{
foreach (DirectoryInfo recursiveDirectory in directory.EnumerateDirectories())
{
// Making sure to obey the StopProcessing.
if (Stopping)
{
return;
}
bool hidden = false;
if (!Force)
{
hidden = (recursiveDirectory.Attributes & FileAttributes.Hidden) != 0;
}
// if "Hidden" is explicitly specified anywhere in the attribute filter, then override
// default hidden attribute filter.
if (Force || !hidden || isFilterHiddenSpecified || isSwitchFilterHiddenSpecified)
{
// We only want to recurse into symlinks if
// a) the user has asked to with the -FollowSymLinks switch parameter and
// b) the directory pointed to by the symlink has not already been visited,
// preventing symlink loops.
// c) it is not a reparse point with a target (not OneDrive or an AppX link).
if (tracker == null)
{
if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(recursiveDirectory))
{
continue;
}
}
else if (!tracker.TryVisitPath(recursiveDirectory.FullName))
{
WriteWarning(StringUtil.Format(FileSystemProviderStrings.AlreadyListedDirectory,
recursiveDirectory.FullName));
continue;
}
Dir(recursiveDirectory, recurse, depth - 1, nameOnly, returnContainers, tracker);
}
}
}
}
}
catch (ArgumentException argException)
{
WriteError(new ErrorRecord(argException, "DirArgumentError", ErrorCategory.InvalidArgument, directory.FullName));
}
catch (IOException e)
{
// 2004/10/13-JonN removed ResourceActionFailedException wrapper
WriteError(new ErrorRecord(e, "DirIOError", ErrorCategory.ReadError, directory.FullName));
}
catch (UnauthorizedAccessException uae)
{
// 2004/10/13-JonN removed ResourceActionFailedException wrapper
WriteError(new ErrorRecord(uae, "DirUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory.FullName));
}
}
/// <summary>
/// Create an enum expression evaluator for user-specified attribute filtering
/// switch parameters.
/// </summary>
/// <returns>
/// If any attribute filtering switch parameters are set,
/// returns an evaluator that evaluates these parameters.
/// Otherwise,
/// returns NULL
/// </returns>
private FlagsExpression<FileAttributes> FormatAttributeSwitchParameters()
{
FlagsExpression<FileAttributes> switchParamEvaluator = null;
StringBuilder sb = new StringBuilder();
if (((GetChildDynamicParameters)DynamicParameters).Directory)
{
sb.Append("+Directory");
}
if (((GetChildDynamicParameters)DynamicParameters).File)
{
sb.Append("+!Directory");
}
if (((GetChildDynamicParameters)DynamicParameters).System)
{
sb.Append("+System");
}
if (((GetChildDynamicParameters)DynamicParameters).ReadOnly)
{
sb.Append("+ReadOnly");
}
if (((GetChildDynamicParameters)DynamicParameters).Hidden)
{
sb.Append("+Hidden");
}
string switchParamString = sb.ToString();
if (!string.IsNullOrEmpty(switchParamString))
{
// Remove unnecessary PLUS sign
switchParamEvaluator = new FlagsExpression<FileAttributes>(switchParamString.Substring(1));
}
return switchParamEvaluator;
}
/// <summary>
/// Provides a mode property for FileSystemInfo.
/// </summary>
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
/// <returns>A string representation of the FileAttributes, with one letter per attribute.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
public static string Mode(PSObject instance) => Mode(instance, excludeHardLink: false);
/// <summary>
/// Provides a ModeWithoutHardLink property for FileSystemInfo, without HardLinks for performance reasons.
/// </summary>
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
/// <returns>A string representation of the FileAttributes, with one letter per attribute.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
public static string ModeWithoutHardLink(PSObject instance) => Mode(instance, excludeHardLink: true);
private static string Mode(PSObject instance, bool excludeHardLink)
{
string ToModeString(FileSystemInfo fileSystemInfo)
{
FileAttributes fileAttributes = fileSystemInfo.Attributes;
bool isReparsePoint = InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(fileSystemInfo);
bool isLink = isReparsePoint || (excludeHardLink ? false : InternalSymbolicLinkLinkCodeMethods.IsHardLink(fileSystemInfo));
if (!isLink)
{
// special casing for the common cases - no allocations
switch (fileAttributes)
{
case FileAttributes.Archive:
return "-a---";
case FileAttributes.Directory:
return "d----";
case FileAttributes.Normal:
return "-----";
case FileAttributes.Directory | FileAttributes.ReadOnly:
return "d-r--";
case FileAttributes.Archive | FileAttributes.ReadOnly:
return "-ar--";
}
}
bool isDirectory = fileAttributes.HasFlag(FileAttributes.Directory);
ReadOnlySpan<char> mode = stackalloc char[]
{
isLink ? 'l' : isDirectory ? 'd' : '-',
fileAttributes.HasFlag(FileAttributes.Archive) ? 'a' : '-',
fileAttributes.HasFlag(FileAttributes.ReadOnly) ? 'r' : '-',
fileAttributes.HasFlag(FileAttributes.Hidden) ? 'h' : '-',
fileAttributes.HasFlag(FileAttributes.System) ? 's' : '-',
};
return new string(mode);
}
return instance?.BaseObject is FileSystemInfo fileInfo
? ToModeString(fileInfo)
: string.Empty;
}
/// <summary>
/// Provides a NameString property for FileSystemInfo.
/// </summary>
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
/// <returns>Name if a file or directory, Name -> Target if symlink.</returns>
public static string NameString(PSObject instance)
{
return instance?.BaseObject is FileSystemInfo fileInfo
? InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(fileInfo)
? $"{fileInfo.Name} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}"
: fileInfo.Name
: string.Empty;
}
/// <summary>
/// Provides a LengthString property for FileSystemInfo.
/// </summary>
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
/// <returns>Length as a string.</returns>
public static string LengthString(PSObject instance)
{
return instance?.BaseObject is FileInfo fileInfo
? fileInfo.Attributes.HasFlag(FileAttributes.Offline)
? $"({fileInfo.Length})"
: fileInfo.Length.ToString()
: string.Empty;
}
/// <summary>
/// Provides a LastWriteTimeString property for FileSystemInfo.
/// </summary>
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
/// <returns>LastWriteTime formatted as short date + short time.</returns>
public static string LastWriteTimeString(PSObject instance)
{
return instance?.BaseObject is FileSystemInfo fileInfo
? string.Format(CultureInfo.CurrentCulture, "{0,10:d} {0,8:t}", fileInfo.LastWriteTime)
: string.Empty;
}
#region RenameItem
/// <summary>
/// Renames a file or directory.
/// </summary>
/// <param name="path">
/// The current full path to the file or directory.
/// </param>
/// <param name="newName">
/// The new full path to the file or directory.
/// </param>
/// <returns>
/// Nothing. The renamed DirectoryInfo or FileInfo object is
/// written to the context's pipeline.
/// </returns>
/// <exception cref="System.ArgumentException">
/// path is null or empty.
/// newName is null or empty
/// </exception>
protected override void RenameItem(
string path,
string newName)
{
// Check the parameters
if (string.IsNullOrEmpty(path))
{
throw PSTraceSource.NewArgumentException("path");
}
path = NormalizePath(path);
if (string.IsNullOrEmpty(newName))
{
throw PSTraceSource.NewArgumentException("newName");
}
// Clean up "newname" to fix some common usability problems:
// Rename .\foo.txt .\bar.txt
// Rename C:\temp\foo.txt C:\temp\bar.txt
if (newName.StartsWith(".\\", StringComparison.OrdinalIgnoreCase) ||
newName.StartsWith("./", StringComparison.OrdinalIgnoreCase))
{
newName = newName.Remove(0, 2);
}
else if (string.Equals(Path.GetDirectoryName(path), Path.GetDirectoryName(newName), StringComparison.OrdinalIgnoreCase))
{
newName = Path.GetFileName(newName);
}
// Check to see if the target specified is just filename. We dont allow rename to move the file to a different directory.
// If a path is specified for the newName then we flag that as an error.
if (string.Compare(Path.GetFileName(newName), newName, StringComparison.OrdinalIgnoreCase) != 0)
{
throw PSTraceSource.NewArgumentException("newName", FileSystemProviderStrings.RenameError);
}
// Verify that the target doesn't represent a device name
if (PathIsReservedDeviceName(newName, "RenameError"))
{
return;
}
try
{
bool isContainer = IsItemContainer(path);
FileSystemInfo result = null;
if (isContainer)
{
// Get the DirectoryInfo
DirectoryInfo dir = new DirectoryInfo(path);
// Generate the new path which the directory will
// be renamed to.
string parentDirectory = dir.Parent.FullName;
string newPath = MakePath(parentDirectory, newName);
// Confirm the rename with the user
string action = FileSystemProviderStrings.RenameItemActionDirectory;
string resource = StringUtil.Format(FileSystemProviderStrings.RenameItemResourceFileTemplate, dir.FullName, newPath);
if (ShouldProcess(resource, action))
{
// Now move the file
dir.MoveTo(newPath);
result = dir;
WriteItemObject(result, result.FullName, isContainer);
}
}
else
{
// Get the FileInfo
FileInfo file = new FileInfo(path);
// Generate the new path which the file will be renamed to.
string parentDirectory = file.DirectoryName;
string newPath = MakePath(parentDirectory, newName);
// Confirm the rename with the user
string action = FileSystemProviderStrings.RenameItemActionFile;
string resource = StringUtil.Format(FileSystemProviderStrings.RenameItemResourceFileTemplate, file.FullName, newPath);
if (ShouldProcess(resource, action))
{
// Now move the file
file.MoveTo(newPath);
result = file;
WriteItemObject(result, result.FullName, isContainer);
}
}
}
catch (ArgumentException argException)
{
WriteError(new ErrorRecord(argException, "RenameItemArgumentError", ErrorCategory.InvalidArgument, path));
}
catch (IOException ioException)
{
// IOException contains specific message about the error occured and so no need for errordetails.
WriteError(new ErrorRecord(ioException, "RenameItemIOError", ErrorCategory.WriteError, path));
}
catch (UnauthorizedAccessException accessException)
{
WriteError(new ErrorRecord(accessException, "RenameItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
}
}
#endregion RenameItem
#region NewItem
/// <summary>
/// Creates a file or directory with the given path.
/// </summary>
/// <param name="path">
/// The path of the file or directory to create.
/// </param>
///<param name="type">
/// Specify "file" to create a file.
/// Specify "directory" or "container" to create a directory.
/// </param>
/// <param name="value">
/// If <paramref name="type" /> is "file" then this parameter becomes the content
/// of the file to be created.
/// </param>
/// <returns>
/// Nothing. The new DirectoryInfo or FileInfo object is
/// written to the context's pipeline.
/// </returns>
/// <exception cref="System.ArgumentException">
/// path is null or empty.
/// type is null or empty.
/// </exception>
protected override void NewItem(
string path,
string type,
object value)
{
ItemType itemType = ItemType.Unknown;
// Verify parameters
if (string.IsNullOrEmpty(path))
{
throw PSTraceSource.NewArgumentException("path");
}
if (string.IsNullOrEmpty(type))
{
type = "file";
}
path = NormalizePath(path);
if (Force)
{
if (!CreateIntermediateDirectories(path))
{
return;
}
}
itemType = GetItemType(type);
if (itemType == ItemType.Directory)
{
CreateDirectory(path, true);
}
else if (itemType == ItemType.File)
{
try
{
FileMode fileMode = FileMode.CreateNew;
if (Force)
{
// If force is specified, overwrite the existing
// file
fileMode = FileMode.Create;
}
string action = FileSystemProviderStrings.NewItemActionFile;
string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path);
if (ShouldProcess(resource, action))
{
// Create the file with read/write access and
// not allowing sharing.
using (FileStream newFile =
new FileStream(
path,
fileMode,
FileAccess.Write,
FileShare.None))
{
if (value != null)
{
StreamWriter streamWriter = new StreamWriter(newFile);
streamWriter.Write(value.ToString());
streamWriter.Flush();
streamWriter.Dispose();
}
}
FileInfo fileInfo = new FileInfo(path);
WriteItemObject(fileInfo, path, false);
}
}
catch (IOException exception)
{
// IOException contains specific message about the error occured and so no need for errordetails.
WriteError(new ErrorRecord(exception, "NewItemIOError", ErrorCategory.WriteError, path));
}
catch (UnauthorizedAccessException accessException)
{
WriteError(new ErrorRecord(accessException, "NewItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
}
}
else if (itemType == ItemType.SymbolicLink || itemType == ItemType.HardLink)
{
string action = null;
if (itemType == ItemType.SymbolicLink)
{
action = FileSystemProviderStrings.NewItemActionSymbolicLink;
}
else if (itemType == ItemType.HardLink)
{
action = FileSystemProviderStrings.NewItemActionHardLink;
}
string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path);
if (ShouldProcess(resource, action))
{
bool isDirectory = false;
string strTargetPath = value?.ToString();
if (string.IsNullOrEmpty(strTargetPath))
{
throw PSTraceSource.NewArgumentNullException("value");
}
bool exists = false;
// It is legal to create symbolic links to non-existing targets on
// both Windows and Linux. It is not legal to create hard links to
// non-existing targets on either Windows or Linux.
try
{
exists = GetFileSystemInfo(strTargetPath, out isDirectory) != null;
// Pretend the target exists if we're making a symbolic link.
if (itemType == ItemType.SymbolicLink)
{
exists = true;
}
}
catch (Exception e)
{
WriteError(new ErrorRecord(e, "AccessException", ErrorCategory.PermissionDenied, strTargetPath));
return;
}
if (!exists)
{
string message = StringUtil.Format(FileSystemProviderStrings.ItemNotFound, strTargetPath);
WriteError(new ErrorRecord(new ItemNotFoundException(message), "ItemNotFound", ErrorCategory.ObjectNotFound, strTargetPath));
return;
}
if (itemType == ItemType.HardLink)
{
// Hard links can only be to files, not directories.
if (isDirectory == true)
{
string message = StringUtil.Format(FileSystemProviderStrings.ItemNotFile, strTargetPath);
WriteError(new ErrorRecord(new InvalidOperationException(message), "ItemNotFile", ErrorCategory.InvalidOperation, strTargetPath));
return;
}
}
bool isSymLinkDirectory = false;
bool symLinkExists = false;
try
{
symLinkExists = GetFileSystemInfo(path, out isSymLinkDirectory) != null;
}
catch (Exception e)
{
WriteError(new ErrorRecord(e, "AccessException", ErrorCategory.PermissionDenied, path));
return;
}
if (Force)
{
try
{
if (!isSymLinkDirectory && symLinkExists)
{
File.Delete(path);
}
else if (isSymLinkDirectory && symLinkExists)
{
Directory.Delete(path);
}
}
catch (Exception exception)
{
if ((exception is FileNotFoundException) ||
(exception is DirectoryNotFoundException) ||
(exception is UnauthorizedAccessException) ||
(exception is System.Security.SecurityException) ||
(exception is ArgumentException) ||
(exception is PathTooLongException) ||
(exception is NotSupportedException) ||
(exception is ArgumentNullException) ||
(exception is IOException))
{
WriteError(new ErrorRecord(exception, "NewItemDeleteIOError", ErrorCategory.WriteError, path));
}
else
{
throw;
}
}
}
else
{
if (symLinkExists)
{
string message = StringUtil.Format(FileSystemProviderStrings.SymlinkItemExists, path);
WriteError(new ErrorRecord(new IOException(message), "SymLinkExists", ErrorCategory.ResourceExists, path));
return;
}
}
bool success = false;
if (itemType == ItemType.SymbolicLink)
{
#if UNIX
success = Platform.NonWindowsCreateSymbolicLink(path, strTargetPath);
#else
success = WinCreateSymbolicLink(path, strTargetPath, isDirectory);
#endif
}
else if (itemType == ItemType.HardLink)
{
#if UNIX
success = Platform.NonWindowsCreateHardLink(path, strTargetPath);
#else
success = WinCreateHardLink(path, strTargetPath);
#endif
}
if (!success)
{
// Porting note: The Win32Exception will report the correct error on Linux
int errorCode = Marshal.GetLastWin32Error();
Win32Exception w32Exception = new Win32Exception((int)errorCode);
#if UNIX
if (Platform.Unix.GetErrorCategory(errorCode) == ErrorCategory.PermissionDenied)
#else
if (errorCode == 1314) // ERROR_PRIVILEGE_NOT_HELD
#endif
{
string message = FileSystemProviderStrings.ElevationRequired;
WriteError(new ErrorRecord(new UnauthorizedAccessException(message, w32Exception), "NewItemSymbolicLinkElevationRequired", ErrorCategory.PermissionDenied, value.ToString()));
return;
}
if (errorCode == 1) // ERROR_INVALID_FUNCTION
{
string message = null;
if (itemType == ItemType.SymbolicLink)
{
message = FileSystemProviderStrings.SymbolicLinkNotSupported;
}
else
{
message = FileSystemProviderStrings.HardLinkNotSupported;
}
WriteError(new ErrorRecord(new InvalidOperationException(message, w32Exception), "NewItemInvalidOperation", ErrorCategory.InvalidOperation, value.ToString()));
return;
}
throw w32Exception;
}
else
{
if (isDirectory)
{
DirectoryInfo dirInfo = new DirectoryInfo(path);
WriteItemObject(dirInfo, path, true);
}
else
{
FileInfo fileInfo = new FileInfo(path);
WriteItemObject(fileInfo, path, false);
}
}
}
}
else if (itemType == ItemType.Junction)
{
string action = FileSystemProviderStrings.NewItemActionJunction;
string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path);
if (ShouldProcess(resource, action))
{
bool isDirectory = false;
string strTargetPath = value?.ToString();
bool exists = false;
try
{
exists = GetFileSystemInfo(strTargetPath, out isDirectory) != null;
}
catch (Exception e)
{
WriteError(new ErrorRecord(e, "AccessException", ErrorCategory.PermissionDenied, strTargetPath));
return;
}
if (!exists)
{
WriteError(new ErrorRecord(new InvalidOperationException(FileSystemProviderStrings.ItemNotFound), "ItemNotFound", ErrorCategory.ObjectNotFound, value));
return;
}
// Junctions can only be directories.
if (!isDirectory)
{
string message = StringUtil.Format(FileSystemProviderStrings.ItemNotDirectory, value);
WriteError(new ErrorRecord(new InvalidOperationException(message), "ItemNotDirectory", ErrorCategory.InvalidOperation, value));
return;
}
bool isPathDirectory = false;
FileSystemInfo pathDirInfo;
try
{
pathDirInfo = GetFileSystemInfo(path, out isPathDirectory);
}
catch (Exception e)
{
WriteError(new ErrorRecord(e, "AccessException", ErrorCategory.PermissionDenied, strTargetPath));
return;
}
bool pathExists = pathDirInfo != null;
if (pathExists)
{
// Junctions can only be directories.
if (!isPathDirectory)
{
string message = StringUtil.Format(FileSystemProviderStrings.ItemNotDirectory, path);
WriteError(new ErrorRecord(new InvalidOperationException(message), "ItemNotDirectory", ErrorCategory.InvalidOperation, path));
return;
}
// Junctions cannot have files
if (DirectoryInfoHasChildItems((DirectoryInfo)pathDirInfo))
{
string message = StringUtil.Format(FileSystemProviderStrings.DirectoryNotEmpty, path);
WriteError(new ErrorRecord(new IOException(message), "DirectoryNotEmpty", ErrorCategory.WriteError, path));
return;
}
if (Force)
{
try
{
pathDirInfo.Delete();
}
catch (Exception exception)
{
if ((exception is DirectoryNotFoundException) ||
(exception is UnauthorizedAccessException) ||
(exception is System.Security.SecurityException) ||
(exception is IOException))
{
WriteError(new ErrorRecord(exception, "NewItemDeleteIOError", ErrorCategory.WriteError, path));
}
else
{
throw;
}
}
}
}
else
{
CreateDirectory(path, false);
pathDirInfo = new DirectoryInfo(path);
}
try
{
bool junctionCreated = WinCreateJunction(path, strTargetPath);
if (junctionCreated)
{
WriteItemObject(pathDirInfo, path, true);
}
else // rollback the directory creation if we created it.
{
if (!pathExists)
{
pathDirInfo.Delete();
}
}
}
catch (Exception exception)
{
// rollback the directory creation if it was created.
if (!pathExists)
{
pathDirInfo.Delete();
}
if ((exception is FileNotFoundException) ||
(exception is DirectoryNotFoundException) ||
(exception is UnauthorizedAccessException) ||
(exception is System.Security.SecurityException) ||
(exception is ArgumentException) ||
(exception is PathTooLongException) ||
(exception is NotSupportedException) ||
(exception is ArgumentNullException) ||
(exception is Win32Exception) ||
(exception is IOException))
{
WriteError(new ErrorRecord(exception, "NewItemCreateIOError", ErrorCategory.WriteError, path));
}
else
{
throw;
}
}
}
}
else
{
throw PSTraceSource.NewArgumentException("type", FileSystemProviderStrings.UnknownType);
}
}
private static bool WinCreateSymbolicLink(string path, string strTargetPath, bool isDirectory)
{
// The new AllowUnprivilegedCreate is only available on Win10 build 14972 or newer
var flags = isDirectory ? NativeMethods.SymbolicLinkFlags.Directory : NativeMethods.SymbolicLinkFlags.File;
Version minBuildOfDeveloperMode = new Version(10, 0, 14972, 0);
if (Environment.OSVersion.Version >= minBuildOfDeveloperMode)
{
flags |= NativeMethods.SymbolicLinkFlags.AllowUnprivilegedCreate;
}
var created = NativeMethods.CreateSymbolicLink(path, strTargetPath, flags);
return created;
}
private static bool WinCreateHardLink(string path, string strTargetPath)
{
bool success = NativeMethods.CreateHardLink(path, strTargetPath, IntPtr.Zero);
return success;
}
private static bool WinCreateJunction(string path, string strTargetPath)
{
bool junctionCreated = InternalSymbolicLinkLinkCodeMethods.CreateJunction(path, strTargetPath);
return junctionCreated;
}
private enum ItemType
{
Unknown,
File,
Directory,
SymbolicLink,
Junction,
HardLink
};
private static ItemType GetItemType(string input)
{
ItemType itemType = ItemType.Unknown;
WildcardPattern typeEvaluator =
WildcardPattern.Get(input + "*",
WildcardOptions.IgnoreCase |
WildcardOptions.Compiled);
if (typeEvaluator.IsMatch("directory") ||
typeEvaluator.IsMatch("container"))
{
itemType = ItemType.Directory;
}
else if (typeEvaluator.IsMatch("file"))
{
itemType = ItemType.File;
}
else if (typeEvaluator.IsMatch("symboliclink"))
{
itemType = ItemType.SymbolicLink;
}
else if (typeEvaluator.IsMatch("junction"))
{
itemType = ItemType.Junction;
}
else if (typeEvaluator.IsMatch("hardlink"))
{
itemType = ItemType.HardLink;
}
return itemType;
}
/// <summary>
/// Creates a directory at the specified path.
/// </summary>
/// <param name="path">
/// The path of the directory to create
/// </param>
/// <param name="streamOutput">
/// Determines if the directory should be streamed out after being created.
/// </param>
private void CreateDirectory(string path, bool streamOutput)
{
Dbg.Diagnostics.Assert(
!string.IsNullOrEmpty(path),
"The caller should verify path");
// Get the parent path
string parentPath = GetParentPath(path, null);
// The directory name
string childName = GetChildName(path);
ErrorRecord error = null;
if (!Force && ItemExists(path, out error))
{
string errorMessage = StringUtil.Format(FileSystemProviderStrings.DirectoryExist, path);
Exception e = new IOException(errorMessage);
WriteError(new ErrorRecord(
e,
"DirectoryExist",
ErrorCategory.ResourceExists,
path));
return;
}
if (error != null)
{
WriteError(error);
return;
}
try
{
string action = FileSystemProviderStrings.NewItemActionDirectory;
string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path);
if (ShouldProcess(resource, action))
{
var result = Directory.CreateDirectory(Path.Combine(parentPath, childName));
if (streamOutput)
{
// Write the result to the pipeline
WriteItemObject(result, path, true);
}
}
}
catch (ArgumentException argException)
{
WriteError(new ErrorRecord(argException, "CreateDirectoryArgumentError", ErrorCategory.InvalidArgument, path));
}
catch (IOException ioException)
{
// Ignore the error if force was specified
if (!Force)
{
// IOException contains specific message about the error occured and so no need for errordetails.
WriteError(new ErrorRecord(ioException, "CreateDirectoryIOError", ErrorCategory.WriteError, path));
}
}
catch (UnauthorizedAccessException accessException)
{
WriteError(new ErrorRecord(accessException, "CreateDirectoryUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
}
}
private bool CreateIntermediateDirectories(string path)
{
bool result = false;
if (string.IsNullOrEmpty(path))
{
throw PSTraceSource.NewArgumentException("path");
}
try
{
// Push the paths of the missing directories onto a stack such that the highest missing
// parent in the tree is at the top of the stack.
Stack<string> missingDirectories = new Stack<string>();
string previousParent = path;
do
{
string root = string.Empty;
if (PSDriveInfo != null)
{
root = PSDriveInfo.Root;
}
string parentPath = GetParentPath(path, root);
if (!string.IsNullOrEmpty(parentPath) &&
string.Compare(
parentPath,
previousParent,
StringComparison.OrdinalIgnoreCase) != 0)
{
if (!ItemExists(parentPath))
{
missingDirectories.Push(parentPath);
}
else
{
break;
}
}
else
{
break;
}
previousParent = parentPath;
} while (!string.IsNullOrEmpty(previousParent));
// Now create the missing directories
foreach (string directoryPath in missingDirectories)
{
CreateDirectory(directoryPath, false);
}
result = true;
}
catch (ArgumentException argException)
{
WriteError(new ErrorRecord(argException, "CreateIntermediateDirectoriesArgumentError", ErrorCategory.InvalidArgument, path));
}
catch (IOException ioException)
{
// IOException contains specific message about the error occured and so no need for errordetails.
WriteError(new ErrorRecord(ioException, "CreateIntermediateDirectoriesIOError", ErrorCategory.WriteError, path));
}
catch (UnauthorizedAccessException accessException)
{
WriteError(new ErrorRecord(accessException, "CreateIntermediateDirectoriesUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
}
return result;
}
#endregion NewItem
#region RemoveItem
/// <summary>
/// Removes the specified file or directory.
/// </summary>
/// <param name="path">
/// The full path to the file or directory to be removed.
/// </param>
/// <param name="recurse">
/// Specifies if the operation should also remove child items.
/// </param>
/// <exception cref="System.ArgumentException">
/// path is null or empty.
/// </exception>
protected override void RemoveItem(string path, bool recurse)
{
if (string.IsNullOrEmpty(path))
{
throw PSTraceSource.NewArgumentException("path");
}
try
{
path = NormalizePath(path);
#if !UNIX
bool removeStreams = false;
FileSystemProviderRemoveItemDynamicParameters dynamicParameters = null;
if (DynamicParameters != null)
{
dynamicParameters = DynamicParameters as FileSystemProviderRemoveItemDynamicParameters;
if (dynamicParameters != null)
{
if ((dynamicParameters.Stream != null) && (dynamicParameters.Stream.Length > 0))
{
removeStreams = true;
}
else
{
// See if they've used the inline stream syntax. They have more than one colon.
int firstColon = path.IndexOf(':');
int secondColon = path.IndexOf(':', firstColon + 1);
if (secondColon > 0)
{
string streamName = path.Substring(secondColon + 1);
path = path.Remove(secondColon);
removeStreams = true;
dynamicParameters = new FileSystemProviderRemoveItemDynamicParameters();
dynamicParameters.Stream = new string[] { streamName };
}
}
}
}
#endif
FileSystemInfo fsinfo = GetFileSystemInfo(path, out bool iscontainer);
if (fsinfo == null)
{
string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
Exception e = new IOException(error);
WriteError(new ErrorRecord(e, "ItemDoesNotExist", ErrorCategory.ObjectNotFound, path));
return;
}
#if UNIX
if (iscontainer)
{
RemoveDirectoryInfoItem((DirectoryInfo)fsinfo, recurse, Force, true);
}
else
{
RemoveFileInfoItem((FileInfo)fsinfo, Force);
}
#else
if ((!removeStreams) && iscontainer)
{
RemoveDirectoryInfoItem((DirectoryInfo)fsinfo, recurse, Force, true);
}
else
{
// If we want to remove the file streams, retrieve them and remove them.
if (removeStreams)
{
foreach (string desiredStream in dynamicParameters.Stream)
{
// See that it matches the name specified
WildcardPattern p = WildcardPattern.Get(desiredStream, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);
bool foundStream = false;
foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(fsinfo.FullName))
{
if (!p.IsMatch(stream.Stream)) { continue; }
foundStream = true;
string action = string.Format(
CultureInfo.InvariantCulture,
FileSystemProviderStrings.StreamAction,
stream.Stream, fsinfo.FullName);
if (ShouldProcess(action))
{
AlternateDataStreamUtilities.DeleteFileStream(fsinfo.FullName, stream.Stream);
}
}
if ((!WildcardPattern.ContainsWildcardCharacters(desiredStream)) && (!foundStream))
{
string errorMessage = StringUtil.Format(
FileSystemProviderStrings.AlternateDataStreamNotFound, desiredStream, fsinfo.FullName);
Exception e = new FileNotFoundException(errorMessage, fsinfo.FullName);
WriteError(new ErrorRecord(
e,
"AlternateDataStreamNotFound",
ErrorCategory.ObjectNotFound,
path));
}
}
}
else
{
RemoveFileInfoItem((FileInfo)fsinfo, Force);
}
}
#endif
}
catch (IOException exception)
{
// IOException contains specific message about the error occured and so no need for errordetails.
WriteError(new ErrorRecord(exception, "RemoveItemIOError", ErrorCategory.WriteError, path));
}
catch (UnauthorizedAccessException accessException)
{
WriteError(new ErrorRecord(accessException, "RemoveItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
}
}
/// <summary>
/// Retrieves the dynamic parameters required for the Remove-Item cmdlet.
/// </summary>
/// <param name="path">The path of the file to process.</param>
/// <param name="recurse">Whether to recurse into containers.</param>
/// <returns>An instance of the FileSystemProviderRemoveItemDynamicParameters class that represents the dynamic parameters.</returns>
protected override object RemoveItemDynamicParameters(string path, bool recurse)
{
if (!recurse)
{
return new FileSystemProviderRemoveItemDynamicParameters();
}
else
{
return null;
}
}
/// <summary>
/// Removes a directory from the file system.
/// </summary>
/// <param name="directory">
/// The DirectoryInfo object representing the directory to be removed.
/// </param>
/// <param name="recurse">
/// If true, ShouldProcess will be called for each item in the subtree.
/// If false, ShouldProcess will only be called for the directory item.
/// </param>
/// <param name="force">
/// If true, attempts to modify the file attributes in case of a failure so that
/// the file can be removed.
/// </param>
/// <param name="rootOfRemoval">
/// True if the DirectoryInfo being passed in is the root of the tree being removed.
/// ShouldProcess will be called if this is true or if recurse is true.
/// </param>
private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool force, bool rootOfRemoval)
{
Dbg.Diagnostics.Assert(directory != null, "Caller should always check directory");
bool continueRemoval = true;
// We only want to confirm the removal if this is the root of the
// tree being removed or the recurse flag is specified.
if (rootOfRemoval || recurse)
{
// Confirm the user wants to remove the directory
string action = FileSystemProviderStrings.RemoveItemActionDirectory;
continueRemoval = ShouldProcess(directory.FullName, action);
}
if (directory.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
try
{
directory.Delete();
}
catch (Exception e)
{
string error = StringUtil.Format(FileSystemProviderStrings.CannotRemoveItem, directory.FullName);
Exception exception = new IOException(error, e);
WriteError(new ErrorRecord(exception, "DeleteSymbolicLinkFailed", ErrorCategory.WriteError, directory));
}
return;
}
if (continueRemoval)
{
// Loop through each of the contained directories and recurse into them for
// removal.
foreach (DirectoryInfo childDir in directory.EnumerateDirectories())
{
// Making sure to obey the StopProcessing.
if (Stopping)
{
return;
}
if (childDir != null)
{
RemoveDirectoryInfoItem(childDir, recurse, force, false);
}
}
// Loop through each of the contained files and remove them.
IEnumerable<FileInfo> files = null;
if (!string.IsNullOrEmpty(Filter))
{
files = directory.EnumerateFiles(Filter);
}
else
{
files = directory.EnumerateFiles();
}
foreach (FileInfo file in files)
{
// Making sure to obey the StopProcessing.
if (Stopping)
{
return;
}
if (file != null)
{
if (recurse)
{
// When recurse is specified we need to confirm each
// item before removal.
RemoveFileInfoItem(file, force);
}
else
{
// When recurse is not specified just delete all the
// subitems without confirming with the user.
RemoveFileSystemItem(file, force);
}
}
}
// Check to see if the item has children
bool hasChildren = DirectoryInfoHasChildItems(directory);
if (hasChildren && !force)
{
string error = StringUtil.Format(FileSystemProviderStrings.DirectoryNotEmpty, directory.FullName);
Exception e = new IOException(error);
WriteError(new ErrorRecord(e, "DirectoryNotEmpty", ErrorCategory.WriteError, directory));
}
else // !hasChildren || force
{
// Finally, remove the directory
RemoveFileSystemItem(directory, force);
}
}
}
/// <summary>
/// Removes a file from the file system.
/// </summary>
/// <param name="file">
/// The FileInfo object representing the file to be removed.
/// </param>
/// <param name="force">
/// If true, attempts to modify the file attributes in case of a failure so that
/// the file can be removed.
/// </param>
private void RemoveFileInfoItem(FileInfo file, bool force)
{
Dbg.Diagnostics.Assert(
file != null,
"Caller should always check file");
string action = FileSystemProviderStrings.RemoveItemActionFile;
if (ShouldProcess(file.FullName, action))
{
RemoveFileSystemItem(file, force);
}
}
/// <summary>
/// Removes the file system object from the file system.
/// </summary>
/// <param name="fileSystemInfo">
/// The FileSystemInfo object representing the file or directory to be removed.
/// </param>
/// <param name="force">
/// If true, the readonly and hidden attributes will be masked off in the case of
/// an error, and the removal will be attempted again. If false, exceptions are
/// written to the error pipeline.
/// </param>
private void RemoveFileSystemItem(FileSystemInfo fileSystemInfo, bool force)
{
Dbg.Diagnostics.Assert(
fileSystemInfo != null,
"Caller should always check fileSystemInfo");
// First check if we can delete this file when force is not specified.
if (!Force &&
(fileSystemInfo.Attributes & (FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReadOnly)) != 0)
{
string error = StringUtil.Format(FileSystemProviderStrings.PermissionError);
Exception e = new IOException(error);
ErrorDetails errorDetails =
new ErrorDetails(this, "FileSystemProviderStrings",
"CannotRemoveItem",
fileSystemInfo.FullName,
e.Message);
ErrorRecord errorRecord = new ErrorRecord(e, "RemoveFileSystemItemUnAuthorizedAccess", ErrorCategory.PermissionDenied, fileSystemInfo);
errorRecord.ErrorDetails = errorDetails;
WriteError(errorRecord);
return;
}
// Store the old attributes in case we fail to delete
FileAttributes oldAttributes = fileSystemInfo.Attributes;
bool attributeRecoveryRequired = false;
try
{
// Try to delete the item. Strip any problematic attributes
// if they've specified force.
if (force)
{
fileSystemInfo.Attributes = fileSystemInfo.Attributes & ~(FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
attributeRecoveryRequired = true;
}
fileSystemInfo.Delete();
if (force)
{
attributeRecoveryRequired = false;
}
}
catch (Exception fsException)
{
ErrorDetails errorDetails =
new ErrorDetails(this, "FileSystemProviderStrings",
"CannotRemoveItem",
fileSystemInfo.FullName,
fsException.Message);
if ((fsException is System.Security.SecurityException) ||
(fsException is UnauthorizedAccessException))
{
ErrorRecord errorRecord = new ErrorRecord(fsException, "RemoveFileSystemItemUnAuthorizedAccess", ErrorCategory.PermissionDenied, fileSystemInfo);
errorRecord.ErrorDetails = errorDetails;
WriteError(errorRecord);
}
else if (fsException is ArgumentException)
{
ErrorRecord errorRecord = new ErrorRecord(fsException, "RemoveFileSystemItemArgumentError", ErrorCategory.InvalidArgument, fileSystemInfo);
errorRecord.ErrorDetails = errorDetails;
WriteError(errorRecord);
}
else if ((fsException is IOException) ||
(fsException is FileNotFoundException) ||
(fsException is DirectoryNotFoundException))
{
ErrorRecord errorRecord = new ErrorRecord(fsException, "RemoveFileSystemItemIOError", ErrorCategory.WriteError, fileSystemInfo);
errorRecord.ErrorDetails = errorDetails;
WriteError(errorRecord);
}
else
{
throw;
}
}
finally
{
if (attributeRecoveryRequired)
{
try
{
if (fileSystemInfo.Exists)
{
fileSystemInfo.Attributes = oldAttributes;
}
}
catch (Exception attributeException)
{
if ((attributeException is System.IO.DirectoryNotFoundException) ||
(attributeException is System.Security.SecurityException) ||
(attributeException is System.ArgumentException) ||
(attributeException is System.IO.FileNotFoundException) ||
(attributeException is System.IO.IOException))
{
ErrorDetails attributeDetails = new ErrorDetails(
this, "FileSystemProviderStrings",
"CannotRestoreAttributes",
fileSystemInfo.FullName,
attributeException.Message);
ErrorRecord errorRecord = new ErrorRecord(attributeException, "RemoveFileSystemItemCannotRestoreAttributes", ErrorCategory.PermissionDenied, fileSystemInfo);
errorRecord.ErrorDetails = attributeDetails;
WriteError(errorRecord);
}
else
throw;
}
}
}
}
#endregion RemoveItem
#region ItemExists
/// <summary>
/// Determines if a file or directory exists at the specified path.
/// </summary>
/// <param name="path">
/// The path of the item to check.
/// </param>
/// <returns>
/// True if a file or directory exists at the specified path, false otherwise.