9538 lines
377 KiB
C#
9538 lines
377 KiB
C#
// 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.Security;
|
|
using System.Security.AccessControl;
|
|
using System.Text;
|
|
using System.Xml;
|
|
using System.Xml.XPath;
|
|
using Microsoft.Win32.SafeHandles;
|
|
using Dbg = System.Management.Automation;
|
|
using System.Runtime.InteropServices;
|
|
using System.Management.Automation.Runspaces;
|
|
|
|
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(Boolean), typeof(String), typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItem)]
|
|
[OutputType(typeof(Boolean), 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
|
|
{
|
|
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 path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
|
|
} // NormalizePath
|
|
|
|
/// <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);
|
|
}
|
|
}
|
|
return providerInfo;
|
|
} // Start
|
|
|
|
#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;
|
|
} // NewDrive
|
|
|
|
/// <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(":", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// 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 http://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)
|
|
{
|
|
}
|
|
} // foreach
|
|
}
|
|
return results;
|
|
} // InitializeDefaultDrives
|
|
|
|
#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) && !IsUNCPath(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));
|
|
}
|
|
} // GetItem
|
|
|
|
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;
|
|
} // GetFileSystemItem
|
|
|
|
/// <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
|
|
}
|
|
} // InvokeDefaultAction
|
|
|
|
#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);
|
|
} // GetChildItems
|
|
#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);
|
|
} // GetChildNames
|
|
#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));
|
|
}
|
|
}// foreach
|
|
}// foreach
|
|
|
|
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.
|
|
if (tracker == null)
|
|
{
|
|
if (InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(recursiveDirectory))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else if (!tracker.TryVisitPath(recursiveDirectory.FullName))
|
|
{
|
|
WriteWarning(StringUtil.Format(FileSystemProviderStrings.AlreadyListedDirectory,
|
|
recursiveDirectory.FullName));
|
|
continue;
|
|
}
|
|
|
|
Dir(recursiveDirectory, recurse, depth - 1, nameOnly, returnContainers, tracker);
|
|
}
|
|
}//foreach
|
|
}//if
|
|
}//if
|
|
}
|
|
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));
|
|
}
|
|
} // Dir
|
|
|
|
/// <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>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
|
|
public static string Mode(PSObject instance)
|
|
{
|
|
if (instance == null)
|
|
return String.Empty;
|
|
|
|
FileSystemInfo fileInfo = (FileSystemInfo)instance.BaseObject;
|
|
if (fileInfo == null)
|
|
return String.Empty;
|
|
|
|
char[] mode = new char[6];
|
|
mode[0] = (fileInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory ? 'd' : '-';
|
|
mode[1] = (fileInfo.Attributes & FileAttributes.Archive) == FileAttributes.Archive ? 'a' : '-';
|
|
mode[2] = (fileInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly ? 'r' : '-';
|
|
mode[3] = (fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden ? 'h' : '-';
|
|
mode[4] = (fileInfo.Attributes & FileAttributes.System) == FileAttributes.System ? 's' : '-';
|
|
// Mark the last bit as a "l" if it's a reparsepoint (symbolic link or junction)
|
|
// Porting note: these need to be handled specially
|
|
bool isReparsePoint = InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(fileInfo);
|
|
bool isHardLink = InternalSymbolicLinkLinkCodeMethods.IsHardLink(fileInfo);
|
|
mode[5] = isReparsePoint || isHardLink ? 'l' : '-';
|
|
|
|
return new string(mode);
|
|
}
|
|
|
|
#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));
|
|
}
|
|
} // RenameItem
|
|
|
|
#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);
|
|
}
|
|
} // NewItem
|
|
|
|
private static bool WinCreateSymbolicLink(string path, string strTargetPath, bool isDirectory)
|
|
{
|
|
int created = NativeMethods.CreateSymbolicLink(path, strTargetPath, (isDirectory ? 1 : 0));
|
|
bool success = (created == 1) ? true : false;
|
|
return success;
|
|
}
|
|
|
|
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));
|
|
}
|
|
} // CreateDirectory
|
|
|
|
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;
|
|
} // CreateIntermediateDirectories
|
|
|
|
#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));
|
|
}
|
|
} // RemoveItem
|
|
|
|
/// <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);
|
|
}
|
|
} // ShouldProcess
|
|
} // RemoveDirectoryInfoItem
|
|
|
|
/// <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);
|
|
} // ShouldProcess
|
|
} // RemoveFileInfoItem
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
}
|
|
} // RemoveFileSystemItem
|
|
|
|
#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.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
protected override bool ItemExists(string path)
|
|
{
|
|
ErrorRecord error = null;
|
|
bool result = ItemExists(path, out error);
|
|
|
|
if (error != null)
|
|
{
|
|
WriteError(error);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implementation of ItemExists for the provider. This implementation
|
|
/// allows the caller to decide if it wants to WriteError or not based
|
|
/// on the returned ErrorRecord
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path of the object to check
|
|
/// </param>
|
|
/// <param name="error">
|
|
/// An error record is returned in this parameter if there was an error.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if an object exists at the specified path, false otherwise.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
private bool ItemExists(string path, out ErrorRecord error)
|
|
{
|
|
error = null;
|
|
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
bool result = false;
|
|
|
|
path = NormalizePath(path);
|
|
|
|
try
|
|
{
|
|
var fsinfo = GetFileSystemInfo(path, out bool _);
|
|
result = fsinfo != null;
|
|
|
|
FileSystemItemProviderDynamicParameters itemExistsDynamicParameters =
|
|
DynamicParameters as FileSystemItemProviderDynamicParameters;
|
|
|
|
// If the items see if we need to check the age of the file...
|
|
if (result && itemExistsDynamicParameters != null)
|
|
{
|
|
DateTime lastWriteTime = fsinfo.LastWriteTime;
|
|
|
|
if (itemExistsDynamicParameters.OlderThan.HasValue)
|
|
{
|
|
result = lastWriteTime < itemExistsDynamicParameters.OlderThan.Value;
|
|
}
|
|
if (itemExistsDynamicParameters.NewerThan.HasValue)
|
|
{
|
|
result = lastWriteTime > itemExistsDynamicParameters.NewerThan.Value;
|
|
}
|
|
}
|
|
}
|
|
catch (System.Security.SecurityException security)
|
|
{
|
|
error = new ErrorRecord(security, "ItemExistsSecurityError", ErrorCategory.PermissionDenied, path);
|
|
}
|
|
catch (ArgumentException argument)
|
|
{
|
|
error = new ErrorRecord(argument, "ItemExistsArgumentError", ErrorCategory.InvalidArgument, path);
|
|
}
|
|
catch (UnauthorizedAccessException unauthorized)
|
|
{
|
|
error = new ErrorRecord(unauthorized, "ItemExistsUnauthorizedAccessError", ErrorCategory.PermissionDenied, path);
|
|
}
|
|
catch (PathTooLongException pathTooLong)
|
|
{
|
|
error = new ErrorRecord(pathTooLong, "ItemExistsPathTooLongError", ErrorCategory.InvalidArgument, path);
|
|
}
|
|
catch (NotSupportedException notSupported)
|
|
{
|
|
error = new ErrorRecord(notSupported, "ItemExistsNotSupportedError", ErrorCategory.InvalidOperation, path);
|
|
}
|
|
|
|
return result;
|
|
} // Exists
|
|
|
|
/// <summary>
|
|
/// Adds -OlderThan, -NewerThan dynamic properties.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// If the path was specified on the command line, this is the path
|
|
/// to the item to get the dynamic parameters for.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Overrides of this method should return an object that has properties and fields decorated with
|
|
/// parsing attributes similar to a cmdlet class or a
|
|
/// <see cref="System.Management.Automation.RuntimeDefinedParameterDictionary"/>.
|
|
///
|
|
/// The default implementation returns null. (no additional parameters)
|
|
/// </returns>
|
|
protected override object ItemExistsDynamicParameters(string path)
|
|
{
|
|
using (PSTransactionManager.GetEngineProtectionScope())
|
|
{
|
|
return new FileSystemItemProviderDynamicParameters();
|
|
}
|
|
} // ItemExistsDynamicParameters
|
|
|
|
#endregion ItemExists
|
|
|
|
#region HasChildItems
|
|
|
|
/// <summary>
|
|
/// Determines if the given path is a directory, and has children.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The full path to the directory.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the path refers to a directory that contains other
|
|
/// directories or files. False otherwise.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
protected override bool HasChildItems(string path)
|
|
{
|
|
bool result = false;
|
|
|
|
// verify parameters
|
|
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
|
|
// First check to see if it is a directory
|
|
try
|
|
{
|
|
DirectoryInfo directory = new DirectoryInfo(path);
|
|
|
|
// If the above didn't throw an exception, check to
|
|
// see if we should proceed and if it contains any children
|
|
if ((directory.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
|
|
return false;
|
|
|
|
result = DirectoryInfoHasChildItems(directory);
|
|
}
|
|
catch (ArgumentNullException)
|
|
{
|
|
// Since we couldn't convert the path to a DirectoryInfo
|
|
// the path could not be a file system container with
|
|
// children
|
|
|
|
result = false;
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
// Since we couldn't convert the path to a DirectoryInfo
|
|
// the path could not be a file system container with
|
|
// children
|
|
|
|
result = false;
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
// Since we couldn't convert the path to a DirectoryInfo
|
|
// the path could not be a file system container with
|
|
// children
|
|
|
|
result = false;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// Since we couldn't convert the path to a DirectoryInfo
|
|
// the path could not be a file system container with
|
|
// children
|
|
|
|
result = false;
|
|
}
|
|
catch (NotSupportedException)
|
|
{
|
|
// Happens when we try to access an alternate data stream
|
|
result = false;
|
|
}
|
|
|
|
return result;
|
|
} // HasChildItems
|
|
|
|
private static bool DirectoryInfoHasChildItems(DirectoryInfo directory)
|
|
{
|
|
Dbg.Diagnostics.Assert(
|
|
directory != null,
|
|
"The caller should verify directory.");
|
|
|
|
bool result = false;
|
|
|
|
IEnumerable<FileSystemInfo> children = directory.EnumerateFileSystemInfos();
|
|
|
|
if (children.Any())
|
|
{
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
} // DirectoryInfoHasChildItems
|
|
|
|
#endregion HasChildItems
|
|
|
|
#region CopyItem
|
|
|
|
/// <summary>
|
|
/// Copies an item at the specified path to the given destination.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path of the item to copy.
|
|
/// </param>
|
|
/// <param name="destinationPath">
|
|
/// The path of the destination.
|
|
/// </param>
|
|
/// <param name="recurse">
|
|
/// Specifies if the operation should also copy child items.
|
|
/// </param>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// destination path is null or empty.
|
|
/// </exception>
|
|
/// <returns>
|
|
/// Nothing. Copied items are written to the context's pipeline.
|
|
/// </returns>
|
|
protected override void CopyItem(
|
|
string path,
|
|
string destinationPath,
|
|
bool recurse)
|
|
{
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(destinationPath))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("destinationPath");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
destinationPath = NormalizePath(destinationPath);
|
|
|
|
PSSession fromSession = null;
|
|
PSSession toSession = null;
|
|
|
|
CopyItemDynamicParameters copyDynamicParameter = DynamicParameters as CopyItemDynamicParameters;
|
|
|
|
if (copyDynamicParameter != null)
|
|
{
|
|
if (copyDynamicParameter.FromSession != null)
|
|
{
|
|
fromSession = copyDynamicParameter.FromSession;
|
|
}
|
|
else
|
|
{
|
|
toSession = copyDynamicParameter.ToSession;
|
|
}
|
|
}
|
|
|
|
_excludeMatcher = SessionStateUtilities.CreateWildcardsFromStrings(Exclude, WildcardOptions.IgnoreCase);
|
|
|
|
// if the source and destination path are same (for a local copy) then flag it as error.
|
|
if ((toSession == null) && (fromSession == null) && InternalSymbolicLinkLinkCodeMethods.IsSameFileSystemItem(path, destinationPath))
|
|
{
|
|
String error = StringUtil.Format(FileSystemProviderStrings.CopyError, path);
|
|
Exception e = new IOException(error);
|
|
e.Data[SelfCopyDataKey] = destinationPath;
|
|
WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, path));
|
|
return;
|
|
}
|
|
// Copy-Item from session
|
|
if (fromSession != null)
|
|
{
|
|
CopyItemFromRemoteSession(path, destinationPath, recurse, Force, fromSession);
|
|
}
|
|
|
|
else
|
|
{
|
|
// Copy-Item to session
|
|
if (toSession != null)
|
|
{
|
|
using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
|
|
{
|
|
ps.Runspace = toSession.Runspace;
|
|
CopyItemLocalOrToSession(path, destinationPath, recurse, Force, ps);
|
|
}
|
|
}
|
|
|
|
// Copy-Item local
|
|
else
|
|
{
|
|
CopyItemLocalOrToSession(path, destinationPath, recurse, Force, null);
|
|
}
|
|
}
|
|
|
|
_excludeMatcher.Clear();
|
|
_excludeMatcher = null;
|
|
} //CopyItem
|
|
|
|
private void CopyItemFromRemoteSession(string path, string destinationPath, bool recurse, bool force, PSSession fromSession)
|
|
{
|
|
using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
|
|
{
|
|
ps.Runspace = fromSession.Runspace;
|
|
|
|
InitializeFunctionPSCopyFileFromRemoteSession(ps);
|
|
|
|
try
|
|
{
|
|
// get info on source
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
|
|
ps.AddParameter("getPathItems", path);
|
|
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
if (op == null)
|
|
{
|
|
Exception e = new IOException(String.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailedToReadFile, path));
|
|
WriteError(new ErrorRecord(e, "CopyItemRemotelyFailedToReadFile", ErrorCategory.WriteError, path));
|
|
return;
|
|
}
|
|
|
|
bool exists = (bool)(op["Exists"]);
|
|
if (!exists)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(SessionStateStrings.PathNotFound, path);
|
|
}
|
|
|
|
if (op["Items"] != null)
|
|
{
|
|
PSObject obj = (PSObject)op["Items"];
|
|
ArrayList itemsList = (ArrayList)obj.BaseObject;
|
|
foreach (PSObject item in itemsList)
|
|
{
|
|
Hashtable ItemInfo = (Hashtable)item.BaseObject;
|
|
string itemName = (string)ItemInfo["Name"];
|
|
string itemFullName = (string)ItemInfo["FullName"];
|
|
bool isContainer = (bool)ItemInfo["IsDirectory"];
|
|
|
|
if (isContainer)
|
|
{
|
|
if (File.Exists(destinationPath))
|
|
{
|
|
Exception e = new IOException(String.Format(
|
|
CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemotelyDestinationIsFile,
|
|
path,
|
|
destinationPath));
|
|
WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destinationPath));
|
|
return;
|
|
}
|
|
|
|
CopyDirectoryFromRemoteSession(
|
|
itemName,
|
|
itemFullName,
|
|
destinationPath,
|
|
force,
|
|
recurse,
|
|
ps);
|
|
}
|
|
else
|
|
{
|
|
bool excludeFile = SessionStateUtilities.MatchesAnyWildcardPattern(itemName, _excludeMatcher, false);
|
|
if (!excludeFile)
|
|
{
|
|
long itemSize = (long)ItemInfo["FileSize"];
|
|
CopyFileFromRemoteSession(itemName, itemFullName, destinationPath, force, ps, itemSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
RemoveFunctionsPSCopyFileFromRemoteSession(ps);
|
|
}
|
|
}
|
|
} // CopyItemFromRemoteSession
|
|
|
|
private void CopyItemLocalOrToSession(string path, string destinationPath, bool recurse, bool Force, System.Management.Automation.PowerShell ps)
|
|
{
|
|
bool isContainer = IsItemContainer(path);
|
|
|
|
InitializeFunctionsPSCopyFileToRemoteSession(ps);
|
|
|
|
try
|
|
{
|
|
if (isContainer)
|
|
{
|
|
// Get the directory info
|
|
DirectoryInfo dir = new DirectoryInfo(path);
|
|
|
|
// Now copy the directory to the destination
|
|
|
|
CopyDirectoryInfoItem(dir, destinationPath, recurse, Force, ps);
|
|
}
|
|
else // !isContainer
|
|
{
|
|
// Get the file info
|
|
FileInfo file = new FileInfo(path);
|
|
|
|
CopyFileInfoItem(file, destinationPath, Force, ps);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
RemoveFunctionPSCopyFileToRemoteSession(ps);
|
|
}
|
|
} // CopyItem
|
|
|
|
private void CopyDirectoryInfoItem(
|
|
DirectoryInfo directory,
|
|
string destination,
|
|
bool recurse,
|
|
bool force,
|
|
System.Management.Automation.PowerShell ps)
|
|
{
|
|
Dbg.Diagnostics.Assert(
|
|
directory != null,
|
|
"The caller should verify directory.");
|
|
|
|
// Generate the path based on whether the destination path exists and
|
|
// is a container.
|
|
// If the destination exists and is a container the directory we are copying
|
|
// will become a child of that directory.
|
|
// If the destination doesn't exist we will just try to copy to that new
|
|
// path.
|
|
|
|
if (ps == null)
|
|
{
|
|
if (IsItemContainer(destination))
|
|
{
|
|
destination = MakePath(destination, directory.Name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RemoteDirectoryExist(ps, destination))
|
|
{
|
|
destination = Path.Combine(destination, directory.Name);
|
|
}
|
|
}
|
|
|
|
s_tracer.WriteLine("destination = {0}", destination);
|
|
|
|
// Confirm the copy with the user
|
|
|
|
string action = FileSystemProviderStrings.CopyItemActionDirectory;
|
|
|
|
string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, directory.FullName, destination);
|
|
|
|
if (ShouldProcess(resource, action))
|
|
{
|
|
// Create the new directory
|
|
// CreateDirectory does the WriteItemObject for the new DirectoryInfo
|
|
|
|
if (ps == null)
|
|
{
|
|
CreateDirectory(destination, true);
|
|
}
|
|
else
|
|
{
|
|
// Verify that the destination is not a file on the remote end
|
|
if (RemoteDestinationPathIsFile(destination, ps))
|
|
{
|
|
Exception e = new IOException(String.Format(CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemoteDestinationIsFile,
|
|
destination));
|
|
WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destination));
|
|
return;
|
|
}
|
|
|
|
destination = CreateDirectoryOnRemoteSession(destination, force, ps);
|
|
if (destination == null)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (recurse)
|
|
{
|
|
// Now copy all the files to that directory
|
|
|
|
IEnumerable<FileInfo> files = null;
|
|
|
|
if (String.IsNullOrEmpty(Filter))
|
|
{
|
|
files = directory.EnumerateFiles();
|
|
}
|
|
else
|
|
{
|
|
files = directory.EnumerateFiles(Filter);
|
|
}
|
|
|
|
foreach (FileInfo file in files)
|
|
{
|
|
// Making sure to obey the StopProcessing.
|
|
if (Stopping)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (file != null)
|
|
{
|
|
try
|
|
{
|
|
// CopyFileInfoItem does the WriteItemObject for the new FileInfo
|
|
|
|
CopyFileInfoItem(file, destination, force, ps);
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "CopyDirectoryInfoItemArgumentError", ErrorCategory.InvalidArgument, file));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "CopyDirectoryInfoItemIOError", ErrorCategory.WriteError, file));
|
|
}
|
|
catch (UnauthorizedAccessException accessException)
|
|
{
|
|
WriteError(new ErrorRecord(accessException, "CopyDirectoryInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
|
|
}
|
|
}
|
|
} // for files
|
|
|
|
// Now copy all the directories to that directory
|
|
|
|
foreach (DirectoryInfo childDir in directory.EnumerateDirectories())
|
|
{
|
|
// Making sure to obey the StopProcessing.
|
|
if (Stopping)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (childDir != null)
|
|
{
|
|
try
|
|
{
|
|
CopyDirectoryInfoItem(childDir, destination, recurse, force, ps);
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "CopyDirectoryInfoItemArgumentError", ErrorCategory.InvalidArgument, childDir));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "CopyDirectoryInfoItemIOError", ErrorCategory.WriteError, childDir));
|
|
}
|
|
catch (UnauthorizedAccessException accessException)
|
|
{
|
|
WriteError(new ErrorRecord(accessException, "CopyDirectoryInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, childDir));
|
|
}
|
|
}
|
|
} // for directories
|
|
}
|
|
} // ShouldProcess
|
|
} // CopyDirectoryInfoItem
|
|
|
|
private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, System.Management.Automation.PowerShell ps)
|
|
{
|
|
Dbg.Diagnostics.Assert(
|
|
file != null,
|
|
"The caller should verify file.");
|
|
|
|
// If the destination is a container, add the file name
|
|
// to the destination path.
|
|
|
|
if (ps == null)
|
|
{
|
|
if (IsItemContainer(destinationPath))
|
|
{
|
|
destinationPath = MakePath(destinationPath, file.Name);
|
|
}
|
|
|
|
//if the source and destination path are same then flag it as error.
|
|
if (InternalSymbolicLinkLinkCodeMethods.IsSameFileSystemItem(destinationPath, file.FullName))
|
|
{
|
|
String error = StringUtil.Format(FileSystemProviderStrings.CopyError, destinationPath);
|
|
Exception e = new IOException(error);
|
|
e.Data[SelfCopyDataKey] = file.FullName;
|
|
WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destinationPath));
|
|
return;
|
|
}
|
|
// Verify that the target doesn't represent a device name
|
|
if (PathIsReservedDeviceName(destinationPath, "CopyError"))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Confirm the copy with the user
|
|
|
|
string action = FileSystemProviderStrings.CopyItemActionFile;
|
|
|
|
string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, file.FullName, destinationPath);
|
|
|
|
bool excludeFile = SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false);
|
|
|
|
if (!excludeFile)
|
|
{
|
|
if (ShouldProcess(resource, action))
|
|
{
|
|
try
|
|
{
|
|
if (ps == null)
|
|
{
|
|
// Now copy the file
|
|
// We assume that if we get called we want to make
|
|
// the copy even if the destination already exists.
|
|
|
|
file.CopyTo(destinationPath, true);
|
|
|
|
FileInfo result = new FileInfo(destinationPath);
|
|
WriteItemObject(result, destinationPath, false);
|
|
}
|
|
else
|
|
{
|
|
PerformCopyFileToRemoteSession(file, destinationPath, ps);
|
|
}
|
|
}
|
|
catch (System.UnauthorizedAccessException unAuthorizedAccessException)
|
|
{
|
|
if (force)
|
|
{
|
|
try
|
|
{
|
|
if (ps == null)
|
|
{
|
|
// If the destination exists and force is specified,
|
|
// mask of the readonly and hidden attributes and
|
|
// try again
|
|
|
|
FileInfo destinationItem = new FileInfo(destinationPath);
|
|
|
|
destinationItem.Attributes =
|
|
destinationItem.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden);
|
|
}
|
|
else
|
|
{
|
|
PerformCopyFileToRemoteSession(file, destinationPath, ps);
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
if ((exception is FileNotFoundException) ||
|
|
(exception is DirectoryNotFoundException) ||
|
|
(exception is System.Security.SecurityException) ||
|
|
(exception is ArgumentException) ||
|
|
(exception is IOException))
|
|
{
|
|
// Write out the original error since we failed to force the copy
|
|
WriteError(new ErrorRecord(unAuthorizedAccessException, "CopyFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
|
|
}
|
|
else
|
|
throw;
|
|
}
|
|
|
|
file.CopyTo(destinationPath, true);
|
|
|
|
FileInfo result = new FileInfo(destinationPath);
|
|
WriteItemObject(result, destinationPath, false);
|
|
} // force
|
|
else
|
|
{
|
|
WriteError(new ErrorRecord(unAuthorizedAccessException, "CopyFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
|
|
}
|
|
}
|
|
} // ShouldProcess
|
|
}// ExcludeFile
|
|
} // CopyFileInfoItem
|
|
|
|
private void CopyDirectoryFromRemoteSession(
|
|
string sourceDirectoryName,
|
|
string sourceDirectoryFullName,
|
|
string destination,
|
|
bool force,
|
|
bool recurse,
|
|
System.Management.Automation.PowerShell ps)
|
|
{
|
|
Dbg.Diagnostics.Assert((sourceDirectoryName != null && sourceDirectoryFullName != null), "The caller should verify directory.");
|
|
|
|
if (IsItemContainer(destination))
|
|
{
|
|
destination = MakePath(destination, sourceDirectoryName);
|
|
}
|
|
|
|
s_tracer.WriteLine("destination = {0}", destination);
|
|
|
|
// Confirm the copy with the user
|
|
string action = FileSystemProviderStrings.CopyItemActionDirectory;
|
|
string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, sourceDirectoryFullName, destination);
|
|
|
|
if (ShouldProcess(resource, action))
|
|
{
|
|
// Create destinationPath directory. This will fail if the directory already exists
|
|
// and Force is not selected.
|
|
CreateDirectory(destination, false);
|
|
|
|
// If failed to create directory
|
|
if (!Directory.Exists(destination))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (recurse)
|
|
{
|
|
// Get all the files for that directory from the remote session
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
|
|
ps.AddParameter("getPathDir", sourceDirectoryFullName);
|
|
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
if (op == null)
|
|
{
|
|
Exception e = new IOException(String.Format(
|
|
CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemotelyFailedToGetDirectoryChildItems,
|
|
sourceDirectoryFullName));
|
|
WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, sourceDirectoryFullName));
|
|
return;
|
|
}
|
|
|
|
if (op["Files"] != null)
|
|
{
|
|
PSObject obj = (PSObject)op["Files"];
|
|
ArrayList filesList = (ArrayList)obj.BaseObject;
|
|
|
|
foreach (PSObject fileObject in filesList)
|
|
{
|
|
Hashtable file = (Hashtable)fileObject.BaseObject;
|
|
string fileName = (string)file["FileName"];
|
|
string filePath = (string)file["FilePath"];
|
|
long fileSize = (long)file["FileSize"];
|
|
|
|
// Making sure to obey the StopProcessing.
|
|
if (Stopping)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool excludeFile = SessionStateUtilities.MatchesAnyWildcardPattern(fileName, _excludeMatcher, defaultValue: false);
|
|
|
|
if (!excludeFile)
|
|
{
|
|
// If an exception is thrown in the remote session, it is surface to the user via PowerShell Write-Error.
|
|
CopyFileFromRemoteSession(fileName,
|
|
filePath,
|
|
destination,
|
|
force,
|
|
ps,
|
|
fileSize);
|
|
}
|
|
}
|
|
} // for files
|
|
|
|
if (op["Directories"] != null)
|
|
{
|
|
PSObject obj = (PSObject)op["Directories"];
|
|
ArrayList directories = (ArrayList)obj.BaseObject;
|
|
|
|
foreach (PSObject dirObject in directories)
|
|
{
|
|
Hashtable dir = (Hashtable)dirObject.BaseObject;
|
|
string dirName = (string)dir["Name"];
|
|
string dirFullName = (string)dir["FullName"];
|
|
|
|
// Making sure to obey the StopProcessing.
|
|
if (Stopping)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If an exception is thrown in the remote session, it is surface to the user via PowerShell Write-Error.
|
|
CopyDirectoryFromRemoteSession(dirName,
|
|
dirFullName,
|
|
destination,
|
|
force,
|
|
recurse,
|
|
ps);
|
|
}
|
|
} // for directories
|
|
}
|
|
} // ShouldProcess
|
|
} // CopyDirectoryFromRemoteSession
|
|
|
|
private ArrayList GetRemoteSourceAlternateStreams(System.Management.Automation.PowerShell ps, string path)
|
|
{
|
|
ArrayList streams = null;
|
|
bool supportsAlternateStreams = false;
|
|
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
|
|
ps.AddParameter("supportAltStreamPath", path);
|
|
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
if (op != null && op["SourceSupportsAlternateStreams"] != null)
|
|
{
|
|
supportsAlternateStreams = (bool)op["SourceSupportsAlternateStreams"];
|
|
}
|
|
|
|
if (supportsAlternateStreams)
|
|
{
|
|
PSObject obj = (PSObject)op["Streams"];
|
|
streams = (ArrayList)obj.BaseObject;
|
|
}
|
|
|
|
return streams;
|
|
}
|
|
|
|
private void InitializeFunctionPSCopyFileFromRemoteSession(System.Management.Automation.PowerShell ps)
|
|
{
|
|
if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) { return; }
|
|
|
|
ps.AddScript(CopyFileRemoteUtils.AllCopyFromRemoteScripts);
|
|
SafeInvokeCommand.Invoke(ps, this, null, false);
|
|
}
|
|
|
|
private void RemoveFunctionsPSCopyFileFromRemoteSession(System.Management.Automation.PowerShell ps)
|
|
{
|
|
if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) { return; }
|
|
|
|
string remoteScript = @"
|
|
Microsoft.PowerShell.Management\Remove-Item function:PSCopyFromSessionHelper -ea SilentlyContinue -Force
|
|
Microsoft.PowerShell.Management\Remove-Item function:PSCopyRemoteUtils -ea SilentlyContinue -Force
|
|
";
|
|
ps.AddScript(remoteScript);
|
|
SafeInvokeCommand.Invoke(ps, this, null, false);
|
|
}
|
|
|
|
private bool ValidRemoteSessionForScripting(Runspace runspace)
|
|
{
|
|
if (!(runspace is RemoteRunspace)) { return false; }
|
|
|
|
PSLanguageMode languageMode = runspace.SessionStateProxy.LanguageMode;
|
|
if (languageMode == PSLanguageMode.ConstrainedLanguage || languageMode == PSLanguageMode.NoLanguage)
|
|
{
|
|
// SessionStateInternal.ValidateRemotePathAndGetRoot checked for expected helper functions on the
|
|
// restricted session and will have returned an error if they are missing. So at this point we
|
|
// assume the session is set up with the needed helper functions.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private Hashtable GetRemoteFileMetadata(string filePath, System.Management.Automation.PowerShell ps)
|
|
{
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
|
|
ps.AddParameter("getMetaFilePath", filePath);
|
|
Hashtable metadata = SafeInvokeCommand.Invoke(ps, this, null);
|
|
return metadata;
|
|
}
|
|
|
|
private void SetFileMetadata(string sourceFileFullName, FileInfo destinationFile, System.Management.Automation.PowerShell ps)
|
|
{
|
|
Hashtable metadata = GetRemoteFileMetadata(sourceFileFullName, ps);
|
|
|
|
if (metadata != null)
|
|
{
|
|
// LastWriteTime
|
|
if (metadata["LastWriteTimeUtc"] != null)
|
|
{
|
|
destinationFile.LastWriteTimeUtc = (DateTime)metadata["LastWriteTimeUtc"];
|
|
}
|
|
if (metadata["LastWriteTime"] != null)
|
|
{
|
|
destinationFile.LastWriteTime = (DateTime)metadata["LastWriteTime"];
|
|
}
|
|
|
|
// Attributes
|
|
if (metadata["Attributes"] != null)
|
|
{
|
|
PSObject obj = (PSObject)metadata["Attributes"];
|
|
foreach (string value in (ArrayList)obj.BaseObject)
|
|
{
|
|
if (String.Equals(value, "ReadOnly", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
destinationFile.Attributes = destinationFile.Attributes | FileAttributes.ReadOnly;
|
|
}
|
|
else if (String.Equals(value, "Hidden", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
destinationFile.Attributes = destinationFile.Attributes | FileAttributes.Hidden;
|
|
}
|
|
else if (String.Equals(value, "Archive", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
destinationFile.Attributes = destinationFile.Attributes | FileAttributes.Archive;
|
|
}
|
|
else if (String.Equals(value, "System", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
destinationFile.Attributes = destinationFile.Attributes | FileAttributes.System;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CopyFileFromRemoteSession(
|
|
string sourceFileName,
|
|
string sourceFileFullName,
|
|
string destinationPath,
|
|
bool force,
|
|
System.Management.Automation.PowerShell ps,
|
|
long fileSize = 0)
|
|
{
|
|
Dbg.Diagnostics.Assert(sourceFileFullName != null, "The caller should verify file.");
|
|
|
|
// If the destination is a container, add the file name
|
|
// to the destination path.
|
|
if (IsItemContainer(destinationPath))
|
|
{
|
|
destinationPath = MakePath(destinationPath, sourceFileName);
|
|
}
|
|
|
|
// Verify that the target doesn't represent a device name
|
|
if (PathIsReservedDeviceName(destinationPath, "CopyError"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
FileInfo destinationFile = new FileInfo(destinationPath);
|
|
|
|
string action = FileSystemProviderStrings.CopyItemActionFile;
|
|
string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, sourceFileFullName, destinationPath);
|
|
|
|
if (ShouldProcess(resource, action))
|
|
{
|
|
bool result = PerformCopyFileFromRemoteSession(sourceFileFullName, destinationFile, destinationPath, force, ps, fileSize, false, null);
|
|
|
|
// Copying the file from the remote session completed successfully
|
|
if (result)
|
|
{
|
|
// Check if the remote source file has any alternate data streams
|
|
ArrayList remoteFileStreams = GetRemoteSourceAlternateStreams(ps, sourceFileFullName);
|
|
if ((remoteFileStreams != null) && (remoteFileStreams.Count > 0))
|
|
{
|
|
foreach (string streamName in remoteFileStreams)
|
|
{
|
|
result = PerformCopyFileFromRemoteSession(sourceFileFullName, destinationFile, destinationPath, force, ps, fileSize, true, streamName);
|
|
if (!result)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The file was copied successfully. Now, set the file metadata
|
|
if (result)
|
|
{
|
|
SetFileMetadata(sourceFileFullName, destinationFile, ps);
|
|
}
|
|
} // ShouldProcess
|
|
}
|
|
|
|
private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInfo destinationFile, string destinationPath, bool force, System.Management.Automation.PowerShell ps,
|
|
long fileSize, bool isAlternateDataStream, string streamName)
|
|
{
|
|
bool success = false;
|
|
string activity = String.Format(CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemotelyProgressActivity,
|
|
sourceFileFullName,
|
|
destinationFile.FullName);
|
|
string statusDescription = String.Format(CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemotelyStatusDescription,
|
|
ps.Runspace.ConnectionInfo.ComputerName,
|
|
"localhost");
|
|
|
|
ProgressRecord progress = new ProgressRecord(0, activity, statusDescription);
|
|
progress.PercentComplete = 0;
|
|
progress.RecordType = ProgressRecordType.Processing;
|
|
WriteProgress(progress);
|
|
FileStream wStream = null;
|
|
bool errorWhileCopyRemoteFile = false;
|
|
|
|
try
|
|
{
|
|
// The main data stream
|
|
if (!isAlternateDataStream)
|
|
{
|
|
// If force is specified, and the file already exist at the destination, mask of the readonly, hidden, and system attributes
|
|
if (force && File.Exists(destinationFile.FullName))
|
|
{
|
|
destinationFile.Attributes = destinationFile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.System);
|
|
}
|
|
wStream = new FileStream(destinationFile.FullName, FileMode.Create);
|
|
}
|
|
|
|
#if !UNIX
|
|
// an alternate stream
|
|
else
|
|
{
|
|
wStream = AlternateDataStreamUtilities.CreateFileStream(destinationFile.FullName, streamName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
|
}
|
|
#endif
|
|
long fragmentSize = FILETRANSFERSIZE;
|
|
long copiedSoFar = 0;
|
|
long currentIndex = 0;
|
|
|
|
while (true)
|
|
{
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
|
|
ps.AddParameter("copyFromFilePath", sourceFileFullName);
|
|
ps.AddParameter("copyFromStart", currentIndex);
|
|
ps.AddParameter("copyFromNumBytes", fragmentSize);
|
|
if (force)
|
|
{
|
|
ps.AddParameter("force", true);
|
|
}
|
|
|
|
#if !UNIX
|
|
if (isAlternateDataStream)
|
|
{
|
|
ps.AddParameter("isAlternateStream", true);
|
|
ps.AddParameter("streamName", streamName);
|
|
}
|
|
#endif
|
|
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
|
|
// Check if there was an exception when reading the remote file.
|
|
if (op == null)
|
|
{
|
|
errorWhileCopyRemoteFile = true;
|
|
Exception e = new IOException(String.Format(CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemotelyFailedToReadFile,
|
|
sourceFileFullName));
|
|
WriteError(new ErrorRecord(e, "FailedToCopyFileFromRemoteSession", ErrorCategory.WriteError, sourceFileFullName));
|
|
break;
|
|
}
|
|
|
|
if (op["ExceptionThrown"] != null)
|
|
{
|
|
bool failedToReadFile = (bool)(op["ExceptionThrown"]);
|
|
if (failedToReadFile)
|
|
{
|
|
// The error is written to the error array via SafeInvokeCommand
|
|
errorWhileCopyRemoteFile = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// To accomodate empty files
|
|
String content = string.Empty;
|
|
if (op["b64Fragment"] != null)
|
|
{
|
|
content = (String)op["b64Fragment"];
|
|
}
|
|
bool more = (bool)op["moreAvailable"];
|
|
currentIndex += fragmentSize;
|
|
byte[] bytes = System.Convert.FromBase64String(content);
|
|
wStream.Write(bytes, 0, bytes.Length);
|
|
copiedSoFar += bytes.Length;
|
|
|
|
if (wStream.Length > 0)
|
|
{
|
|
int percentage = (int)(copiedSoFar * 100 / wStream.Length);
|
|
progress.PercentComplete = percentage;
|
|
WriteProgress(progress);
|
|
}
|
|
|
|
if (!more)
|
|
{
|
|
success = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
progress.PercentComplete = 100;
|
|
progress.RecordType = ProgressRecordType.Completed;
|
|
WriteProgress(progress);
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
// IOException takes care of FileNotFoundException, DirectoryNotFoundException, and PathTooLongException
|
|
WriteError(new ErrorRecord(ioException, "CopyItemRemotelyIOError", ErrorCategory.WriteError, sourceFileFullName));
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "CopyItemRemotelyArgumentError", ErrorCategory.WriteError, sourceFileFullName));
|
|
}
|
|
catch (NotSupportedException notSupportedException)
|
|
{
|
|
WriteError(new ErrorRecord(notSupportedException, "CopyFileInfoRemotelyPathRefersToANonFileDevice", ErrorCategory.InvalidArgument, sourceFileFullName));
|
|
}
|
|
catch (SecurityException securityException)
|
|
{
|
|
WriteError(new ErrorRecord(securityException, "CopyFileInfoRemotelyUnauthorizedAccessError", ErrorCategory.PermissionDenied, sourceFileFullName));
|
|
}
|
|
catch (UnauthorizedAccessException unauthorizedAccessException)
|
|
{
|
|
WriteError(new ErrorRecord(unauthorizedAccessException, "CopyFileInfoItemRemotelyUnauthorizedAccessError", ErrorCategory.PermissionDenied, sourceFileFullName));
|
|
}
|
|
finally
|
|
{
|
|
if (wStream != null)
|
|
{
|
|
wStream.Dispose();
|
|
}
|
|
|
|
// If copying the file from the remote session failed, then remove it.
|
|
if (errorWhileCopyRemoteFile && File.Exists(destinationFile.FullName))
|
|
{
|
|
if (!(destinationFile.Attributes.HasFlag(FileAttributes.ReadOnly) ||
|
|
destinationFile.Attributes.HasFlag(FileAttributes.Hidden) ||
|
|
destinationFile.Attributes.HasFlag(FileAttributes.System)))
|
|
{
|
|
RemoveFileSystemItem(destinationFile, true);
|
|
}
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
private void InitializeFunctionsPSCopyFileToRemoteSession(System.Management.Automation.PowerShell ps)
|
|
{
|
|
if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) { return; }
|
|
|
|
ps.AddScript(CopyFileRemoteUtils.AllCopyToRemoteScripts);
|
|
SafeInvokeCommand.Invoke(ps, this, null, false);
|
|
}
|
|
|
|
private void RemoveFunctionPSCopyFileToRemoteSession(System.Management.Automation.PowerShell ps)
|
|
{
|
|
if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) { return; }
|
|
|
|
string remoteScript = @"
|
|
Microsoft.PowerShell.Management\Remove-Item function:PSCopyToSessionHelper -ea SilentlyContinue -Force
|
|
Microsoft.PowerShell.Management\Remove-Item function:PSCopyRemoteUtils -ea SilentlyContinue -Force
|
|
";
|
|
ps.AddScript(remoteScript);
|
|
SafeInvokeCommand.Invoke(ps, this, null, false);
|
|
}
|
|
|
|
// If the target supports alternate data streams the following must be true:
|
|
// 1) The remote session must be PowerShell V3 or higher to support Streams
|
|
// 2) The target drive must be NTFS
|
|
//
|
|
private bool RemoteTargetSupportsAlternateStreams(System.Management.Automation.PowerShell ps, string path)
|
|
{
|
|
bool supportsAlternateStreams = false;
|
|
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
|
|
ps.AddParameter("supportAltStreamPath", path);
|
|
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
if (op != null && op["TargetSupportsAlternateStreams"] != null)
|
|
{
|
|
supportsAlternateStreams = (bool)op["TargetSupportsAlternateStreams"];
|
|
}
|
|
|
|
return supportsAlternateStreams;
|
|
}
|
|
|
|
// Validate that the given remotePath exists, and do the following:
|
|
// 1) If the remotePath is a FileInfo, then just return the remotePath.
|
|
// 2) If the remotePath is a DirectoryInfo, then return the remotePath + the given filename.
|
|
// 3) If the remote path does not exist, but its parent does, and it is a DirectoryInfo, then return the remotePath.
|
|
// 4) If the remotePath or its parent do not exist, return null.
|
|
private string MakeRemotePath(System.Management.Automation.PowerShell ps, string remotePath, string filename)
|
|
{
|
|
bool isFileInfo = false;
|
|
bool isDirectoryInfo = false;
|
|
bool parentIsDirectoryInfo = false;
|
|
string path = null;
|
|
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
|
|
ps.AddParameter("remotePath", remotePath);
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
|
|
if (op != null)
|
|
{
|
|
if (op["IsDirectoryInfo"] != null)
|
|
{
|
|
isDirectoryInfo = (bool)op["IsDirectoryInfo"];
|
|
}
|
|
if (op["IsFileInfo"] != null)
|
|
{
|
|
isFileInfo = (bool)op["IsFileInfo"];
|
|
}
|
|
if (op["ParentIsDirectoryInfo"] != null)
|
|
{
|
|
parentIsDirectoryInfo = (bool)op["ParentIsDirectoryInfo"];
|
|
}
|
|
}
|
|
|
|
if (isFileInfo)
|
|
{
|
|
// The destination is a file, so we are going to overwrite it.
|
|
path = remotePath;
|
|
}
|
|
else if (isDirectoryInfo)
|
|
{
|
|
// The destination is a directory, so append the file name to the path.
|
|
path = Path.Combine(remotePath, filename);
|
|
}
|
|
else if (parentIsDirectoryInfo)
|
|
{
|
|
// At this point we know that the remotePath is neither a file or a directory on the remote target.
|
|
// However, if the parent of the remotePath exists, then we are doing a copy-item operation in which
|
|
// the destination file name is already being passed, e.g.,
|
|
// copy-item -path c:\localDir\foo.txt -destination d:\remoteDir\bar.txt -toSession $s
|
|
// Note that d:\remoteDir is a directory that exists on the remote target machine.
|
|
path = remotePath;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
private bool RemoteDirectoryExist(System.Management.Automation.PowerShell ps, string path)
|
|
{
|
|
bool pathExists = false;
|
|
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyRemoteUtilsName);
|
|
ps.AddParameter("dirPathExists", path);
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
|
|
if (op != null)
|
|
{
|
|
if (op["Exists"] != null)
|
|
pathExists = (bool)op["Exists"];
|
|
}
|
|
|
|
return pathExists;
|
|
}
|
|
|
|
private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath, System.Management.Automation.PowerShell ps, bool isAlternateStream, string streamName)
|
|
{
|
|
string activity = String.Format(CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemotelyProgressActivity,
|
|
file.FullName,
|
|
destinationPath);
|
|
string statusDescription = String.Format(CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemotelyStatusDescription,
|
|
"localhost",
|
|
ps.Runspace.ConnectionInfo.ComputerName);
|
|
|
|
ProgressRecord progress = new ProgressRecord(0, activity, statusDescription);
|
|
progress.PercentComplete = 0;
|
|
progress.RecordType = ProgressRecordType.Processing;
|
|
WriteProgress(progress);
|
|
|
|
// 4MB gives the best results without spiking the resources on the remote connection.
|
|
int fragmentSize = FILETRANSFERSIZE;
|
|
byte[] fragment = null;
|
|
int iteration = 0;
|
|
bool success = false;
|
|
|
|
FileStream fStream = null;
|
|
try
|
|
{
|
|
// Main data stream
|
|
if (!isAlternateStream)
|
|
{
|
|
fStream = File.OpenRead(file.FullName);
|
|
}
|
|
#if !UNIX
|
|
else
|
|
{
|
|
fStream = AlternateDataStreamUtilities.CreateFileStream(file.FullName, streamName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
}
|
|
#endif
|
|
long remainingFileSize = fStream.Length;
|
|
do
|
|
{
|
|
if (Stopping)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
iteration++;
|
|
int toRead = fragmentSize;
|
|
if (toRead > remainingFileSize)
|
|
{
|
|
toRead = (int)remainingFileSize;
|
|
}
|
|
if (fragment == null)
|
|
{
|
|
fragment = new byte[toRead];
|
|
}
|
|
else if (toRead < fragmentSize)
|
|
{
|
|
fragment = new byte[toRead];
|
|
}
|
|
|
|
int readSoFar = 0;
|
|
while (readSoFar < toRead)
|
|
{
|
|
readSoFar += fStream.Read(fragment, 0, toRead);
|
|
}
|
|
remainingFileSize -= readSoFar;
|
|
|
|
string b64Fragment = System.Convert.ToBase64String(fragment);
|
|
|
|
// Main data stream
|
|
if (!isAlternateStream)
|
|
{
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
|
|
ps.AddParameter("copyToFilePath", destinationPath);
|
|
ps.AddParameter("createFile", (iteration == 1));
|
|
|
|
if ((iteration == 1) && (b64Fragment.Length == 0))
|
|
{
|
|
// This fixes the case in which the user tries to copy an empty file between sessions.
|
|
// Scenario 1: The user creates an empty file using the Out-File cmdlet.
|
|
// In this case the file length is 6.
|
|
// "" | out-file test.txt
|
|
// Scenario 2: The user generates an empty file using the New-Item cmdlet.
|
|
// In this case the file length is 0.
|
|
// New-Item -Path test.txt -Type File
|
|
// Because of this, when we create the file on the remote session, we need to check
|
|
// the length of b64Fragment to figure out if we are creating an empty file.
|
|
ps.AddParameter("emptyFile", true);
|
|
}
|
|
else
|
|
{
|
|
ps.AddParameter("b64Fragment", b64Fragment);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
|
|
ps.AddParameter("copyToFilePath", destinationPath);
|
|
ps.AddParameter("b64Fragment", b64Fragment);
|
|
ps.AddParameter("streamName", streamName);
|
|
}
|
|
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
if (op == null || op["BytesWritten"] == null)
|
|
{
|
|
//write error to stream
|
|
Exception e = new IOException(String.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailed, file));
|
|
WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, file.FullName));
|
|
return false;
|
|
}
|
|
|
|
if ((int)(op["BytesWritten"]) != toRead)
|
|
{
|
|
Exception e = new IOException(String.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailed, file));
|
|
WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, file.FullName));
|
|
return false;
|
|
}
|
|
|
|
if (fStream.Length > 0)
|
|
{
|
|
int percentage = (int)((fStream.Length - remainingFileSize) * 100 / fStream.Length);
|
|
progress.PercentComplete = percentage;
|
|
WriteProgress(progress);
|
|
}
|
|
} while (remainingFileSize > 0);
|
|
progress.PercentComplete = 100;
|
|
progress.RecordType = ProgressRecordType.Completed;
|
|
WriteProgress(progress);
|
|
success = true;
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
// IOException takes care of FileNotFoundException, DirectoryNotFoundException, and PathTooLongException
|
|
WriteError(new ErrorRecord(ioException, "CopyItemRemotelyIOError", ErrorCategory.WriteError, file.FullName));
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "CopyItemRemotelyArgumentError", ErrorCategory.WriteError, file.FullName));
|
|
}
|
|
catch (NotSupportedException notSupportedException)
|
|
{
|
|
WriteError(new ErrorRecord(notSupportedException, "CopyFileInfoRemotelyPathRefersToANonFileDevice", ErrorCategory.InvalidArgument, file.FullName));
|
|
}
|
|
catch (SecurityException securityException)
|
|
{
|
|
WriteError(new ErrorRecord(securityException, "CopyFileInfoRemotelyUnauthorizedAccessError", ErrorCategory.PermissionDenied, file.FullName));
|
|
}
|
|
finally
|
|
{
|
|
if (fStream != null)
|
|
{
|
|
fStream.Dispose();
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
// Returns a hash table with metadata about this file info.
|
|
//
|
|
private Hashtable GetFileMetadata(FileInfo file)
|
|
{
|
|
Hashtable metadata = new Hashtable();
|
|
|
|
// LastWriteTime
|
|
metadata.Add("LastWriteTime", file.LastWriteTime);
|
|
metadata.Add("LastWriteTimeUtc", file.LastWriteTimeUtc);
|
|
|
|
// File attributes
|
|
metadata.Add("Attributes", file.Attributes);
|
|
|
|
return metadata;
|
|
}
|
|
|
|
private void SetRemoteFileMetadata(FileInfo file, string remoteFilePath, System.Management.Automation.PowerShell ps)
|
|
{
|
|
Hashtable metadata = GetFileMetadata(file);
|
|
if (metadata != null)
|
|
{
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
|
|
ps.AddParameter("metaDataFilePath", remoteFilePath);
|
|
ps.AddParameter("metaDataToSet", metadata);
|
|
SafeInvokeCommand.Invoke(ps, this, null, false);
|
|
}
|
|
}
|
|
|
|
private bool PerformCopyFileToRemoteSession(FileInfo file, string destinationPath, System.Management.Automation.PowerShell ps)
|
|
{
|
|
// Make the remote path
|
|
var remoteFilePath = MakeRemotePath(ps, destinationPath, file.Name);
|
|
|
|
if (String.IsNullOrEmpty(remoteFilePath))
|
|
{
|
|
Exception e = new ArgumentException(String.Format(CultureInfo.InvariantCulture, SessionStateStrings.PathNotFound, destinationPath));
|
|
WriteError(new ErrorRecord(e, "RemotePathNotFound", ErrorCategory.WriteError, destinationPath));
|
|
return false;
|
|
}
|
|
|
|
bool result = CopyFileStreamToRemoteSession(file, remoteFilePath, ps, false, null);
|
|
|
|
#if !UNIX
|
|
bool targetSupportsAlternateStreams = RemoteTargetSupportsAlternateStreams(ps, remoteFilePath);
|
|
|
|
// Once the file is copied successfully, check if the file has any alternate data streams
|
|
if (result && targetSupportsAlternateStreams)
|
|
{
|
|
foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(file.FullName))
|
|
{
|
|
if (!(String.Equals(":$DATA", stream.Stream, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
result = CopyFileStreamToRemoteSession(file, remoteFilePath, ps, true, stream.Stream);
|
|
if (!result)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (result)
|
|
{
|
|
SetRemoteFileMetadata(file, Path.Combine(destinationPath, file.Name), ps);
|
|
}
|
|
|
|
return result;
|
|
} // PerformCopyFileToRemoteSession
|
|
|
|
private bool RemoteDestinationPathIsFile(string destination, System.Management.Automation.PowerShell ps)
|
|
{
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
|
|
ps.AddParameter("isFilePath", destination);
|
|
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
|
|
if (op == null || op["IsFileInfo"] == null)
|
|
{
|
|
Exception e = new IOException(String.Format(
|
|
CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemotelyFailedToValidateIfDestinationIsFile,
|
|
destination));
|
|
WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destination));
|
|
return false;
|
|
}
|
|
|
|
return (bool)(op["IsFileInfo"]);
|
|
}
|
|
|
|
private string CreateDirectoryOnRemoteSession(string destination, bool force, System.Management.Automation.PowerShell ps)
|
|
{
|
|
ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
|
|
ps.AddParameter("createDirectoryPath", destination);
|
|
if (force)
|
|
{
|
|
ps.AddParameter("force", true);
|
|
}
|
|
|
|
Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
|
|
|
|
// If op == null, SafeInvokeCommand.Invoke throwns an error.
|
|
if (op["ExceptionThrown"] != null)
|
|
{
|
|
// If an error is thrown on the remote session, it is written via SafeInvokeCommand.Invoke.
|
|
if ((bool)op["ExceptionThrown"])
|
|
return null;
|
|
}
|
|
|
|
if (force && (op["DirectoryPath"] == null))
|
|
{
|
|
Exception e = new IOException(String.Format(CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.CopyItemRemotelyFailedToCreateDirectory,
|
|
destination));
|
|
WriteError(new ErrorRecord(e, "FailedToCreateDirectory", ErrorCategory.WriteError, destination));
|
|
return null;
|
|
}
|
|
string path = (String)(op["DirectoryPath"]);
|
|
|
|
if ((!force) && (bool)op["PathExists"])
|
|
{
|
|
Exception e = new IOException(StringUtil.Format(FileSystemProviderStrings.DirectoryExist, path));
|
|
WriteError(new ErrorRecord(e, "DirectoryExist", ErrorCategory.ResourceExists, path));
|
|
return null;
|
|
}
|
|
|
|
return path;
|
|
} // CreateDirectoryOnRemoteSession
|
|
|
|
// Returns true if the destination path represents a device name, and write an error to the user.
|
|
private bool PathIsReservedDeviceName(string destinationPath, string errorId)
|
|
{
|
|
bool pathIsReservedDeviceName = false;
|
|
if (Utils.IsReservedDeviceName(destinationPath))
|
|
{
|
|
pathIsReservedDeviceName = true;
|
|
String error = StringUtil.Format(FileSystemProviderStrings.TargetCannotContainDeviceName, destinationPath);
|
|
Exception e = new IOException(error);
|
|
WriteError(new ErrorRecord(e, errorId, ErrorCategory.WriteError, destinationPath));
|
|
}
|
|
|
|
return pathIsReservedDeviceName;
|
|
}
|
|
|
|
#endregion CopyItem
|
|
|
|
#endregion ContainerCmdletProvider members
|
|
|
|
#region NavigationCmdletProvider members
|
|
|
|
/// <summary>
|
|
/// Gets the parent of the given path.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path of which to get the parent.
|
|
/// </param>
|
|
/// <param name="root">
|
|
/// The root of the drive.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The parent of the given path.
|
|
/// </returns>
|
|
protected override string GetParentPath(string path, string root)
|
|
{
|
|
string parentPath = base.GetParentPath(path, root);
|
|
if (!IsUNCPath(path))
|
|
{
|
|
parentPath = EnsureDriveIsRooted(parentPath);
|
|
}
|
|
#if !UNIX
|
|
else if (parentPath.Equals(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal))
|
|
{
|
|
// make sure we return two backslashes so it still results in a UNC path
|
|
parentPath = "\\\\";
|
|
}
|
|
#endif
|
|
return parentPath;
|
|
} // GetParentPath
|
|
|
|
// Note: we don't use IO.Path.IsPathRooted as this deals with "invalid" i.e. unnormalized paths
|
|
private static bool IsAbsolutePath(string path)
|
|
{
|
|
bool result = false;
|
|
|
|
// check if we're on a single root filesystem and it's an absolute path
|
|
if (LocationGlobber.IsSingleFileSystemAbsolutePath(path))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Find the drive separator
|
|
int index = path.IndexOf(':');
|
|
|
|
if (index != -1)
|
|
{
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static bool IsUNCPath(string path)
|
|
{
|
|
return path.StartsWith("\\\\", StringComparison.Ordinal);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the specified path is a root of a UNC share
|
|
/// by counting the path separators "\" following "\\". If only
|
|
/// one path separator is found we know the path is in the form
|
|
/// "\\server\share" and is a valid UNC root.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path to check to see if its a UNC root.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the path is a UNC root, or false otherwise.
|
|
/// </returns>
|
|
private static bool IsUNCRoot(string path)
|
|
{
|
|
bool result = false;
|
|
|
|
if (!String.IsNullOrEmpty(path))
|
|
{
|
|
if (IsUNCPath(path))
|
|
{
|
|
int lastIndex = path.Length - 1;
|
|
|
|
if (path[path.Length - 1] == '\\')
|
|
{
|
|
lastIndex--;
|
|
}
|
|
|
|
int separatorsFound = 0;
|
|
do
|
|
{
|
|
lastIndex = path.LastIndexOf('\\', lastIndex);
|
|
if (lastIndex == -1)
|
|
{
|
|
break;
|
|
}
|
|
--lastIndex;
|
|
if (lastIndex < 3)
|
|
{
|
|
break;
|
|
}
|
|
++separatorsFound;
|
|
} while (lastIndex > 3);
|
|
|
|
if (separatorsFound == 1)
|
|
{
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the specified path is either a drive root or a UNC root
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the path is either a drive root or a UNC root, or false otherwise.
|
|
/// </returns>
|
|
private static bool IsPathRoot(string path)
|
|
{
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool isDriveRoot = String.Equals(path, Path.GetPathRoot(path), StringComparison.OrdinalIgnoreCase);
|
|
bool isUNCRoot = IsUNCRoot(path);
|
|
bool result = isDriveRoot || isUNCRoot;
|
|
s_tracer.WriteLine("result = {0}; isDriveRoot = {1}; isUNCRoot = {2}", result, isDriveRoot, isUNCRoot);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Normalizes the path that was passed in and returns it as a normalized
|
|
/// path relative to the given basePath.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// A fully qualifiedpath to an item. The item must exist,
|
|
/// or the provider writes out an error.
|
|
/// </param>
|
|
/// <param name="basePath">
|
|
/// The path that the normalized path should be relative to.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A normalized path, relative to the given basePath.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
protected override string NormalizeRelativePath(
|
|
string path,
|
|
string basePath)
|
|
{
|
|
if (String.IsNullOrEmpty(path) || !IsValidPath(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
if (basePath == null)
|
|
{
|
|
basePath = String.Empty;
|
|
}
|
|
|
|
s_tracer.WriteLine("basePath = {0}", basePath);
|
|
|
|
string result = path;
|
|
|
|
do // false loop
|
|
{
|
|
path = NormalizePath(path);
|
|
path = EnsureDriveIsRooted(path);
|
|
|
|
// If it's not fully normalized, normalize it.
|
|
path = NormalizeRelativePathHelper(path, basePath);
|
|
|
|
basePath = NormalizePath(basePath);
|
|
basePath = EnsureDriveIsRooted(basePath);
|
|
|
|
result = path;
|
|
if (String.IsNullOrEmpty(result))
|
|
{
|
|
break;
|
|
}
|
|
|
|
try
|
|
{
|
|
string originalPathComparison = path;
|
|
if (!originalPathComparison.EndsWith(StringLiterals.DefaultPathSeparator))
|
|
{
|
|
originalPathComparison += StringLiterals.DefaultPathSeparator;
|
|
}
|
|
|
|
string basePathComparison = basePath;
|
|
if (!basePathComparison.EndsWith(StringLiterals.DefaultPathSeparator))
|
|
{
|
|
basePathComparison += StringLiterals.DefaultPathSeparator;
|
|
}
|
|
|
|
if (originalPathComparison.StartsWith(basePathComparison, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
bool isUNCPath = IsUNCPath(result);
|
|
if (!isUNCPath)
|
|
{
|
|
// Add the base path back on so that it can be used for
|
|
// processing
|
|
if (!result.StartsWith(basePath, StringComparison.CurrentCulture))
|
|
{
|
|
result = MakePath(basePath, result);
|
|
}
|
|
}
|
|
|
|
if (IsPathRoot(result))
|
|
{
|
|
result = EnsureDriveIsRooted(result);
|
|
}
|
|
else
|
|
{
|
|
// Now ensure that we have the proper casing by
|
|
// getting the names of the files and directories that match
|
|
|
|
string directoryPath = GetParentPath(result, String.Empty);
|
|
|
|
if (String.IsNullOrEmpty(directoryPath))
|
|
{
|
|
return String.Empty;
|
|
}
|
|
|
|
#if UNIX
|
|
// We don't use the Directory.EnumerateFiles() for Unix because the path
|
|
// may contain additional globbing patterns such as '[ab]'
|
|
// which Directory.EnumerateFiles() processes, giving undesireable
|
|
// results in this context.
|
|
if (!File.Exists(result) && !Directory.Exists(result))
|
|
{
|
|
String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
|
|
Exception e = new IOException(error);
|
|
WriteError(new ErrorRecord(
|
|
e,
|
|
"ItemDoesNotExist",
|
|
ErrorCategory.ObjectNotFound,
|
|
path));
|
|
break;
|
|
}
|
|
#else
|
|
string leafName = GetChildName(result);
|
|
|
|
// Use the Directory class to get the real path (this will
|
|
// ensure the proper casing
|
|
|
|
IEnumerable<string> files = Directory.EnumerateFiles(directoryPath, leafName);
|
|
|
|
if (files == null || !files.Any())
|
|
{
|
|
files = Directory.EnumerateDirectories(directoryPath, leafName);
|
|
}
|
|
|
|
if (files == null || !files.Any())
|
|
{
|
|
String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
|
|
Exception e = new IOException(error);
|
|
WriteError(new ErrorRecord(
|
|
e,
|
|
"ItemDoesNotExist",
|
|
ErrorCategory.ObjectNotFound,
|
|
path));
|
|
break;
|
|
}
|
|
|
|
result = files.First();
|
|
#endif
|
|
|
|
if (result.StartsWith(basePath, StringComparison.CurrentCulture))
|
|
{
|
|
result = result.Substring(basePath.Length);
|
|
}
|
|
else
|
|
{
|
|
String error = StringUtil.Format(FileSystemProviderStrings.PathOutSideBasePath, path);
|
|
Exception e =
|
|
new ArgumentException(error);
|
|
WriteError(new ErrorRecord(
|
|
e,
|
|
"PathOutSideBasePath",
|
|
ErrorCategory.InvalidArgument,
|
|
null));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (ArgumentException argumentException)
|
|
{
|
|
WriteError(new ErrorRecord(argumentException, "NormalizeRelativePathArgumentError", ErrorCategory.InvalidArgument, path));
|
|
break;
|
|
}
|
|
catch (DirectoryNotFoundException directoryNotFound)
|
|
{
|
|
WriteError(new ErrorRecord(directoryNotFound, "NormalizeRelativePathDirectoryNotFoundError", ErrorCategory.ObjectNotFound, path));
|
|
break;
|
|
}
|
|
catch (IOException ioError)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioError, "NormalizeRelativePathIOError", ErrorCategory.ReadError, path));
|
|
break;
|
|
}
|
|
catch (UnauthorizedAccessException accessException)
|
|
{
|
|
WriteError(new ErrorRecord(accessException, "NormalizeRelativePathUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
|
|
break;
|
|
}
|
|
} while (false);
|
|
|
|
return result;
|
|
} // NormalizeRelativePath
|
|
|
|
/// <summary>
|
|
/// Normalizes the path that was passed in and returns the normalized path
|
|
/// as a relative path to the basePath that was passed.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// A fully qualified provider specific path to an item. The item should exist
|
|
/// or the provider should write out an error.
|
|
/// </param>
|
|
/// <param name="basePath">
|
|
/// The path that the return value should be relative to.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A normalized path that is relative to the basePath that was passed. The
|
|
/// provider should parse the path parameter, normalize the path, and then
|
|
/// return the normalized path relative to the basePath.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// This method does not have to be purely syntactical parsing of the path. It
|
|
/// is encouraged that the provider actually use the path to lookup in its store
|
|
/// and create a relative path that matches the casing, and standardized path syntax.
|
|
///
|
|
/// Note, the base class implementation uses GetParentPath, GetChildName, and MakePath
|
|
/// to normalize the path and then make it relative to basePath. All string comparisons
|
|
/// are done using StringComparison.InvariantCultureIgnoreCase.
|
|
/// </remarks>
|
|
private string NormalizeRelativePathHelper(string path, string basePath)
|
|
{
|
|
if (path == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("path");
|
|
}
|
|
|
|
if (path.Length == 0)
|
|
{
|
|
return String.Empty;
|
|
}
|
|
|
|
if (basePath == null)
|
|
{
|
|
basePath = String.Empty;
|
|
}
|
|
|
|
s_tracer.WriteLine("basePath = {0}", basePath);
|
|
|
|
#if !UNIX
|
|
// Remove alternate data stream references
|
|
// See if they've used the inline stream syntax. They have more than one colon.
|
|
string alternateDataStream = String.Empty;
|
|
int firstColon = path.IndexOf(':');
|
|
int secondColon = path.IndexOf(':', firstColon + 1);
|
|
if (secondColon > 0)
|
|
{
|
|
string newPath = path.Substring(0, secondColon);
|
|
alternateDataStream = path.Replace(newPath, string.Empty);
|
|
path = newPath;
|
|
}
|
|
#endif
|
|
|
|
string result = path;
|
|
|
|
do // false loop
|
|
{
|
|
// Convert to the correct path separators and trim trailing separators
|
|
path = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
|
|
string originalPath = path;
|
|
|
|
path = path.TrimEnd(StringLiterals.DefaultPathSeparator);
|
|
basePath = basePath.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
|
|
basePath = basePath.TrimEnd(StringLiterals.DefaultPathSeparator);
|
|
|
|
path = RemoveRelativeTokens(path);
|
|
|
|
// See if the base and the path are already the same. We resolve this to
|
|
// ..\Leaf, since resolving "." to "." doesn't offer much information.
|
|
if (String.Equals(path, basePath, StringComparison.OrdinalIgnoreCase) &&
|
|
(!originalPath.EndsWith(StringLiterals.DefaultPathSeparator)))
|
|
{
|
|
string childName = GetChildName(path);
|
|
result = MakePath("..", childName);
|
|
break;
|
|
}
|
|
|
|
Stack<string> tokenizedPathStack = null;
|
|
|
|
// If the base path isn't really a base, then we resolve to a parent
|
|
// path (such as ../../foo)
|
|
// For example: base = c:/temp/bar/baz
|
|
// path = c:/temp/foo
|
|
if ((
|
|
!(path + StringLiterals.DefaultPathSeparator).StartsWith(
|
|
basePath + StringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase)) &&
|
|
(!String.IsNullOrEmpty(basePath))
|
|
)
|
|
{
|
|
result = String.Empty;
|
|
string commonBase = GetCommonBase(path, basePath);
|
|
|
|
Stack<string> parentNavigationStack = TokenizePathToStack(basePath, commonBase);
|
|
int parentPopCount = parentNavigationStack.Count;
|
|
|
|
if (String.IsNullOrEmpty(commonBase))
|
|
{
|
|
parentPopCount--;
|
|
}
|
|
|
|
for (int leafCounter = 0; leafCounter < parentPopCount; leafCounter++)
|
|
{
|
|
result = MakePath("..", result);
|
|
}
|
|
|
|
// This is true if we get passed a base path like:
|
|
// c:\directory1\directory2
|
|
// and an actual path of
|
|
// c:\directory1
|
|
// Which happens when the user is in c:\directory1\directory2
|
|
// and wants to resolve something like:
|
|
// ..\..\dir*
|
|
// In that case (as above,) we keep the ..\..\directory1
|
|
// instead of ".." as would usually be returned
|
|
if (!String.IsNullOrEmpty(commonBase))
|
|
{
|
|
if (String.Equals(path, commonBase, StringComparison.OrdinalIgnoreCase) &&
|
|
(!path.EndsWith(StringLiterals.DefaultPathSeparator)))
|
|
{
|
|
string childName = GetChildName(path);
|
|
result = MakePath("..", result);
|
|
result = MakePath(result, childName);
|
|
}
|
|
else
|
|
{
|
|
string[] childNavigationItems = TokenizePathToStack(path, commonBase).ToArray();
|
|
|
|
for (int leafCounter = 0; leafCounter < childNavigationItems.Length; leafCounter++)
|
|
{
|
|
result = MakePath(result, childNavigationItems[leafCounter]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Otherwise, we resolve to a child path (such as foo/bar)
|
|
else
|
|
{
|
|
// If the path is a root, then the result will either be empty or the root depending
|
|
// on the value of basePath.
|
|
if (IsPathRoot(path))
|
|
{
|
|
if (String.IsNullOrEmpty(basePath))
|
|
{
|
|
result = path;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
result = String.Empty;
|
|
break;
|
|
}
|
|
}
|
|
|
|
tokenizedPathStack = TokenizePathToStack(path, basePath);
|
|
|
|
// Now we have to normalize the path
|
|
// by processing each token on the stack
|
|
Stack<string> normalizedPathStack;
|
|
|
|
try
|
|
{
|
|
normalizedPathStack = NormalizeThePath(basePath, tokenizedPathStack);
|
|
}
|
|
catch (ArgumentException argumentException)
|
|
{
|
|
WriteError(new ErrorRecord(argumentException, "NormalizeRelativePathHelperArgumentError", ErrorCategory.InvalidArgument, null));
|
|
result = null;
|
|
break;
|
|
}
|
|
|
|
// Now that the path has been normalized, create the relative path
|
|
result = CreateNormalizedRelativePathFromStack(normalizedPathStack);
|
|
}
|
|
} while (false);
|
|
|
|
#if !UNIX
|
|
if (!String.IsNullOrEmpty(alternateDataStream))
|
|
{
|
|
result = result + alternateDataStream;
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
} // NormalizeRelativePathHelper
|
|
|
|
private string RemoveRelativeTokens(string path)
|
|
{
|
|
string testPath = path.Replace('/', '\\');
|
|
if (
|
|
(testPath.IndexOf("\\", StringComparison.OrdinalIgnoreCase) < 0) ||
|
|
testPath.StartsWith(".\\", StringComparison.OrdinalIgnoreCase) ||
|
|
testPath.StartsWith("..\\", StringComparison.OrdinalIgnoreCase) ||
|
|
testPath.EndsWith("\\.", StringComparison.OrdinalIgnoreCase) ||
|
|
testPath.EndsWith("\\..", StringComparison.OrdinalIgnoreCase) ||
|
|
(testPath.IndexOf("\\.\\", StringComparison.OrdinalIgnoreCase) > 0) ||
|
|
(testPath.IndexOf("\\..\\", StringComparison.OrdinalIgnoreCase) > 0))
|
|
{
|
|
try
|
|
{
|
|
Stack<string> tokenizedPathStack = TokenizePathToStack(path, string.Empty);
|
|
Stack<string> normalizedPath = NormalizeThePath(string.Empty, tokenizedPathStack);
|
|
return CreateNormalizedRelativePathFromStack(normalizedPath);
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
// Catch any errors here, as we may be in an AppContainer
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the common base path of two paths
|
|
/// </summary>
|
|
/// <param name="path1">One path</param>
|
|
/// <param name="path2">Another path</param>
|
|
private string GetCommonBase(string path1, string path2)
|
|
{
|
|
// Always see if the shorter path is a substring of the
|
|
// longer path. If it is not, take the child off of the longer
|
|
// path and compare again.
|
|
|
|
while (!String.Equals(path1, path2, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (path2.Length > path1.Length)
|
|
{
|
|
path2 = GetParentPath(path2, null);
|
|
}
|
|
else
|
|
{
|
|
path1 = GetParentPath(path1, null);
|
|
}
|
|
}
|
|
|
|
return path1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tokenizes the specified path onto a stack
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path to tokenize.
|
|
/// </param>
|
|
/// <param name="basePath">
|
|
/// The base part of the path that should not be tokenized.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A stack containing the tokenized path with leaf elements on the bottom
|
|
/// of the stack and the most ancestral parent at the top.
|
|
/// </returns>
|
|
private Stack<string> TokenizePathToStack(string path, string basePath)
|
|
{
|
|
Stack<string> tokenizedPathStack = new Stack<string>();
|
|
|
|
string tempPath = path;
|
|
string previousParent = path;
|
|
|
|
while (tempPath.Length > basePath.Length)
|
|
{
|
|
// Get the child name and push it onto the stack
|
|
// if its valid
|
|
|
|
string childName = GetChildName(tempPath);
|
|
if (String.IsNullOrEmpty(childName))
|
|
{
|
|
// Push the parent on and then stop
|
|
s_tracer.WriteLine("tokenizedPathStack.Push({0})", tempPath);
|
|
tokenizedPathStack.Push(tempPath);
|
|
break;
|
|
}
|
|
|
|
s_tracer.WriteLine("tokenizedPathStack.Push({0})", childName);
|
|
tokenizedPathStack.Push(childName);
|
|
|
|
// Get the parent path and verify if we have to continue
|
|
// tokenizing
|
|
// We are done if the remaining path is:
|
|
// - the same as the previous path
|
|
// - a UNC path that is the root of a UNC share
|
|
// - not a UNC path and the string length is less than or
|
|
// equal to 3. "C:\"
|
|
|
|
tempPath = GetParentPath(tempPath, basePath);
|
|
if (tempPath.Length >= previousParent.Length ||
|
|
IsPathRoot(tempPath))
|
|
{
|
|
if (String.IsNullOrEmpty(basePath))
|
|
{
|
|
s_tracer.WriteLine("tokenizedPathStack.Push({0})", tempPath);
|
|
tokenizedPathStack.Push(tempPath);
|
|
}
|
|
break;
|
|
}
|
|
previousParent = tempPath;
|
|
}
|
|
|
|
return tokenizedPathStack;
|
|
} // TokenizePathToStack
|
|
|
|
/// <summary>
|
|
/// Given the tokenized path, the relative path elements are removed.
|
|
/// </summary>
|
|
/// <param name="basepath">
|
|
/// String containing basepath for which we are trying to find the relative path.
|
|
/// </param>
|
|
/// <param name="tokenizedPathStack">
|
|
/// A stack containing path elements where the leaf most element is at
|
|
/// the bottom of the stack and the most ancestral parent is on the top.
|
|
/// Generally this stack comes from TokenizePathToStack().
|
|
/// </param>
|
|
/// <returns>
|
|
/// A stack in reverse order with the path elements normalized and all relative
|
|
/// path tokens removed.
|
|
/// </returns>
|
|
private Stack<string> NormalizeThePath(string basepath, Stack<string> tokenizedPathStack)
|
|
{
|
|
Stack<string> normalizedPathStack = new Stack<string>();
|
|
String currentPath = basepath;
|
|
|
|
while (tokenizedPathStack.Count > 0)
|
|
{
|
|
string childName = tokenizedPathStack.Pop();
|
|
|
|
s_tracer.WriteLine("childName = {0}", childName);
|
|
|
|
// Ignore the current directory token
|
|
if (childName.Equals(".", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// Just ignore it and move on.
|
|
continue;
|
|
}
|
|
else if (childName.Equals("..", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (normalizedPathStack.Count > 0)
|
|
{
|
|
// Pop the result and continue processing
|
|
string poppedName = normalizedPathStack.Pop();
|
|
//update our currentpath to reflect the change.
|
|
if (currentPath.Length > poppedName.Length)
|
|
{
|
|
currentPath = currentPath.Substring(0, currentPath.Length - poppedName.Length - 1);
|
|
}
|
|
else
|
|
{
|
|
currentPath = string.Empty;
|
|
}
|
|
|
|
s_tracer.WriteLine("normalizedPathStack.Pop() : {0}", poppedName);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path", FileSystemProviderStrings.PathOutSideBasePath);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentPath = MakePath(currentPath, childName);
|
|
|
|
var fsinfo = GetFileSystemInfo(currentPath, out bool _);
|
|
|
|
// Clean up the child name to proper casing and short-path
|
|
// expansion if required. Also verify that .NET hasn't over-normalized
|
|
// the path
|
|
if (fsinfo != null)
|
|
{
|
|
// This might happen if you've passed a child name of two or more dots,
|
|
// which the .NET APIs treat as the parent directory
|
|
if (fsinfo.FullName.Length < currentPath.Length)
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path", FileSystemProviderStrings.ItemDoesNotExist, currentPath);
|
|
}
|
|
|
|
// Expand the short file name
|
|
if (fsinfo.Name.Length >= childName.Length)
|
|
{
|
|
childName = fsinfo.Name;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We couldn't find the item
|
|
if (tokenizedPathStack.Count == 0)
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path", FileSystemProviderStrings.ItemDoesNotExist, currentPath);
|
|
}
|
|
}
|
|
}
|
|
s_tracer.WriteLine("normalizedPathStack.Push({0})", childName);
|
|
normalizedPathStack.Push(childName);
|
|
}
|
|
|
|
return normalizedPathStack;
|
|
} // NormalizeThePath
|
|
|
|
/// <summary>
|
|
/// Pops each leaf element of the stack and uses MakePath to generate the relative path
|
|
/// </summary>
|
|
/// <param name="normalizedPathStack">
|
|
/// The stack containing the leaf elements of the path.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A path that is made up of the leaf elements on the given stack.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The elements on the stack start from the leaf element followed by its parent
|
|
/// followed by its parent, etc. Each following element on the stack is the parent
|
|
/// of the one before it.
|
|
/// </remarks>
|
|
private string CreateNormalizedRelativePathFromStack(Stack<string> normalizedPathStack)
|
|
{
|
|
string leafElement = String.Empty;
|
|
|
|
while (normalizedPathStack.Count > 0)
|
|
{
|
|
if (String.IsNullOrEmpty(leafElement))
|
|
{
|
|
leafElement = normalizedPathStack.Pop();
|
|
}
|
|
else
|
|
{
|
|
string parentElement = normalizedPathStack.Pop();
|
|
leafElement = MakePath(parentElement, leafElement);
|
|
}
|
|
}
|
|
return leafElement;
|
|
} // CreateNormalizedRelativePathFromStack
|
|
|
|
/// <summary>
|
|
/// Gets the name of the leaf element of the specified path.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The fully qualified path to the item.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The leaf element of the specified path.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
protected override string GetChildName(string path)
|
|
{
|
|
// Verify the parameters
|
|
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
// Normalize the path
|
|
|
|
path = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
|
|
|
|
// Trim trailing back slashes
|
|
|
|
path = path.TrimEnd(StringLiterals.DefaultPathSeparator);
|
|
|
|
string result = null;
|
|
|
|
int separatorIndex = path.LastIndexOf(StringLiterals.DefaultPathSeparator);
|
|
|
|
if (separatorIndex == -1)
|
|
{
|
|
// Since there was no path separator return an empty string
|
|
|
|
result = EnsureDriveIsRooted(path);
|
|
}
|
|
else
|
|
{
|
|
result = path.Substring(separatorIndex + 1);
|
|
}
|
|
|
|
return result;
|
|
} // GetChildName
|
|
|
|
private static string EnsureDriveIsRooted(string path)
|
|
{
|
|
string result = path;
|
|
|
|
// Find the drive separator
|
|
|
|
int index = path.IndexOf(':');
|
|
|
|
if (index != -1)
|
|
{
|
|
// if the drive separator is the end of the path, add
|
|
// the root path separator back
|
|
|
|
if (index + 1 == path.Length)
|
|
{
|
|
result = path + StringLiterals.DefaultPathSeparator;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} // EnsureDriveIsRooted
|
|
|
|
/// <summary>
|
|
/// Determines if the item at the specified path is a directory.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path to the file or directory to check.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the item at the specified path is a directory.
|
|
/// False otherwise.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
protected override bool IsItemContainer(string path)
|
|
{
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
|
|
return Directory.Exists(path);
|
|
}
|
|
|
|
#region MoveItem
|
|
|
|
/// <summary>
|
|
/// Moves an item at the specified path to the given destination.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path of the item to move.
|
|
/// </param>
|
|
/// <param name="destination">
|
|
/// The path of the destination.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Nothing. Moved items are written to the context's pipeline.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// destination is null or empty.
|
|
/// </exception>
|
|
protected override void MoveItem(
|
|
string path,
|
|
string destination)
|
|
{
|
|
// Check the parameters
|
|
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(destination))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("destination");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
destination = NormalizePath(destination);
|
|
|
|
// Verify that the target doesn't represent a device name
|
|
if (PathIsReservedDeviceName(destination, "MoveError"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
bool isContainer = IsItemContainer(path);
|
|
s_tracer.WriteLine("Moving {0} to {1}", path, destination);
|
|
|
|
if (isContainer)
|
|
{
|
|
// Get the DirectoryInfo
|
|
|
|
DirectoryInfo dir = new DirectoryInfo(path);
|
|
|
|
if (ItemExists(destination) &&
|
|
IsItemContainer(destination))
|
|
{
|
|
destination = MakePath(destination, dir.Name);
|
|
}
|
|
|
|
// Get the confirmation text
|
|
string action = FileSystemProviderStrings.MoveItemActionDirectory;
|
|
|
|
string resource = StringUtil.Format(FileSystemProviderStrings.MoveItemResourceFileTemplate, dir.FullName, destination);
|
|
|
|
// Confirm the move with the user
|
|
if (ShouldProcess(resource, action))
|
|
{
|
|
// Now move the directory
|
|
|
|
MoveDirectoryInfoItem(dir, destination, Force);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get the FileInfo
|
|
|
|
FileInfo file = new FileInfo(path);
|
|
|
|
Dbg.Diagnostics.Assert(
|
|
file != null,
|
|
"FileInfo should be throwing an exception but it's " +
|
|
"returning null instead");
|
|
|
|
if (IsItemContainer(destination))
|
|
{
|
|
// Construct the new file path from the destination
|
|
// directory and the file name
|
|
|
|
destination = MakePath(destination, file.Name);
|
|
}
|
|
|
|
// Get the confirmation text
|
|
|
|
string action = FileSystemProviderStrings.MoveItemActionFile;
|
|
|
|
string resource = StringUtil.Format(FileSystemProviderStrings.MoveItemResourceFileTemplate, file.FullName, destination);
|
|
|
|
// Confirm the move with the user
|
|
|
|
if (ShouldProcess(resource, action))
|
|
{
|
|
MoveFileInfoItem(file, destination, Force, true);
|
|
}
|
|
}
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "MoveItemArgumentError", ErrorCategory.InvalidArgument, path));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "MoveItemIOError", ErrorCategory.WriteError, path));
|
|
}
|
|
catch (UnauthorizedAccessException accessException)
|
|
{
|
|
WriteError(new ErrorRecord(accessException, "MoveItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
|
|
}
|
|
} // MoveItem
|
|
|
|
private void MoveFileInfoItem(
|
|
FileInfo file,
|
|
string destination,
|
|
bool force,
|
|
bool output)
|
|
{
|
|
Dbg.Diagnostics.Assert(
|
|
file != null,
|
|
"The caller should verify file.");
|
|
|
|
Dbg.Diagnostics.Assert(
|
|
!String.IsNullOrEmpty(destination),
|
|
"THe caller should verify destination.");
|
|
|
|
try
|
|
{
|
|
// Move the file
|
|
file.MoveTo(destination);
|
|
|
|
if (output)
|
|
{
|
|
WriteItemObject(
|
|
file,
|
|
file.FullName,
|
|
false);
|
|
}
|
|
}
|
|
catch (System.UnauthorizedAccessException unauthorizedAccess)
|
|
{
|
|
// This error is thrown when the readonly bit is set.
|
|
|
|
if (force)
|
|
{
|
|
try
|
|
{
|
|
// mask off the readonly and hidden bits and try again
|
|
|
|
file.Attributes =
|
|
file.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden);
|
|
|
|
file.MoveTo(destination);
|
|
|
|
if (output)
|
|
{
|
|
WriteItemObject(file, file.FullName, false);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if ((e is IOException) ||
|
|
(e is ArgumentNullException) ||
|
|
(e is ArgumentException) ||
|
|
(e is System.Security.SecurityException) ||
|
|
(e is UnauthorizedAccessException) ||
|
|
(e is FileNotFoundException) ||
|
|
(e is DirectoryNotFoundException) ||
|
|
(e is PathTooLongException) ||
|
|
(e is NotSupportedException))
|
|
{
|
|
// If any exception occurs return the original error
|
|
WriteError(new ErrorRecord(unauthorizedAccess, "MoveFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
|
|
}
|
|
else
|
|
throw;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WriteError(new ErrorRecord(unauthorizedAccess, "MoveFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
|
|
}
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "MoveFileInfoItemArgumentError", ErrorCategory.InvalidArgument, file));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//check if destination file exists. if force is specified then we should delete the destination before moving
|
|
if (force && File.Exists(destination))
|
|
{
|
|
FileInfo destfile = new FileInfo(destination);
|
|
if (destfile != null)
|
|
{
|
|
try
|
|
{
|
|
//Make sure the file is not read only
|
|
destfile.Attributes = destfile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden);
|
|
destfile.Delete();
|
|
file.MoveTo(destination);
|
|
|
|
if (output)
|
|
{
|
|
WriteItemObject(file, file.FullName, false);
|
|
}
|
|
}
|
|
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))
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "MoveFileInfoItemIOError", ErrorCategory.WriteError, destfile));
|
|
}
|
|
else
|
|
throw;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "MoveFileInfoItemIOError", ErrorCategory.WriteError, file));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "MoveFileInfoItemIOError", ErrorCategory.WriteError, file));
|
|
}
|
|
}
|
|
} // MoveFileInfoItem
|
|
|
|
private void MoveDirectoryInfoItem(
|
|
DirectoryInfo directory,
|
|
string destination,
|
|
bool force)
|
|
{
|
|
Dbg.Diagnostics.Assert(
|
|
directory != null,
|
|
"The caller should verify directory.");
|
|
|
|
Dbg.Diagnostics.Assert(
|
|
!String.IsNullOrEmpty(destination),
|
|
"The caller should verify destination.");
|
|
|
|
try
|
|
{
|
|
if (!IsSameVolume(directory.FullName, destination))
|
|
{
|
|
CopyAndDelete(directory, destination, force);
|
|
}
|
|
else
|
|
{
|
|
// Move the file
|
|
directory.MoveTo(destination);
|
|
}
|
|
|
|
WriteItemObject(
|
|
directory,
|
|
directory.FullName,
|
|
true);
|
|
}
|
|
catch (System.UnauthorizedAccessException unauthorizedAccess)
|
|
{
|
|
// This error is thrown when the readonly bit is set.
|
|
if (force)
|
|
{
|
|
try
|
|
{
|
|
// mask off the readonly and hidden bits and try again
|
|
|
|
directory.Attributes =
|
|
directory.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden);
|
|
|
|
if (!IsSameVolume(directory.FullName, destination))
|
|
{
|
|
CopyAndDelete(directory, destination, force);
|
|
}
|
|
else
|
|
{
|
|
directory.MoveTo(destination);
|
|
}
|
|
|
|
WriteItemObject(directory, directory.FullName, true);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
WriteError(new ErrorRecord(unauthorizedAccess, "MoveDirectoryItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory));
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
if ((exception is FileNotFoundException) ||
|
|
(exception is ArgumentNullException) ||
|
|
(exception is DirectoryNotFoundException) ||
|
|
(exception is System.Security.SecurityException) ||
|
|
(exception is ArgumentException))
|
|
{
|
|
WriteError(new ErrorRecord(unauthorizedAccess, "MoveDirectoryItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory));
|
|
}
|
|
else
|
|
throw;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WriteError(new ErrorRecord(unauthorizedAccess, "MoveDirectoryItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory));
|
|
}
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "MoveDirectoryItemArgumentError", ErrorCategory.InvalidArgument, directory));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "MoveDirectoryItemIOError", ErrorCategory.WriteError, directory));
|
|
}
|
|
} // MoveDirectoryItem
|
|
|
|
private void CopyAndDelete(DirectoryInfo directory, string destination, bool force)
|
|
{
|
|
if (!ItemExists(destination))
|
|
{
|
|
CreateDirectory(destination, false);
|
|
}
|
|
else if (ItemExists(destination) && !IsItemContainer(destination))
|
|
{
|
|
String errorMessage = StringUtil.Format(FileSystemProviderStrings.DirectoryExist, destination);
|
|
Exception e = new IOException(errorMessage);
|
|
|
|
WriteError(new ErrorRecord(
|
|
e,
|
|
"DirectoryExist",
|
|
ErrorCategory.ResourceExists,
|
|
destination));
|
|
return;
|
|
}
|
|
|
|
foreach (FileInfo file in directory.EnumerateFiles())
|
|
{
|
|
MoveFileInfoItem(file, Path.Combine(destination, file.Name), force, false);
|
|
}
|
|
|
|
foreach (DirectoryInfo dir in directory.EnumerateDirectories())
|
|
{
|
|
CopyAndDelete(dir, Path.Combine(destination, dir.Name), force);
|
|
}
|
|
|
|
if (!directory.EnumerateDirectories().Any() && !directory.EnumerateFiles().Any())
|
|
{
|
|
RemoveItem(directory.FullName, false);
|
|
}
|
|
}
|
|
|
|
private bool IsSameVolume(string source, string destination)
|
|
{
|
|
FileInfo src = new FileInfo(source);
|
|
FileInfo dest = new FileInfo(destination);
|
|
|
|
return (src.Directory.Root.Name == dest.Directory.Root.Name);
|
|
}
|
|
|
|
#endregion MoveItem
|
|
|
|
#endregion NavigationCmdletProvider members
|
|
|
|
#region IPropertyCmdletProvider
|
|
|
|
/// <summary>
|
|
/// Gets a property for the given item.
|
|
/// </summary>
|
|
/// <param name="path">The fully qualified path to the item.</param>
|
|
/// <param name="providerSpecificPickList">
|
|
/// The list of properties to get. Examples include "Attributes", "LastAccessTime,"
|
|
/// and other properties defined by
|
|
/// <see cref="System.IO.DirectoryInfo" /> and
|
|
/// <see cref="System.IO.FileInfo" />
|
|
/// </param>
|
|
public void GetProperty(string path, Collection<string> providerSpecificPickList)
|
|
{
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
|
|
PSObject result = null;
|
|
|
|
try
|
|
{
|
|
var fileSystemObject = GetFileSystemInfo(path, out bool isDirectory);
|
|
|
|
if (fileSystemObject == null)
|
|
{
|
|
String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
|
|
Exception e = new IOException(error);
|
|
WriteError(new ErrorRecord(
|
|
e,
|
|
"ItemDoesNotExist",
|
|
ErrorCategory.ObjectNotFound,
|
|
path));
|
|
}
|
|
else
|
|
{
|
|
// Finally get the properties
|
|
if (providerSpecificPickList == null || providerSpecificPickList.Count == 0)
|
|
{
|
|
result = PSObject.AsPSObject(fileSystemObject);
|
|
}
|
|
else
|
|
{
|
|
foreach (string property in providerSpecificPickList)
|
|
{
|
|
if (property != null && property.Length > 0)
|
|
{
|
|
try
|
|
{
|
|
PSObject mshObject = PSObject.AsPSObject(fileSystemObject);
|
|
PSMemberInfo member = mshObject.Properties[property];
|
|
object value;
|
|
if (member != null)
|
|
{
|
|
value = member.Value;
|
|
if (result == null)
|
|
{
|
|
result = new PSObject();
|
|
}
|
|
result.Properties.Add(new PSNoteProperty(property, value));
|
|
}
|
|
else
|
|
{
|
|
String error =
|
|
StringUtil.Format(
|
|
FileSystemProviderStrings.PropertyNotFound,
|
|
property);
|
|
Exception e = new IOException(error);
|
|
WriteError(new ErrorRecord(e, "GetValueError", ErrorCategory.ReadError, property));
|
|
}
|
|
}
|
|
catch (GetValueException exception)
|
|
{
|
|
WriteError(new ErrorRecord(exception, "GetValueError", ErrorCategory.ReadError, property));
|
|
}
|
|
}
|
|
} // foreach (property in providerSpecificPickList
|
|
}
|
|
}
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "GetPropertyArgumentError", ErrorCategory.InvalidArgument, path));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "GetPropertyIOError", ErrorCategory.ReadError, path));
|
|
}
|
|
catch (UnauthorizedAccessException accessException)
|
|
{
|
|
WriteError(new ErrorRecord(accessException, "GetPropertyUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
|
|
}
|
|
|
|
if (result != null)
|
|
{
|
|
WritePropertyObject(result, path);
|
|
}
|
|
} // GetProperty
|
|
|
|
/// <summary>
|
|
/// Gets the dynamic property parameters required by the get-itemproperty cmdlet.
|
|
/// This feature is not required by the File System provider.
|
|
/// </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="providerSpecificPickList">
|
|
/// A list of properties that should be retrieved. If this parameter is null
|
|
/// or empty, all properties should be retrieved.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Null. This feature is not required by the File System provider.
|
|
/// </returns>
|
|
public object GetPropertyDynamicParameters(
|
|
string path,
|
|
Collection<string> providerSpecificPickList)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the specified properties on the item at the given path.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path of the item on which to set the properties.
|
|
/// </param>
|
|
/// <param name="propertyToSet">
|
|
/// A PSObject which contains a collection of the names and values
|
|
/// of the properties to be set. The File System provider supports setting
|
|
/// only the "Attributes" property.
|
|
/// </param>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// propertyToSet is null.
|
|
/// </exception>
|
|
public void SetProperty(string path, PSObject propertyToSet)
|
|
{
|
|
// verify parameters
|
|
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
if (propertyToSet == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("propertyToSet");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
|
|
PSObject results = new PSObject();
|
|
|
|
var fsinfo = GetFileSystemInfo(path, out bool isDirectory);
|
|
|
|
// Create a PSObject with either a DirectoryInfo or FileInfo object at its core.
|
|
if (fsinfo != null)
|
|
{
|
|
PSObject fileSystemInfoShell = PSObject.AsPSObject(fsinfo);
|
|
|
|
bool propertySet = false;
|
|
|
|
foreach (PSMemberInfo property in propertyToSet.Properties)
|
|
{
|
|
object propertyValue = property.Value;
|
|
|
|
// Get the confirmation text
|
|
string action = null;
|
|
|
|
if (isDirectory)
|
|
{
|
|
action = FileSystemProviderStrings.SetPropertyActionDirectory;
|
|
}
|
|
else
|
|
{
|
|
action = FileSystemProviderStrings.SetPropertyActionFile;
|
|
}
|
|
|
|
string resourceTemplate = FileSystemProviderStrings.SetPropertyResourceTemplate;
|
|
|
|
string propertyValueString;
|
|
|
|
try
|
|
{
|
|
// Use a PSObject to get the string representation of the property value
|
|
PSObject propertyValuePSObject = PSObject.AsPSObject(propertyValue);
|
|
propertyValueString = propertyValuePSObject.ToString();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Dbg.Diagnostics.Assert(
|
|
false,
|
|
"FileSystemProvider.SetProperty exception " + e.Message);
|
|
throw;
|
|
}
|
|
|
|
string resource =
|
|
String.Format(
|
|
Host.CurrentCulture,
|
|
resourceTemplate,
|
|
path,
|
|
property.Name,
|
|
propertyValueString);
|
|
|
|
// Confirm the set with the user
|
|
if (ShouldProcess(resource, action))
|
|
{
|
|
PSObject mshObject = PSObject.AsPSObject(fileSystemInfoShell);
|
|
PSMemberInfo member = mshObject.Properties[property.Name];
|
|
|
|
if (member != null)
|
|
{
|
|
if (string.Compare(property.Name, "attributes", StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
FileAttributes attributes;
|
|
|
|
if (propertyValue is FileAttributes)
|
|
attributes = (FileAttributes)propertyValue;
|
|
else
|
|
attributes = (FileAttributes)Enum.Parse(typeof(FileAttributes), propertyValueString, true);
|
|
|
|
if ((attributes & ~(FileAttributes.Archive | FileAttributes.Hidden |
|
|
FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.System)) != 0)
|
|
{
|
|
String error =
|
|
StringUtil.Format(
|
|
FileSystemProviderStrings.AttributesNotSupported,
|
|
property);
|
|
Exception e = new IOException(error);
|
|
WriteError(new ErrorRecord(e, "SetPropertyError", ErrorCategory.ReadError, property));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
member.Value = propertyValue;
|
|
results.Properties.Add(new PSNoteProperty(property.Name, propertyValue));
|
|
propertySet = true;
|
|
}
|
|
else
|
|
{
|
|
String error =
|
|
StringUtil.Format(
|
|
FileSystemProviderStrings.PropertyNotFound,
|
|
property);
|
|
Exception e = new IOException(error);
|
|
WriteError(new ErrorRecord(e, "SetPropertyError", ErrorCategory.ReadError, property));
|
|
}
|
|
} // ShouldProcess
|
|
} // foreach property
|
|
|
|
if (propertySet)
|
|
{
|
|
WritePropertyObject(results, path);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
|
|
Exception e = new IOException(error);
|
|
WriteError(new ErrorRecord(
|
|
e,
|
|
"ItemDoesNotExist",
|
|
ErrorCategory.ObjectNotFound,
|
|
path));
|
|
}
|
|
} // SetProperty
|
|
|
|
/// <summary>
|
|
/// Gets the dynamic property parameters required by the set-itemproperty cmdlet.
|
|
/// This feature is not required by the File System provider.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// If the path was specified on the command line, this is the path
|
|
/// to the item for which to set the dynamic parameters.
|
|
/// </param>
|
|
/// <param name="propertyValue">
|
|
/// A PSObject which contains a collection of the name, type, value
|
|
/// of the properties to be set.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Null. This feature is not required by the File System provider.
|
|
/// </returns>
|
|
public object SetPropertyDynamicParameters(
|
|
string path,
|
|
PSObject propertyValue)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the specified properties on the item at the given path.
|
|
/// The File System provider supports only the "Attributes" property.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path of the item on which to clear the properties.
|
|
/// </param>
|
|
/// <param name="propertiesToClear">
|
|
/// A collection of the names of the properties to clear. The File System
|
|
/// provider supports clearing only the "Attributes" property.
|
|
/// </param>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// Path is null or empty.
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// propertiesToClear is null or count is zero.
|
|
/// </exception>
|
|
public void ClearProperty(
|
|
string path,
|
|
Collection<string> propertiesToClear)
|
|
{
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
|
|
if (propertiesToClear == null ||
|
|
propertiesToClear.Count == 0)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("propertiesToClear");
|
|
}
|
|
|
|
// Only the attributes property can be cleared
|
|
|
|
if (propertiesToClear.Count > 1 ||
|
|
Host.CurrentCulture.CompareInfo.Compare("Attributes", propertiesToClear[0], CompareOptions.IgnoreCase) != 0)
|
|
{
|
|
throw PSTraceSource.NewArgumentException("propertiesToClear", FileSystemProviderStrings.CannotClearProperty);
|
|
}
|
|
|
|
try
|
|
{
|
|
// Now the only entry in the array should be the Attributes, so clear them
|
|
|
|
FileSystemInfo fileSystemInfo = null;
|
|
|
|
// Get the confirmation text
|
|
|
|
string action = null;
|
|
|
|
bool isContainer = IsItemContainer(path);
|
|
if (isContainer)
|
|
{
|
|
action = FileSystemProviderStrings.ClearPropertyActionDirectory;
|
|
|
|
fileSystemInfo = new DirectoryInfo(path);
|
|
}
|
|
else
|
|
{
|
|
action = FileSystemProviderStrings.ClearPropertyActionFile;
|
|
|
|
fileSystemInfo = new FileInfo(path);
|
|
}
|
|
|
|
string resourceTemplate = FileSystemProviderStrings.ClearPropertyResourceTemplate;
|
|
|
|
string resource =
|
|
String.Format(
|
|
Host.CurrentCulture,
|
|
resourceTemplate,
|
|
fileSystemInfo.FullName,
|
|
propertiesToClear[0]);
|
|
|
|
// Confirm the set with the user
|
|
|
|
if (ShouldProcess(resource, action))
|
|
{
|
|
fileSystemInfo.Attributes = FileAttributes.Normal;
|
|
PSObject result = new PSObject();
|
|
result.Properties.Add(new PSNoteProperty(propertiesToClear[0], fileSystemInfo.Attributes));
|
|
|
|
// Now write out the attribute that was cleared.
|
|
|
|
WritePropertyObject(result, path);
|
|
} // ShouldProcess
|
|
}
|
|
catch (UnauthorizedAccessException unauthorizedAccessException)
|
|
{
|
|
WriteError(new ErrorRecord(unauthorizedAccessException, "ClearPropertyUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "ClearPropertyArgumentError", ErrorCategory.InvalidArgument, path));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "ClearPropertyIOError", ErrorCategory.WriteError, path));
|
|
}
|
|
} // ClearProperty
|
|
|
|
/// <summary>
|
|
/// Gets the dynamic property parameters required by the clear-itemproperty cmdlet.
|
|
/// This feature is not required by the File System provider.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// If the path was specified on the command line, this is the path
|
|
/// to the item for which to set the dynamic parameters.
|
|
/// </param>
|
|
/// <param name="propertiesToClear">
|
|
/// A collection of the names of the properties to clear.
|
|
/// </param>
|
|
/// <returns>
|
|
/// Null. This feature is not required by the File System provider.
|
|
/// </returns>
|
|
public object ClearPropertyDynamicParameters(
|
|
string path,
|
|
Collection<string> propertiesToClear)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
#endregion IPropertyCmdletProvider
|
|
|
|
#region IContentCmdletProvider
|
|
|
|
/// <summary>
|
|
/// Creates an instance of the FileSystemContentStream class, opens
|
|
/// the specified file for reading, and returns the IContentReader interface
|
|
/// to it.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path of the file to be opened for reading.
|
|
/// </param>
|
|
/// <returns>
|
|
/// An IContentReader for the specified file.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
public IContentReader GetContentReader(string path)
|
|
{
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
|
|
// Defaults for the file read operation
|
|
string delimiter = "\n";
|
|
Encoding encoding = ClrFacade.GetDefaultEncoding();
|
|
bool waitForChanges = false;
|
|
|
|
bool streamTypeSpecified = false;
|
|
bool usingByteEncoding = false;
|
|
bool delimiterSpecified = false;
|
|
bool isRawStream = false;
|
|
string streamName = null;
|
|
|
|
// Get the dynamic parameters.
|
|
// They override the defaults specified above.
|
|
if (DynamicParameters != null)
|
|
{
|
|
FileSystemContentReaderDynamicParameters dynParams =
|
|
DynamicParameters as FileSystemContentReaderDynamicParameters;
|
|
|
|
if (dynParams != null)
|
|
{
|
|
// -raw is not allowed when -first,-last or -wait is specified
|
|
// this call will validate that and throws.
|
|
ValidateParameters(dynParams.Raw);
|
|
|
|
isRawStream = dynParams.Raw;
|
|
|
|
// Get the delimiter
|
|
delimiterSpecified = dynParams.DelimiterSpecified;
|
|
if (delimiterSpecified)
|
|
delimiter = dynParams.Delimiter;
|
|
|
|
// Get the stream type
|
|
usingByteEncoding = dynParams.AsByteStream;
|
|
streamTypeSpecified = dynParams.WasStreamTypeSpecified;
|
|
|
|
if (usingByteEncoding && streamTypeSpecified)
|
|
{
|
|
WriteWarning(FileSystemProviderStrings.EncodingNotUsed);
|
|
}
|
|
|
|
if (streamTypeSpecified)
|
|
{
|
|
encoding = dynParams.Encoding;
|
|
}
|
|
|
|
// Get the wait value
|
|
waitForChanges = dynParams.Wait;
|
|
|
|
#if !UNIX
|
|
// Get the stream name
|
|
streamName = dynParams.Stream;
|
|
#endif
|
|
} // dynParams != null
|
|
} // DynamicParameters != null
|
|
|
|
#if !UNIX
|
|
// 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)
|
|
{
|
|
streamName = path.Substring(secondColon + 1);
|
|
path = path.Remove(secondColon);
|
|
}
|
|
#endif
|
|
|
|
FileSystemContentReaderWriter stream = null;
|
|
|
|
try
|
|
{
|
|
if (Directory.Exists(path))
|
|
{
|
|
string errMsg = StringUtil.Format(SessionStateStrings.GetContainerContentException, path);
|
|
ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "GetContainerContentException", ErrorCategory.InvalidOperation, null);
|
|
WriteError(error);
|
|
return stream;
|
|
}
|
|
|
|
// Users can't both read as bytes, and specify a delimiter
|
|
if (delimiterSpecified)
|
|
{
|
|
if (usingByteEncoding)
|
|
{
|
|
Exception e =
|
|
new ArgumentException(FileSystemProviderStrings.DelimiterError, "delimiter");
|
|
WriteError(new ErrorRecord(
|
|
e,
|
|
"GetContentReaderArgumentError",
|
|
ErrorCategory.InvalidArgument,
|
|
path));
|
|
}
|
|
else
|
|
{
|
|
// Initialize the file reader
|
|
stream = new FileSystemContentReaderWriter(path, streamName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, delimiter, encoding, waitForChanges, this, isRawStream);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stream = new FileSystemContentReaderWriter(path, streamName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, encoding, usingByteEncoding, waitForChanges, this, isRawStream);
|
|
}
|
|
}
|
|
catch (PathTooLongException pathTooLong)
|
|
{
|
|
WriteError(new ErrorRecord(pathTooLong, "GetContentReaderPathTooLongError", ErrorCategory.InvalidArgument, path));
|
|
}
|
|
catch (FileNotFoundException fileNotFound)
|
|
{
|
|
WriteError(new ErrorRecord(fileNotFound, "GetContentReaderFileNotFoundError", ErrorCategory.ObjectNotFound, path));
|
|
}
|
|
catch (DirectoryNotFoundException directoryNotFound)
|
|
{
|
|
WriteError(new ErrorRecord(directoryNotFound, "GetContentReaderDirectoryNotFoundError", ErrorCategory.ObjectNotFound, path));
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "GetContentReaderArgumentError", ErrorCategory.InvalidArgument, path));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "GetContentReaderIOError", ErrorCategory.ReadError, path));
|
|
}
|
|
catch (System.Security.SecurityException securityException)
|
|
{
|
|
WriteError(new ErrorRecord(securityException, "GetContentReaderSecurityError", ErrorCategory.PermissionDenied, path));
|
|
}
|
|
catch (UnauthorizedAccessException unauthorizedAccess)
|
|
{
|
|
WriteError(new ErrorRecord(unauthorizedAccess, "GetContentReaderUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
|
|
}
|
|
|
|
return stream;
|
|
} // GetContentReader
|
|
|
|
/// <summary>
|
|
/// Gets the dynamic property parameters required by the get-content cmdlet.
|
|
/// </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>
|
|
public object GetContentReaderDynamicParameters(string path)
|
|
{
|
|
return new FileSystemContentReaderDynamicParameters();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an instance of the FileSystemContentStream class, opens
|
|
/// the specified file for writing, and returns the IContentReader interface
|
|
/// to it.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path of the file to be opened for writing.
|
|
/// </param>
|
|
/// <returns>
|
|
/// An IContentWriter for the specified file.
|
|
/// </returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
public IContentWriter GetContentWriter(string path)
|
|
{
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
|
|
// If this is true, then the content will be read as bytes
|
|
bool usingByteEncoding = false;
|
|
bool streamTypeSpecified = false;
|
|
Encoding encoding = ClrFacade.GetDefaultEncoding();
|
|
FileMode filemode = FileMode.OpenOrCreate;
|
|
string streamName = null;
|
|
bool suppressNewline = false;
|
|
|
|
// Get the dynamic parameters
|
|
|
|
if (DynamicParameters != null)
|
|
{
|
|
FileSystemContentWriterDynamicParameters dynParams =
|
|
DynamicParameters as FileSystemContentWriterDynamicParameters;
|
|
|
|
if (dynParams != null)
|
|
{
|
|
usingByteEncoding = dynParams.AsByteStream;
|
|
streamTypeSpecified = dynParams.WasStreamTypeSpecified;
|
|
|
|
if (usingByteEncoding && streamTypeSpecified)
|
|
{
|
|
WriteWarning(FileSystemProviderStrings.EncodingNotUsed);
|
|
}
|
|
|
|
if (streamTypeSpecified)
|
|
{
|
|
encoding = dynParams.Encoding;
|
|
}
|
|
|
|
#if !UNIX
|
|
streamName = dynParams.Stream;
|
|
#endif
|
|
suppressNewline = dynParams.NoNewline.IsPresent;
|
|
} // dynParams != null
|
|
}
|
|
|
|
#if !UNIX
|
|
// 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)
|
|
{
|
|
streamName = path.Substring(secondColon + 1);
|
|
path = path.Remove(secondColon);
|
|
}
|
|
#endif
|
|
|
|
FileSystemContentReaderWriter stream = null;
|
|
|
|
try
|
|
{
|
|
if (Directory.Exists(path))
|
|
{
|
|
string errMsg = StringUtil.Format(SessionStateStrings.WriteContainerContentException, path);
|
|
ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "WriteContainerContentException", ErrorCategory.InvalidOperation, null);
|
|
WriteError(error);
|
|
return stream;
|
|
}
|
|
|
|
stream = new FileSystemContentReaderWriter(path, streamName, filemode, FileAccess.Write, FileShare.ReadWrite, encoding, usingByteEncoding, false, this, false, suppressNewline);
|
|
}
|
|
catch (PathTooLongException pathTooLong)
|
|
{
|
|
WriteError(new ErrorRecord(pathTooLong, "GetContentWriterPathTooLongError", ErrorCategory.InvalidArgument, path));
|
|
}
|
|
catch (FileNotFoundException fileNotFound)
|
|
{
|
|
WriteError(new ErrorRecord(fileNotFound, "GetContentWriterFileNotFoundError", ErrorCategory.ObjectNotFound, path));
|
|
}
|
|
catch (DirectoryNotFoundException directoryNotFound)
|
|
{
|
|
WriteError(new ErrorRecord(directoryNotFound, "GetContentWriterDirectoryNotFoundError", ErrorCategory.ObjectNotFound, path));
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "GetContentWriterArgumentError", ErrorCategory.InvalidArgument, path));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "GetContentWriterIOError", ErrorCategory.WriteError, path));
|
|
}
|
|
catch (System.Security.SecurityException securityException)
|
|
{
|
|
WriteError(new ErrorRecord(securityException, "GetContentWriterSecurityError", ErrorCategory.PermissionDenied, path));
|
|
}
|
|
catch (UnauthorizedAccessException unauthorizedAccess)
|
|
{
|
|
WriteError(new ErrorRecord(unauthorizedAccess, "GetContentWriterUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
|
|
}
|
|
|
|
return stream;
|
|
} // GetContentWriter
|
|
|
|
/// <summary>
|
|
/// Gets the dynamic property parameters required by the set-content and
|
|
/// add-content cmdlets.
|
|
/// </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>
|
|
public object GetContentWriterDynamicParameters(string path)
|
|
{
|
|
return new FileSystemContentWriterDynamicParameters();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the content of the specified file.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path to the file of which to clear the contents.
|
|
/// </param>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// path is null or empty.
|
|
/// </exception>
|
|
public void ClearContent(string path)
|
|
{
|
|
if (String.IsNullOrEmpty(path))
|
|
{
|
|
throw PSTraceSource.NewArgumentException("path");
|
|
}
|
|
|
|
path = NormalizePath(path);
|
|
|
|
if (Directory.Exists(path))
|
|
{
|
|
string errorMsg = StringUtil.Format(SessionStateStrings.ClearDirectoryContent, path);
|
|
WriteError(new ErrorRecord(new NotSupportedException(errorMsg), "ClearDirectoryContent", ErrorCategory.InvalidOperation, path));
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
#if !UNIX
|
|
bool clearStream = false;
|
|
string streamName = null;
|
|
FileSystemClearContentDynamicParameters dynamicParameters = null;
|
|
FileSystemContentWriterDynamicParameters writerDynamicParameters = null;
|
|
|
|
// We get called during:
|
|
// - Clear-Content
|
|
// - Set-Content, in the phase that clears the path first.
|
|
if (DynamicParameters != null)
|
|
{
|
|
dynamicParameters = DynamicParameters as FileSystemClearContentDynamicParameters;
|
|
writerDynamicParameters = DynamicParameters as FileSystemContentWriterDynamicParameters;
|
|
|
|
if (dynamicParameters != null)
|
|
{
|
|
if ((dynamicParameters.Stream != null) && (dynamicParameters.Stream.Length > 0))
|
|
clearStream = true;
|
|
streamName = dynamicParameters.Stream;
|
|
}
|
|
else if (writerDynamicParameters != null)
|
|
{
|
|
if ((writerDynamicParameters.Stream != null) && (writerDynamicParameters.Stream.Length > 0))
|
|
clearStream = true;
|
|
streamName = writerDynamicParameters.Stream;
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(streamName))
|
|
{
|
|
// 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)
|
|
{
|
|
streamName = path.Substring(secondColon + 1);
|
|
path = path.Remove(secondColon);
|
|
|
|
clearStream = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If they're just working on the DATA stream, don't use the Alternate Data Stream
|
|
// utils to clear the stream - otherwise, the Win32 API will trash the other streams.
|
|
if (String.Equals(":$DATA", streamName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
clearStream = false;
|
|
}
|
|
|
|
if (clearStream)
|
|
{
|
|
FileStream fileStream = null;
|
|
|
|
string streamAction = String.Format(
|
|
CultureInfo.InvariantCulture,
|
|
FileSystemProviderStrings.StreamAction,
|
|
streamName, path);
|
|
if (ShouldProcess(streamAction))
|
|
{
|
|
// If we've been called as part of Clear-Content, validate that the stream exists.
|
|
// This is because the core API doesn't support truncate mode.
|
|
if (dynamicParameters != null)
|
|
{
|
|
fileStream = AlternateDataStreamUtilities.CreateFileStream(path, streamName, FileMode.Open, FileAccess.Write, FileShare.Write);
|
|
fileStream.Dispose();
|
|
}
|
|
|
|
fileStream = AlternateDataStreamUtilities.CreateFileStream(
|
|
path, streamName, FileMode.Create, FileAccess.Write, FileShare.Write);
|
|
fileStream.Dispose();
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
string action = FileSystemProviderStrings.ClearContentActionFile;
|
|
string resource = StringUtil.Format(FileSystemProviderStrings.ClearContentesourceTemplate, path);
|
|
|
|
if (!ShouldProcess(resource, action))
|
|
return;
|
|
|
|
FileStream fileStream = new FileStream(path, FileMode.Truncate, FileAccess.Write, FileShare.Write);
|
|
fileStream.Dispose();
|
|
}
|
|
|
|
// For filesystem once content is cleared
|
|
WriteItemObject(string.Empty, path, false);
|
|
}
|
|
catch (ArgumentException argException)
|
|
{
|
|
WriteError(new ErrorRecord(argException, "ClearContentArgumentError", ErrorCategory.InvalidArgument, path));
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
//IOException contains specific message about the error occured and so no need for errordetails.
|
|
WriteError(new ErrorRecord(ioException, "ClearContentIOError", ErrorCategory.WriteError, path));
|
|
}
|
|
catch (UnauthorizedAccessException accessException)
|
|
{
|
|
if (Force)
|
|
{
|
|
//// Store the old attributes so that we can recover them
|
|
FileAttributes oldAttributes = File.GetAttributes(path);
|
|
|
|
try
|
|
{
|
|
// Since a security exception was thrown, try to mask off
|
|
// the hidden and readonly bits and then retry.
|
|
File.SetAttributes(path, (File.GetAttributes(path) & ~(FileAttributes.Hidden | FileAttributes.ReadOnly)));
|
|
FileStream fileStream = new FileStream(path, FileMode.Truncate, FileAccess.Write, FileShare.Write);
|
|
fileStream.Dispose();
|
|
|
|
//For filesystem once content is cleared
|
|
WriteItemObject(string.Empty, path, false);
|
|
}
|
|
catch (UnauthorizedAccessException failure)
|
|
{
|
|
WriteError(new ErrorRecord(failure, "RemoveFileSystemItemUnAuthorizedAccess", ErrorCategory.PermissionDenied, path));
|
|
}
|
|
finally
|
|
{
|
|
//// Reset the attributes
|
|
File.SetAttributes(path, oldAttributes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WriteError(new ErrorRecord(accessException, "ClearContentUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
|
|
}
|
|
}
|
|
} // ClearContent
|
|
|
|
/// <summary>
|
|
/// Gets the dynamic property parameters required by the clear-content cmdlet.
|
|
/// </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>
|
|
/// A FileSystemClearContentDynamicParameters that provides access to the -Stream dynamic parameter.
|
|
/// </returns>
|
|
public object ClearContentDynamicParameters(string path)
|
|
{
|
|
return new FileSystemClearContentDynamicParameters();
|
|
}
|
|
|
|
#endregion IContentCmdletProvider
|
|
|
|
/// <summary>
|
|
/// -raw is not allowed when -first,-last or -wait is specified
|
|
/// this call will validate that and throws.
|
|
/// </summary>
|
|
private void ValidateParameters(bool isRawSpecified)
|
|
{
|
|
if (isRawSpecified)
|
|
{
|
|
if (this.Context.MyInvocation.BoundParameters.ContainsKey("TotalCount"))
|
|
{
|
|
string message = StringUtil.Format(FileSystemProviderStrings.NoFirstLastWaitForRaw, "Raw", "TotalCount");
|
|
throw new PSInvalidOperationException(message);
|
|
}
|
|
|
|
if (this.Context.MyInvocation.BoundParameters.ContainsKey("Tail"))
|
|
{
|
|
string message = StringUtil.Format(FileSystemProviderStrings.NoFirstLastWaitForRaw, "Raw", "Tail");
|
|
throw new PSInvalidOperationException(message);
|
|
}
|
|
|
|
if (this.Context.MyInvocation.BoundParameters.ContainsKey("Wait"))
|
|
{
|
|
string message = StringUtil.Format(FileSystemProviderStrings.NoFirstLastWaitForRaw, "Raw", "Wait");
|
|
throw new PSInvalidOperationException(message);
|
|
}
|
|
|
|
if (this.Context.MyInvocation.BoundParameters.ContainsKey("Delimiter"))
|
|
{
|
|
string message = StringUtil.Format(FileSystemProviderStrings.NoFirstLastWaitForRaw, "Raw", "Delimiter");
|
|
throw new PSInvalidOperationException(message);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The API 'PathIsNetworkPath' is not available in CoreSystem.
|
|
/// This implementation is based on the 'PathIsNetworkPath' API.
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
/// <returns></returns>
|
|
internal static bool PathIsNetworkPath(string path)
|
|
{
|
|
#if UNIX
|
|
return false;
|
|
#else
|
|
return WinPathIsNetworkPath(path);
|
|
#endif
|
|
}
|
|
|
|
internal static bool WinPathIsNetworkPath(string path)
|
|
{
|
|
return NativeMethods.PathIsNetworkPath(path); // call the native method
|
|
}
|
|
|
|
private static class NativeMethods
|
|
{
|
|
/// <summary>
|
|
/// WNetAddConnection2 API makes a connection to a network resource
|
|
/// and can redirect a local device to the network resource.
|
|
/// This API simulates the "new Use" functionality used to connect to
|
|
/// network resource.
|
|
/// </summary>
|
|
/// <param name="netResource">
|
|
/// The The netResource structure contains information
|
|
/// about a network resource.</param>
|
|
/// <param name="password">
|
|
/// The password used to get connected to network resource.
|
|
/// </param>
|
|
/// <param name="username">
|
|
/// The username used to get connected to network resource.
|
|
/// </param>
|
|
/// <param name="flags">
|
|
/// The flags parameter is used to indicate if the created network
|
|
/// resource has to be persisted or not.
|
|
/// </param>
|
|
/// <returns>If connection is established to the network resource
|
|
/// then success is returned or else the error code describing the
|
|
/// type of failure that occured while establishing
|
|
/// the connection is returned.</returns>
|
|
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
|
|
internal static extern int WNetAddConnection2(ref NetResource netResource, byte[] password, string username, int flags);
|
|
|
|
/// <summary>
|
|
/// WNetCancelConnection2 function cancels an existing network connection.
|
|
/// </summary>
|
|
/// <param name="driveName">
|
|
/// PSDrive Name.
|
|
/// </param>
|
|
/// <param name="flags">
|
|
/// Connection Type.
|
|
/// </param>
|
|
/// <param name="force">
|
|
/// Specifies whether the disconnection should occur if there are open files or jobs
|
|
/// on the connection. If this parameter is FALSE, the function fails
|
|
/// if there are open files or jobs.
|
|
/// </param>
|
|
/// <returns>If connection is removed then success is returned or
|
|
/// else the error code describing the type of failure that occured while
|
|
/// trying to remove the connection is returned.
|
|
/// </returns>
|
|
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
|
|
internal static extern int WNetCancelConnection2(string driveName, int flags, bool force);
|
|
|
|
/// <summary>
|
|
/// WNetGetConnection function retrieves the name of the network resource associated with a local device.
|
|
/// </summary>
|
|
/// <param name="localName">
|
|
/// Local name of the PSDrive.
|
|
/// </param>
|
|
/// <param name="remoteName">
|
|
/// The remote name to which the PSDrive is getting mapped to.
|
|
/// </param>
|
|
/// <param name="remoteNameLength">
|
|
/// length of the remote name of the created PSDrive.
|
|
/// </param>
|
|
/// <returns></returns>
|
|
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
|
|
internal static extern int WNetGetConnection(string localName, StringBuilder remoteName, ref int remoteNameLength);
|
|
|
|
#if CORECLR //TODO:CORECLR Win32 function 'PathIsNetworkPath' is in an extension API set which is currently not on CSS.
|
|
/// <summary>
|
|
/// Searches a path for a drive letter within the range of 'A' to 'Z' and returns the corresponding drive number.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// Path of the file being executed
|
|
/// </param>
|
|
/// <returns>Returns 0 through 25 (corresponding to 'A' through 'Z') if the path has a drive letter, or -1 otherwise.</returns>
|
|
[DllImport("api-ms-win-core-shlwapi-legacy-l1-1-0.dll", CharSet = CharSet.Unicode)]
|
|
internal static extern int PathGetDriveNumber(string path);
|
|
|
|
/// <summary>
|
|
/// Determines if a path string is a valid Universal Naming Convention (UNC) path, as opposed to a path based on a drive letter.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// Path of the file being executed
|
|
/// </param>
|
|
/// <returns>Returns TRUE if the string is a valid UNC path; otherwise, FALSE.</returns>
|
|
[DllImport("api-ms-win-core-shlwapi-legacy-l1-1-0.dll", CharSet = CharSet.Unicode)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static extern bool PathIsUNC(string path);
|
|
|
|
/// <summary>
|
|
/// The API 'PathIsNetworkPath' is not available in CoreSystem.
|
|
/// This implementation is based on the 'PathIsNetworkPath' API.
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
/// <returns></returns>
|
|
internal static bool PathIsNetworkPath(string path)
|
|
{
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (PathIsUNC(path))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// 0 - 25 corresponding to 'A' - 'Z'
|
|
int driveId = PathGetDriveNumber(path);
|
|
if (driveId >= 0 && driveId < 26)
|
|
{
|
|
string driveName = (char)('A' + driveId) + ":";
|
|
|
|
int bufferSize = 260; // MAX_PATH from EhStorIoctl.h
|
|
StringBuilder uncBuffer = new StringBuilder(bufferSize);
|
|
int errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize);
|
|
|
|
// From the 'IsNetDrive' API.
|
|
// 0: success; 1201: connection closed; 31: device error
|
|
if (errorCode == 0 || errorCode == 1201 || errorCode == 31)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#else
|
|
/// <summary>
|
|
/// Facilitates to validate if the supplied path exists locally or on the network share.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// Path of the file being executed.
|
|
/// </param>
|
|
/// <returns>True if the path is a network path or else returns false.</returns>
|
|
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static extern bool PathIsNetworkPath(string path);
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// The function can obtain the current mapping for a particular MS-DOS device name.
|
|
///
|
|
/// If lpDeviceName is non-NULL, the function retrieves information about the particular MS-DOS device specified by lpDeviceName.
|
|
/// The first null-terminated string stored into the buffer is the current mapping for the device.
|
|
/// The other null-terminated strings represent undeleted prior mappings for the device.
|
|
/// </summary>
|
|
/// <param name="lpDeviceName">
|
|
/// The particular MS-DOS device name.
|
|
/// </param>
|
|
/// <param name="lpTargetPath">
|
|
/// The buffer to receive the result of the query.
|
|
/// </param>
|
|
/// <param name="ucchMax">
|
|
/// The maximum number of characters that can be stored into the buffer
|
|
/// </param>
|
|
/// <returns></returns>
|
|
[DllImport(PinvokeDllNames.QueryDosDeviceDllName, CharSet = CharSet.Unicode, SetLastError = true)]
|
|
internal static extern int QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
|
|
|
|
/// <summary>
|
|
/// Creates a symbolic link using the native API.
|
|
/// </summary>
|
|
/// <param name="name">Path of the symbolic link.</param>
|
|
/// <param name="destination">Path of the target of the symbolic link.</param>
|
|
/// <param name="destinationType">0 for destination as file and 1 for destination as directory.</param>
|
|
/// <returns>1 on successful creation.</returns>
|
|
[DllImport(PinvokeDllNames.CreateSymbolicLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)]
|
|
internal static extern int CreateSymbolicLink(string name, string destination, int destinationType);
|
|
|
|
/// <summary>
|
|
/// Creates a hard link using the native API.
|
|
/// </summary>
|
|
/// <param name="name">Name of the hard link.</param>
|
|
/// <param name="existingFileName">Path to the target of the hard link</param>
|
|
/// <param name="SecurityAttributes"></param>
|
|
/// <returns></returns>
|
|
[DllImport(PinvokeDllNames.CreateHardLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)]
|
|
internal static extern bool CreateHardLink(string name, string existingFileName, IntPtr SecurityAttributes);
|
|
|
|
[Flags]
|
|
internal enum FileAttributes
|
|
{
|
|
Hidden = 0x0002,
|
|
Directory = 0x0010
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Managed equivalent of NETRESOURCE structure of WNet API
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct NetResource
|
|
{
|
|
public int Scope;
|
|
public int Type;
|
|
public int DisplayType;
|
|
public int Usage;
|
|
[MarshalAs(UnmanagedType.LPWStr)]
|
|
public string LocalName;
|
|
[MarshalAs(UnmanagedType.LPWStr)]
|
|
public string RemoteName;
|
|
[MarshalAs(UnmanagedType.LPWStr)]
|
|
public string Comment;
|
|
[MarshalAs(UnmanagedType.LPWStr)]
|
|
public string Provider;
|
|
}
|
|
|
|
#region InodeTracker
|
|
/// <summary>
|
|
/// Tracks visited files/directories by caching their device IDs and inodes.
|
|
/// </summary>
|
|
private class InodeTracker
|
|
{
|
|
private HashSet<(UInt64, UInt64)> _visitations;
|
|
|
|
/// <summary>
|
|
/// Construct a new InodeTracker with an initial path
|
|
/// </summary>
|
|
internal InodeTracker(string path)
|
|
{
|
|
_visitations = new HashSet<(UInt64, UInt64)>();
|
|
|
|
if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
|
|
{
|
|
_visitations.Add(inodeData);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempt to mark a path as having been visited.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// Path to the file system item to be visited.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the path had not been previously visited and was
|
|
/// successfully marked as visited, false otherwise.
|
|
/// </returns>
|
|
internal bool TryVisitPath(string path)
|
|
{
|
|
bool returnValue = false;
|
|
|
|
if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
|
|
{
|
|
returnValue = _visitations.Add(inodeData);
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
} // class FileSystemProvider
|
|
|
|
internal static class SafeInvokeCommand
|
|
{
|
|
public static Hashtable Invoke(System.Management.Automation.PowerShell ps, FileSystemProvider fileSystemContext, CmdletProviderContext cmdletContext)
|
|
{
|
|
return Invoke(ps, fileSystemContext, cmdletContext, true);
|
|
}
|
|
public static Hashtable Invoke(System.Management.Automation.PowerShell ps, FileSystemProvider fileSystemContext, CmdletProviderContext cmdletContext, bool shouldHaveOutput)
|
|
{
|
|
bool useFileSystemProviderContext = (cmdletContext == null);
|
|
|
|
if (useFileSystemProviderContext)
|
|
{
|
|
Dbg.Diagnostics.Assert(fileSystemContext != null, "The caller should verify FileSystemProvider context.");
|
|
}
|
|
|
|
Collection<Hashtable> output;
|
|
try
|
|
{
|
|
output = ps.Invoke<Hashtable>();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (useFileSystemProviderContext)
|
|
{
|
|
fileSystemContext.WriteError(new ErrorRecord(e, "CopyFileRemoteExecutionError", ErrorCategory.InvalidOperation, ps));
|
|
ps.Commands.Clear();
|
|
}
|
|
else
|
|
{
|
|
cmdletContext.WriteError(new ErrorRecord(e, "CopyFileRemoteExecutionError", ErrorCategory.InvalidOperation, ps));
|
|
ps.Commands.Clear();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
if (ps.HadErrors)
|
|
{
|
|
foreach (var error in ps.Streams.Error)
|
|
{
|
|
if (useFileSystemProviderContext)
|
|
{
|
|
fileSystemContext.WriteError(error);
|
|
}
|
|
else
|
|
{
|
|
cmdletContext.WriteError(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
ps.Commands.Clear();
|
|
|
|
if (shouldHaveOutput)
|
|
{
|
|
if (output.Count != 1 || output[0].GetType() != typeof(Hashtable))
|
|
{
|
|
// unexpected output
|
|
Dbg.Diagnostics.Assert(output[0] != null, "Expected an output from the remote call.");
|
|
return null;
|
|
}
|
|
return (Hashtable)output[0];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Dynamic Parameters
|
|
|
|
internal sealed class CopyItemDynamicParameters
|
|
{
|
|
[Parameter]
|
|
[ValidateNotNullOrEmpty]
|
|
public PSSession FromSession { get; set; }
|
|
|
|
[Parameter]
|
|
[ValidateNotNullOrEmpty]
|
|
public PSSession ToSession { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the container cmdlet dynamic providers
|
|
/// </summary>
|
|
internal sealed class GetChildDynamicParameters
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the attribute filtering enum evaluator
|
|
/// </summary>
|
|
[Parameter]
|
|
public FlagsExpression<FileAttributes> Attributes { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the flag to follow symbolic links when recursing.
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter FollowSymlink { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the filter directory flag
|
|
/// </summary>
|
|
[Parameter]
|
|
[Alias("ad", "d")]
|
|
public SwitchParameter Directory
|
|
{
|
|
get { return _attributeDirectory; }
|
|
set { _attributeDirectory = value; }
|
|
}
|
|
private bool _attributeDirectory;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the filter file flag
|
|
/// </summary>
|
|
[Parameter]
|
|
[Alias("af")]
|
|
public SwitchParameter File
|
|
{
|
|
get { return _attributeFile; }
|
|
set { _attributeFile = value; }
|
|
}
|
|
private bool _attributeFile;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the filter hidden flag
|
|
/// </summary>
|
|
[Parameter]
|
|
[Alias("ah", "h")]
|
|
public SwitchParameter Hidden
|
|
{
|
|
get { return _attributeHidden; }
|
|
set { _attributeHidden = value; }
|
|
}
|
|
private bool _attributeHidden;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the filter readonly flag
|
|
/// </summary>
|
|
[Parameter]
|
|
[Alias("ar")]
|
|
public SwitchParameter ReadOnly
|
|
{
|
|
get { return _attributeReadOnly; }
|
|
set { _attributeReadOnly = value; }
|
|
}
|
|
private bool _attributeReadOnly;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the filter system flag
|
|
/// </summary>
|
|
[Parameter]
|
|
[Alias("as")]
|
|
public SwitchParameter System
|
|
{
|
|
get { return _attributeSystem; }
|
|
set { _attributeSystem = value; }
|
|
}
|
|
private bool _attributeSystem;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the dynamic parameters used by both the content reader and writer.
|
|
/// </summary>
|
|
public class FileSystemContentDynamicParametersBase
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the encoding method used when
|
|
/// reading data from the file.
|
|
/// </summary>
|
|
[Parameter]
|
|
[ArgumentToEncodingTransformationAttribute()]
|
|
[ArgumentEncodingCompletionsAttribute]
|
|
[ValidateNotNullOrEmpty]
|
|
public Encoding Encoding
|
|
{
|
|
get
|
|
{
|
|
return _encoding;
|
|
}
|
|
set
|
|
{
|
|
_encoding = value;
|
|
// If an encoding was explicitly set, be sure to capture that.
|
|
WasStreamTypeSpecified = true;
|
|
}
|
|
}
|
|
private Encoding _encoding = ClrFacade.GetDefaultEncoding();
|
|
|
|
/// <summary>
|
|
/// Return file contents as a byte stream or create file from a series of bytes
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter AsByteStream { get; set; }
|
|
|
|
#if !UNIX
|
|
/// <summary>
|
|
/// A parameter to return a stream of an item.
|
|
/// </summary>
|
|
[Parameter]
|
|
public String Stream { get; set; }
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Gets the status of the StreamType parameter. Returns true
|
|
/// if the stream was opened with a user-specified encoding, false otherwise.
|
|
/// </summary>
|
|
public bool WasStreamTypeSpecified { get; private set; }
|
|
|
|
} // class FileSystemContentDynamicParametersBase
|
|
|
|
/// <summary>
|
|
/// Defines the dynamic parameters used by the Clear-Content cmdlet.
|
|
/// </summary>
|
|
public class FileSystemClearContentDynamicParameters
|
|
{
|
|
#if !UNIX
|
|
/// <summary>
|
|
/// A parameter to return a stream of an item.
|
|
/// </summary>
|
|
[Parameter]
|
|
public String Stream { get; set; }
|
|
#endif
|
|
} //FileSystemContentWriterDynamicParameters
|
|
|
|
/// <summary>
|
|
/// Defines the dynamic parameters used by the set-content and
|
|
/// add-content cmdlets.
|
|
/// </summary>
|
|
public class FileSystemContentWriterDynamicParameters : FileSystemContentDynamicParametersBase
|
|
{
|
|
/// <summary>
|
|
/// False to add a newline to the end of the output string, true if not.
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter NoNewline
|
|
{
|
|
get
|
|
{
|
|
return _suppressNewline;
|
|
}
|
|
set
|
|
{
|
|
_suppressNewline = value;
|
|
}
|
|
}
|
|
|
|
private bool _suppressNewline = false;
|
|
} //FileSystemContentWriterDynamicParameters
|
|
|
|
/// <summary>
|
|
/// Defines the dynamic parameters used by the get-content cmdlet.
|
|
/// </summary>
|
|
public class FileSystemContentReaderDynamicParameters : FileSystemContentDynamicParametersBase
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the delimiter to use when reading the file. Custom delimiters
|
|
/// may not be used when the file is opened with a "Byte" encoding.
|
|
/// </summary>
|
|
[Parameter]
|
|
public string Delimiter
|
|
{
|
|
get
|
|
{
|
|
return _delimiter;
|
|
}
|
|
|
|
set
|
|
{
|
|
DelimiterSpecified = true;
|
|
_delimiter = value;
|
|
}
|
|
}
|
|
private string _delimiter = "\n";
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Wait flag. The wait flag determines if we want
|
|
/// the read-content call to poll (and wait) for changes to the file,
|
|
/// rather than exit after the content has been read.
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter Wait
|
|
{
|
|
get
|
|
{
|
|
return _wait;
|
|
} // get
|
|
|
|
set
|
|
{
|
|
_wait = value;
|
|
} // set
|
|
}
|
|
private bool _wait;
|
|
|
|
/// <summary>
|
|
/// When the Raw switch is present, we don't do any breaks on newlines,
|
|
/// and only emit one object to the pipeline: all of the content.
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter Raw
|
|
{
|
|
get
|
|
{
|
|
return _isRaw;
|
|
}
|
|
set
|
|
{
|
|
_isRaw = value;
|
|
}
|
|
}
|
|
private bool _isRaw;
|
|
|
|
/// <summary>
|
|
/// Gets the status of the delimiter parameter. Returns true
|
|
/// if the delimiter was explicitly specified by the user, false otherwise.
|
|
/// </summary>
|
|
public bool DelimiterSpecified { get; private set;
|
|
// get
|
|
} // DelimiterSpecified
|
|
} // class FileSystemContentReaderDynamicParameters
|
|
|
|
/// <summary>
|
|
/// Provides the dynamic parameters for test-path on the file system.
|
|
/// </summary>
|
|
public class FileSystemItemProviderDynamicParameters
|
|
{
|
|
/// <summary>
|
|
/// A parameter to test if a file is older than a certain time or date.
|
|
/// </summary>
|
|
[Parameter]
|
|
public DateTime? OlderThan { get; set; }
|
|
|
|
/// <summary>
|
|
/// A parameter to test if a file is newer than a certain time or date
|
|
/// </summary>
|
|
[Parameter]
|
|
public DateTime? NewerThan { get; set; }
|
|
} // class FileSystemItemProviderDynamicParameters
|
|
|
|
/// <summary>
|
|
/// Provides the dynamic parameters for Get-Item on the file system.
|
|
/// </summary>
|
|
public class FileSystemProviderGetItemDynamicParameters
|
|
{
|
|
#if !UNIX
|
|
/// <summary>
|
|
/// A parameter to return the streams of an item.
|
|
/// </summary>
|
|
[Parameter]
|
|
[ValidateNotNullOrEmpty()]
|
|
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
|
public string[] Stream { get; set; }
|
|
#endif
|
|
} // class FileSystemItemProviderDynamicParameters
|
|
|
|
/// <summary>
|
|
/// Provides the dynamic parameters for Remove-Item on the file system.
|
|
/// </summary>
|
|
public class FileSystemProviderRemoveItemDynamicParameters
|
|
{
|
|
#if !UNIX
|
|
/// <summary>
|
|
/// A parameter to return the streams of an item.
|
|
/// </summary>
|
|
[Parameter]
|
|
[ValidateNotNullOrEmpty()]
|
|
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
|
public string[] Stream { get; set; }
|
|
#endif
|
|
} // class FileSystemItemProviderDynamicParameters
|
|
|
|
#endregion
|
|
|
|
#region Symbolic Link
|
|
|
|
/// <summary>
|
|
/// Class to find the symbolic link target.
|
|
/// </summary>
|
|
public static class InternalSymbolicLinkLinkCodeMethods
|
|
{
|
|
//This size comes from measuring the size of the header of REPARSE_GUID_DATA_BUFFER
|
|
private const int REPARSE_GUID_DATA_BUFFER_HEADER_SIZE = 24;
|
|
|
|
// Maximum reparse buffer info size. The max user defined reparse
|
|
// data is 16KB, plus there's a header.
|
|
private const int MAX_REPARSE_SIZE = (16 * 1024) + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE;
|
|
|
|
private const int ERROR_NOT_A_REPARSE_POINT = 4390;
|
|
|
|
private const int FSCTL_GET_REPARSE_POINT = 0x000900A8;
|
|
|
|
private const int FSCTL_SET_REPARSE_POINT = 0x000900A4;
|
|
|
|
private const int FSCTL_DELETE_REPARSE_POINT = 0x000900AC;
|
|
|
|
private const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C;
|
|
|
|
private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;
|
|
|
|
private const string NonInterpretedPathPrefix = @"\??\";
|
|
|
|
[Flags]
|
|
//dwDesiredAccess of CreateFile
|
|
internal enum FileDesiredAccess : uint
|
|
{
|
|
GenericRead = 0x80000000,
|
|
GenericWrite = 0x40000000,
|
|
GenericExecute = 0x20000000,
|
|
GenericAll = 0x10000000,
|
|
}
|
|
|
|
[Flags]
|
|
//dwShareMode of CreateFile
|
|
internal enum FileShareMode : uint
|
|
{
|
|
None = 0x00000000,
|
|
Read = 0x00000001,
|
|
Write = 0x00000002,
|
|
Delete = 0x00000004,
|
|
}
|
|
|
|
//dwCreationDisposition of CreateFile
|
|
internal enum FileCreationDisposition : uint
|
|
{
|
|
New = 1,
|
|
CreateAlways = 2,
|
|
OpenExisting = 3,
|
|
OpenAlways = 4,
|
|
TruncateExisting = 5,
|
|
}
|
|
|
|
[Flags]
|
|
//dwFlagsAndAttributes
|
|
internal enum FileAttributes : uint
|
|
{
|
|
Readonly = 0x00000001,
|
|
Hidden = 0x00000002,
|
|
System = 0x00000004,
|
|
Archive = 0x00000020,
|
|
Encrypted = 0x00004000,
|
|
Write_Through = 0x80000000,
|
|
Overlapped = 0x40000000,
|
|
NoBuffering = 0x20000000,
|
|
RandomAccess = 0x10000000,
|
|
SequentialScan = 0x08000000,
|
|
DeleteOnClose = 0x04000000,
|
|
BackupSemantics = 0x02000000,
|
|
PosixSemantics = 0x01000000,
|
|
OpenReparsePoint = 0x00200000,
|
|
OpenNoRecall = 0x00100000,
|
|
SessionAware = 0x00800000,
|
|
Normal = 0x00000080
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct REPARSE_DATA_BUFFER_SYMBOLICLINK
|
|
{
|
|
public uint ReparseTag;
|
|
public ushort ReparseDataLength;
|
|
public ushort Reserved;
|
|
public ushort SubstituteNameOffset;
|
|
public ushort SubstituteNameLength;
|
|
public ushort PrintNameOffset;
|
|
public ushort PrintNameLength;
|
|
public uint Flags;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
|
|
public byte[] PathBuffer;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct REPARSE_DATA_BUFFER_MOUNTPOINT
|
|
{
|
|
public uint ReparseTag;
|
|
public ushort ReparseDataLength;
|
|
public ushort Reserved;
|
|
public ushort SubstituteNameOffset;
|
|
public ushort SubstituteNameLength;
|
|
public ushort PrintNameOffset;
|
|
public ushort PrintNameLength;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
|
|
public byte[] PathBuffer;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct BY_HANDLE_FILE_INFORMATION
|
|
{
|
|
public uint FileAttributes;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
|
|
public uint VolumeSerialNumber;
|
|
public uint FileSizeHigh;
|
|
public uint FileSizeLow;
|
|
public uint NumberOfLinks;
|
|
public uint FileIndexHigh;
|
|
public uint FileIndexLow;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct GUID
|
|
{
|
|
public uint Data1;
|
|
public ushort Data2;
|
|
public ushort Data3;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
public Char[] Data4;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct REPARSE_GUID_DATA_BUFFER
|
|
{
|
|
public uint ReparseTag;
|
|
public ushort ReparseDataLength;
|
|
public ushort Reserved;
|
|
public GUID ReparseGuid;
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_REPARSE_SIZE)]
|
|
public Char[] DataBuffer;
|
|
}
|
|
|
|
[DllImport(PinvokeDllNames.DeviceIoControlDllName, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
|
private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
|
|
IntPtr InBuffer, int nInBufferSize,
|
|
IntPtr OutBuffer, int nOutBufferSize,
|
|
out int pBytesReturned, IntPtr lpOverlapped);
|
|
|
|
#if !CORECLR
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern IntPtr FindFirstFileName(
|
|
string lpFileName,
|
|
uint flags,
|
|
ref UInt32 StringLength,
|
|
StringBuilder LinkName);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern bool FindNextFileName(
|
|
IntPtr hFindStream,
|
|
ref UInt32 StringLength,
|
|
StringBuilder LinkName);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern bool FindClose(IntPtr hFindFile);
|
|
|
|
#endif
|
|
|
|
[DllImport(PinvokeDllNames.GetFileInformationByHandleDllName, SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern bool GetFileInformationByHandle(
|
|
IntPtr hFile,
|
|
out BY_HANDLE_FILE_INFORMATION lpFileInformation);
|
|
|
|
[DllImport(PinvokeDllNames.CreateFileDllName, SetLastError = true, CharSet = CharSet.Unicode)]
|
|
internal static extern IntPtr CreateFile(
|
|
string lpFileName,
|
|
FileDesiredAccess dwDesiredAccess,
|
|
FileShareMode dwShareMode,
|
|
IntPtr lpSecurityAttributes,
|
|
FileCreationDisposition dwCreationDisposition,
|
|
FileAttributes dwFlagsAndAttributes,
|
|
IntPtr hTemplateFile);
|
|
|
|
/// <summary>
|
|
/// Gets the target of the specified reparse point.
|
|
/// </summary>
|
|
/// <param name="instance">The object of FileInfo or DirectoryInfo type.</param>
|
|
/// <returns>The target of the reparse point</returns>
|
|
public static IEnumerable<string> GetTarget(PSObject instance)
|
|
{
|
|
if (instance.BaseObject is FileSystemInfo fileSysInfo)
|
|
{
|
|
#if !UNIX
|
|
using (SafeFileHandle handle = OpenReparsePoint(fileSysInfo.FullName, FileDesiredAccess.GenericRead))
|
|
{
|
|
string linkTarget = WinInternalGetTarget(handle);
|
|
|
|
if (linkTarget != null)
|
|
{
|
|
return (new string[] { linkTarget });
|
|
}
|
|
}
|
|
#endif
|
|
return InternalGetTarget(fileSysInfo.FullName);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the link type of the specified reparse point.
|
|
/// </summary>
|
|
/// <param name="instance">The object of FileInfo or DirectoryInfo type.</param>
|
|
/// <returns>The link type of the reparse point. SymbolicLink for symbolic links.</returns>
|
|
public static string GetLinkType(PSObject instance)
|
|
{
|
|
FileSystemInfo fileSysInfo = instance.BaseObject as FileSystemInfo;
|
|
|
|
if (fileSysInfo != null)
|
|
{
|
|
return InternalGetLinkType(fileSysInfo);
|
|
}
|
|
else
|
|
return null;
|
|
}
|
|
|
|
private static List<string> InternalGetTarget(string filePath)
|
|
{
|
|
var links = new List<string>();
|
|
#if UNIX
|
|
string link = Platform.NonWindowsInternalGetTarget(filePath);
|
|
if (!String.IsNullOrEmpty(link))
|
|
{
|
|
links.Add(link);
|
|
}
|
|
else
|
|
{
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
}
|
|
|
|
#elif !CORECLR //FindFirstFileName, FindNextFileName and FindClose are not available on Core Clr
|
|
UInt32 linkStringLength = 0;
|
|
var linkName = new StringBuilder();
|
|
|
|
// First get the length for the linkName buffer.
|
|
IntPtr fileHandle = InternalSymbolicLinkLinkCodeMethods.FindFirstFileName(filePath, 0, ref linkStringLength, linkName);
|
|
int lastError = Marshal.GetLastWin32Error();
|
|
|
|
// Return handle is INVALID_HANDLE_VALUE and LastError was ERROR_MORE_DATA
|
|
if ((fileHandle == (IntPtr)(-1)) && (lastError == 234))
|
|
{
|
|
linkName = new StringBuilder((int)linkStringLength);
|
|
fileHandle = InternalSymbolicLinkLinkCodeMethods.FindFirstFileName(filePath, 0, ref linkStringLength, linkName);
|
|
lastError = Marshal.GetLastWin32Error();
|
|
}
|
|
|
|
if (fileHandle == (IntPtr)(-1))
|
|
{
|
|
throw new Win32Exception(lastError);
|
|
}
|
|
|
|
bool continueFind = false;
|
|
|
|
try
|
|
{
|
|
do
|
|
{
|
|
StringBuilder fullName = new StringBuilder();
|
|
fullName.Append(Path.GetPathRoot(filePath)); //hard link source and target must be on the same drive. So we can use the source for find the path root.
|
|
fullName.Append(linkName.ToString());
|
|
FileInfo fInfo = new FileInfo(fullName.ToString());
|
|
|
|
//Don't add the target link to the list.
|
|
|
|
if (String.Compare(fInfo.FullName, filePath, StringComparison.OrdinalIgnoreCase) != 0)
|
|
links.Add(fInfo.FullName);
|
|
|
|
continueFind = InternalSymbolicLinkLinkCodeMethods.FindNextFileName(fileHandle, ref linkStringLength, linkName);
|
|
|
|
lastError = Marshal.GetLastWin32Error();
|
|
|
|
if (!continueFind && lastError == 234) // ERROR_MORE_DATA
|
|
{
|
|
linkName = new StringBuilder((int)linkStringLength);
|
|
continueFind = InternalSymbolicLinkLinkCodeMethods.FindNextFileName(fileHandle, ref linkStringLength, linkName);
|
|
}
|
|
|
|
if (!continueFind && lastError != 38) //ERROR_HANDLE_EOF. No more links.
|
|
{
|
|
throw new Win32Exception(lastError);
|
|
}
|
|
}
|
|
while (continueFind);
|
|
}
|
|
finally
|
|
{
|
|
InternalSymbolicLinkLinkCodeMethods.FindClose(fileHandle);
|
|
}
|
|
#endif
|
|
return links;
|
|
}
|
|
|
|
private static string InternalGetLinkType(FileSystemInfo fileInfo)
|
|
{
|
|
if (Platform.IsWindows)
|
|
{
|
|
return WinInternalGetLinkType(fileInfo.FullName);
|
|
}
|
|
else
|
|
{
|
|
return Platform.NonWindowsInternalGetLinkType(fileInfo);
|
|
}
|
|
}
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
|
|
private static string WinInternalGetLinkType(string filePath)
|
|
{
|
|
if (!Platform.IsWindows)
|
|
{
|
|
throw new PlatformNotSupportedException();
|
|
}
|
|
|
|
using (SafeFileHandle handle = OpenReparsePoint(filePath, FileDesiredAccess.GenericRead))
|
|
{
|
|
int outBufferSize = Marshal.SizeOf<REPARSE_DATA_BUFFER_SYMBOLICLINK>();
|
|
|
|
IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize);
|
|
bool success = false;
|
|
|
|
try
|
|
{
|
|
int bytesReturned;
|
|
string linkType = null;
|
|
|
|
//OACR warning 62001 about using DeviceIOControl has been disabled.
|
|
// According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
|
|
|
|
handle.DangerousAddRef(ref success);
|
|
|
|
//Get Buffer size
|
|
IntPtr dangerousHandle = handle.DangerousGetHandle();
|
|
|
|
bool result = DeviceIoControl(dangerousHandle, FSCTL_GET_REPARSE_POINT,
|
|
IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);
|
|
|
|
if (!result)
|
|
{
|
|
int lastError = Marshal.GetLastWin32Error();
|
|
if (lastError == ERROR_NOT_A_REPARSE_POINT)
|
|
linkType = null;
|
|
else
|
|
throw new Win32Exception(lastError);
|
|
}
|
|
|
|
REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_SYMBOLICLINK>(outBuffer);
|
|
|
|
if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_SYMLINK)
|
|
linkType = "SymbolicLink";
|
|
else if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
|
|
linkType = "Junction";
|
|
else
|
|
{
|
|
linkType = IsHardLink(ref dangerousHandle) ? "HardLink" : null;
|
|
}
|
|
|
|
return linkType;
|
|
}
|
|
finally
|
|
{
|
|
if (success)
|
|
{
|
|
handle.DangerousRelease();
|
|
}
|
|
|
|
Marshal.FreeHGlobal(outBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static bool IsHardLink(FileSystemInfo fileInfo)
|
|
{
|
|
#if UNIX
|
|
return Platform.NonWindowsIsHardLink(fileInfo);
|
|
#else
|
|
return WinIsHardLink(fileInfo);
|
|
#endif
|
|
}
|
|
|
|
internal static bool IsReparsePoint(FileSystemInfo fileInfo)
|
|
{
|
|
if (Platform.IsWindows)
|
|
{
|
|
// Note that this class also has a enum called FileAttributes, so use fully qualified name
|
|
return (fileInfo.Attributes & System.IO.FileAttributes.ReparsePoint)
|
|
== System.IO.FileAttributes.ReparsePoint;
|
|
}
|
|
else
|
|
{
|
|
return Platform.NonWindowsIsSymLink(fileInfo);
|
|
}
|
|
}
|
|
|
|
internal static bool WinIsHardLink(FileSystemInfo fileInfo)
|
|
{
|
|
bool isHardLink = false;
|
|
|
|
// only check for hard link if the item is not directory
|
|
if (!((fileInfo.Attributes & System.IO.FileAttributes.Directory) == System.IO.FileAttributes.Directory))
|
|
{
|
|
IntPtr nativeHandle = InternalSymbolicLinkLinkCodeMethods.CreateFile(
|
|
fileInfo.FullName,
|
|
InternalSymbolicLinkLinkCodeMethods.FileDesiredAccess.GenericRead,
|
|
InternalSymbolicLinkLinkCodeMethods.FileShareMode.Read,
|
|
IntPtr.Zero,
|
|
InternalSymbolicLinkLinkCodeMethods.FileCreationDisposition.OpenExisting,
|
|
InternalSymbolicLinkLinkCodeMethods.FileAttributes.Normal,
|
|
IntPtr.Zero);
|
|
|
|
using (SafeFileHandle handle = new SafeFileHandle(nativeHandle, true))
|
|
{
|
|
bool success = false;
|
|
|
|
try
|
|
{
|
|
handle.DangerousAddRef(ref success);
|
|
IntPtr dangerousHandle = handle.DangerousGetHandle();
|
|
isHardLink = InternalSymbolicLinkLinkCodeMethods.IsHardLink(ref dangerousHandle);
|
|
}
|
|
finally
|
|
{
|
|
if (success)
|
|
handle.DangerousRelease();
|
|
}
|
|
}
|
|
}
|
|
|
|
return isHardLink;
|
|
}
|
|
|
|
internal static bool IsSameFileSystemItem(string pathOne, string pathTwo)
|
|
{
|
|
#if UNIX
|
|
return Platform.NonWindowsIsSameFileSystemItem(pathOne, pathTwo);
|
|
#else
|
|
return WinIsSameFileSystemItem(pathOne, pathTwo);
|
|
#endif
|
|
}
|
|
|
|
#if !UNIX
|
|
private static bool WinIsSameFileSystemItem(string pathOne, string pathTwo)
|
|
{
|
|
var access = FileAccess.Read;
|
|
var share = FileShare.Read;
|
|
var creation = FileMode.Open;
|
|
var attributes = FileAttributes.BackupSemantics | FileAttributes.PosixSemantics;
|
|
|
|
using (var sfOne = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathOne, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero))
|
|
using (var sfTwo = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathTwo, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero))
|
|
{
|
|
if (!sfOne.IsInvalid && !sfTwo.IsInvalid)
|
|
{
|
|
BY_HANDLE_FILE_INFORMATION infoOne;
|
|
BY_HANDLE_FILE_INFORMATION infoTwo;
|
|
if ( GetFileInformationByHandle(sfOne.DangerousGetHandle(), out infoOne)
|
|
&& GetFileInformationByHandle(sfTwo.DangerousGetHandle(), out infoTwo))
|
|
{
|
|
return infoOne.VolumeSerialNumber == infoTwo.VolumeSerialNumber
|
|
&& infoOne.FileIndexHigh == infoTwo.FileIndexHigh
|
|
&& infoOne.FileIndexLow == infoTwo.FileIndexLow;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
internal static bool GetInodeData(string path, out System.ValueTuple<UInt64, UInt64> inodeData)
|
|
{
|
|
#if UNIX
|
|
bool rv = Platform.NonWindowsGetInodeData(path, out inodeData);
|
|
#else
|
|
bool rv = WinGetInodeData(path, out inodeData);
|
|
#endif
|
|
return rv;
|
|
}
|
|
|
|
#if !UNIX
|
|
private static bool WinGetInodeData(string path, out System.ValueTuple<UInt64, UInt64> inodeData)
|
|
{
|
|
var access = FileAccess.Read;
|
|
var share = FileShare.Read;
|
|
var creation = FileMode.Open;
|
|
var attributes = FileAttributes.BackupSemantics | FileAttributes.PosixSemantics;
|
|
|
|
using (var sf = AlternateDataStreamUtilities.NativeMethods.CreateFile(path, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero))
|
|
{
|
|
if (!sf.IsInvalid)
|
|
{
|
|
BY_HANDLE_FILE_INFORMATION info;
|
|
|
|
if (GetFileInformationByHandle(sf.DangerousGetHandle(), out info))
|
|
{
|
|
UInt64 tmp = info.FileIndexHigh;
|
|
tmp = (tmp << 32) | info.FileIndexLow;
|
|
|
|
inodeData = (info.VolumeSerialNumber, tmp);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
inodeData = (0, 0);
|
|
return false;
|
|
}
|
|
#endif
|
|
internal static bool IsHardLink(ref IntPtr handle)
|
|
{
|
|
#if UNIX
|
|
return Platform.NonWindowsIsHardLink(ref handle);
|
|
#else
|
|
return WinIsHardLink(ref handle);
|
|
#endif
|
|
}
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
|
|
internal static bool WinIsHardLink(ref IntPtr handle)
|
|
{
|
|
BY_HANDLE_FILE_INFORMATION handleInfo;
|
|
bool succeeded = InternalSymbolicLinkLinkCodeMethods.GetFileInformationByHandle(handle, out handleInfo);
|
|
return succeeded && (handleInfo.NumberOfLinks > 1);
|
|
}
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
|
|
private static string WinInternalGetTarget(SafeFileHandle handle)
|
|
{
|
|
int outBufferSize = Marshal.SizeOf<REPARSE_DATA_BUFFER_SYMBOLICLINK>();
|
|
|
|
IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize);
|
|
bool success = false;
|
|
|
|
try
|
|
{
|
|
int bytesReturned;
|
|
|
|
//OACR warning 62001 about using DeviceIOControl has been disabled.
|
|
// According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
|
|
|
|
handle.DangerousAddRef(ref success);
|
|
|
|
bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT,
|
|
IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);
|
|
|
|
if (!result)
|
|
{
|
|
int lastError = Marshal.GetLastWin32Error();
|
|
if (lastError == ERROR_NOT_A_REPARSE_POINT)
|
|
return null;
|
|
|
|
throw new Win32Exception(lastError);
|
|
}
|
|
|
|
//Unmarshal to symbolic link to look for tags.
|
|
REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_SYMBOLICLINK>(outBuffer);
|
|
|
|
if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_SYMLINK && reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
|
|
return null;
|
|
|
|
string targetDir = null;
|
|
|
|
if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_SYMLINK)
|
|
{
|
|
targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength);
|
|
}
|
|
|
|
if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
|
|
{
|
|
//Since this is a junction we need to unmarshal to the correct structure.
|
|
REPARSE_DATA_BUFFER_MOUNTPOINT reparseDataBufferMountPoint = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_MOUNTPOINT>(outBuffer);
|
|
|
|
targetDir = Encoding.Unicode.GetString(reparseDataBufferMountPoint.PathBuffer, reparseDataBufferMountPoint.SubstituteNameOffset, reparseDataBufferMountPoint.SubstituteNameLength);
|
|
}
|
|
|
|
if (targetDir.StartsWith(NonInterpretedPathPrefix, StringComparison.OrdinalIgnoreCase))
|
|
targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length);
|
|
|
|
return targetDir;
|
|
}
|
|
finally
|
|
{
|
|
if (success)
|
|
{
|
|
handle.DangerousRelease();
|
|
}
|
|
|
|
Marshal.FreeHGlobal(outBuffer);
|
|
}
|
|
}
|
|
|
|
internal static bool CreateJunction(string path, string target)
|
|
{
|
|
// this is a purely Windows specific feature, no feature flag
|
|
// used for that reason
|
|
if (Platform.IsWindows)
|
|
{
|
|
return WinCreateJunction(path, target);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
|
|
private static bool WinCreateJunction(string path, string target)
|
|
{
|
|
if (!String.IsNullOrEmpty(path))
|
|
{
|
|
if (!String.IsNullOrEmpty(target))
|
|
{
|
|
using (SafeHandle handle = OpenReparsePoint(path, FileDesiredAccess.GenericWrite))
|
|
{
|
|
byte[] mountPointBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target));
|
|
|
|
REPARSE_DATA_BUFFER_MOUNTPOINT mountPoint = new REPARSE_DATA_BUFFER_MOUNTPOINT();
|
|
mountPoint.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
|
|
mountPoint.ReparseDataLength = (ushort)(mountPointBytes.Length + 12); //Added space for the header and null endo
|
|
mountPoint.SubstituteNameOffset = 0;
|
|
mountPoint.SubstituteNameLength = (ushort)mountPointBytes.Length;
|
|
mountPoint.PrintNameOffset = (ushort)(mountPointBytes.Length + 2); // 2 as unicode null take 2 bytes.
|
|
mountPoint.PrintNameLength = 0;
|
|
mountPoint.PathBuffer = new byte[0x3FF0]; //Buffer for max size.
|
|
Array.Copy(mountPointBytes, mountPoint.PathBuffer, mountPointBytes.Length);
|
|
|
|
int nativeBufferSize = Marshal.SizeOf(mountPoint);
|
|
IntPtr nativeBuffer = Marshal.AllocHGlobal(nativeBufferSize);
|
|
bool success = false;
|
|
|
|
try
|
|
{
|
|
Marshal.StructureToPtr(mountPoint, nativeBuffer, false);
|
|
|
|
int bytesReturned = 0;
|
|
|
|
//OACR warning 62001 about using DeviceIOControl has been disabled.
|
|
// According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
|
|
|
|
handle.DangerousAddRef(ref success);
|
|
|
|
bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, nativeBuffer, mountPointBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);
|
|
|
|
if (!result)
|
|
{
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
finally
|
|
{
|
|
Marshal.FreeHGlobal(nativeBuffer);
|
|
|
|
if (success)
|
|
{
|
|
handle.DangerousRelease();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentNullException("target");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentNullException("path");
|
|
}
|
|
}
|
|
|
|
private static SafeFileHandle OpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode)
|
|
{
|
|
#if UNIX
|
|
throw new PlatformNotSupportedException();
|
|
#else
|
|
return WinOpenReparsePoint(reparsePoint, accessMode);
|
|
#endif
|
|
}
|
|
|
|
private static SafeFileHandle WinOpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode)
|
|
{
|
|
IntPtr nativeHandle = CreateFile(reparsePoint, accessMode,
|
|
FileShareMode.Read | FileShareMode.Write | FileShareMode.Delete,
|
|
IntPtr.Zero, FileCreationDisposition.OpenExisting,
|
|
FileAttributes.BackupSemantics | FileAttributes.OpenReparsePoint,
|
|
IntPtr.Zero);
|
|
|
|
int lastError = Marshal.GetLastWin32Error();
|
|
|
|
if (lastError != 0)
|
|
throw new Win32Exception(lastError);
|
|
|
|
SafeFileHandle reparsePointHandle = new SafeFileHandle(nativeHandle, true);
|
|
|
|
return reparsePointHandle;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
namespace System.Management.Automation.Internal
|
|
{
|
|
#if !UNIX
|
|
#region AlternateDataStreamUtilities
|
|
|
|
/// <summary>
|
|
/// Represents alternate stream data retrieved from a file.
|
|
/// </summary>
|
|
public class AlternateStreamData
|
|
{
|
|
/// <summary>
|
|
/// The name of the file that holds this stream.
|
|
/// </summary>
|
|
public string FileName { get; set; }
|
|
|
|
/// <summary>
|
|
/// The name of this stream.
|
|
/// </summary>
|
|
public string Stream { get; set; }
|
|
|
|
/// <summary>
|
|
/// The length of this stream.
|
|
/// </summary>
|
|
public long Length { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides access to alternate data streams on a file
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes",
|
|
Justification = "Needed by both the FileSystem provider and Unblock-File cmdlet.")]
|
|
public static class AlternateDataStreamUtilities
|
|
{
|
|
/// <summary>
|
|
/// List all of the streams on a file
|
|
/// </summary>
|
|
/// <param name="path">The fully-qualified path to the file.</param>
|
|
/// <returns>The list of streams (and their size) in the file.</returns>
|
|
internal static List<AlternateStreamData> GetStreams(string path)
|
|
{
|
|
if (path == null) throw new ArgumentNullException("path");
|
|
|
|
List<AlternateStreamData> alternateStreams = new List<AlternateStreamData>();
|
|
|
|
AlternateStreamNativeData findStreamData = new AlternateStreamNativeData();
|
|
|
|
SafeFindHandle handle = NativeMethods.FindFirstStreamW(
|
|
path, NativeMethods.StreamInfoLevels.FindStreamInfoStandard,
|
|
findStreamData, 0);
|
|
if (handle.IsInvalid) throw new Win32Exception();
|
|
|
|
try
|
|
{
|
|
do
|
|
{
|
|
// Remove the leading ':'
|
|
findStreamData.Name = findStreamData.Name.Substring(1);
|
|
|
|
// And trailing :$DATA (as long as it's not the default data stream)
|
|
string dataStream = ":$DATA";
|
|
if (!String.Equals(findStreamData.Name, dataStream, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
findStreamData.Name = findStreamData.Name.Replace(dataStream, string.Empty);
|
|
}
|
|
|
|
AlternateStreamData data = new AlternateStreamData();
|
|
data.Stream = findStreamData.Name;
|
|
data.Length = findStreamData.Length;
|
|
data.FileName = path.Replace(data.Stream, string.Empty);
|
|
data.FileName = data.FileName.Trim(Utils.Separators.Colon);
|
|
|
|
alternateStreams.Add(data);
|
|
findStreamData = new AlternateStreamNativeData();
|
|
}
|
|
while (NativeMethods.FindNextStreamW(handle, findStreamData));
|
|
|
|
int lastError = Marshal.GetLastWin32Error();
|
|
if (lastError != NativeMethods.ERROR_HANDLE_EOF)
|
|
throw new Win32Exception(lastError);
|
|
}
|
|
finally { handle.Dispose(); }
|
|
|
|
return alternateStreams;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a file stream on a file
|
|
/// </summary>
|
|
/// <param name="path">The fully-qualified path to the file.</param>
|
|
/// <param name="streamName">The name of the alternate data stream to open.</param>
|
|
/// <param name="mode">The FileMode of the file.</param>
|
|
/// <param name="access">The FileAccess of the file.</param>
|
|
/// <param name="share">The FileShare of the file.</param>
|
|
/// <returns>A FileStream that can be used to interact with the file.</returns>
|
|
internal static FileStream CreateFileStream(string path, string streamName, FileMode mode, FileAccess access, FileShare share)
|
|
{
|
|
if (!TryCreateFileStream(path, streamName, mode, access, share, out var stream))
|
|
{
|
|
string errorMessage = StringUtil.Format(
|
|
FileSystemProviderStrings.AlternateDataStreamNotFound, streamName, path);
|
|
throw new FileNotFoundException(errorMessage, $"{path}:{streamName}");
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to create a file stream on a file.
|
|
/// </summary>
|
|
/// <param name="path">The fully-qualified path to the file.</param>
|
|
/// <param name="streamName">The name of the alternate data stream to open.</param>
|
|
/// <param name="mode">The FileMode of the file.</param>
|
|
/// <param name="access">The FileAccess of the file.</param>
|
|
/// <param name="share">The FileShare of the file.</param>
|
|
/// <param name="stream">A FileStream that can be used to interact with the file.</param>
|
|
/// <returns>true if the stream was successfully created, otherwise false.</returns>
|
|
internal static bool TryCreateFileStream(string path, string streamName, FileMode mode, FileAccess access, FileShare share, out FileStream stream)
|
|
{
|
|
if (path == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(path));
|
|
}
|
|
|
|
if (streamName == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(streamName));
|
|
}
|
|
|
|
if (mode == FileMode.Append)
|
|
{
|
|
mode = FileMode.OpenOrCreate;
|
|
}
|
|
|
|
var resultPath = $"{path}:{streamName}";
|
|
SafeFileHandle handle = NativeMethods.CreateFile(resultPath, access, share, IntPtr.Zero, mode, 0, IntPtr.Zero);
|
|
|
|
if (handle.IsInvalid)
|
|
{
|
|
stream = null;
|
|
return false;
|
|
}
|
|
|
|
stream = new FileStream(handle, access);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an alternate data stream.
|
|
/// </summary>
|
|
/// <param name="path">The path to the file.</param>
|
|
/// <param name="streamName">The name of the alternate data stream to delete.</param>
|
|
internal static void DeleteFileStream(string path, string streamName)
|
|
{
|
|
if (path == null) throw new ArgumentNullException("path");
|
|
if (streamName == null) throw new ArgumentNullException("streamName");
|
|
|
|
string adjustedStreamName = streamName.Trim();
|
|
if (adjustedStreamName.IndexOf(':') != 0)
|
|
{
|
|
adjustedStreamName = ":" + adjustedStreamName;
|
|
}
|
|
string resultPath = path + adjustedStreamName;
|
|
|
|
File.Delete(resultPath);
|
|
}
|
|
|
|
internal static void SetZoneOfOrigin(string path, SecurityZone securityZone)
|
|
{
|
|
using (FileStream fileStream = CreateFileStream(path, "Zone.Identifier", FileMode.Create, FileAccess.Write, FileShare.None))
|
|
using (TextWriter textWriter = new StreamWriter(fileStream, Encoding.Unicode))
|
|
{
|
|
textWriter.WriteLine("[ZoneTransfer]");
|
|
textWriter.WriteLine("ZoneId={0}", (int)securityZone);
|
|
}
|
|
|
|
// an alternative is to use IAttachmentExecute interface as described here:
|
|
// http://joco.name/2010/12/22/windows-antivirus-api-in-net-and-a-com-interop-crash-course/
|
|
// the code above seems cleaner and more robust than the IAttachmentExecute approach
|
|
}
|
|
|
|
internal static class NativeMethods
|
|
{
|
|
internal const int ERROR_HANDLE_EOF = 38;
|
|
internal enum StreamInfoLevels { FindStreamInfoStandard = 0 }
|
|
|
|
[DllImport(PinvokeDllNames.CreateFileDllName, CharSet = CharSet.Unicode, SetLastError = true)]
|
|
internal static extern SafeFileHandle CreateFile(string lpFileName,
|
|
FileAccess dwDesiredAccess, FileShare dwShareMode,
|
|
IntPtr lpSecurityAttributes, FileMode dwCreationDisposition,
|
|
int dwFlagsAndAttributes, IntPtr hTemplateFile);
|
|
|
|
[DllImport(PinvokeDllNames.FindFirstStreamDllName, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
|
|
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "AlternateStreamNativeData.Name")]
|
|
internal static extern SafeFindHandle FindFirstStreamW(
|
|
string lpFileName, StreamInfoLevels InfoLevel,
|
|
[In, Out, MarshalAs(UnmanagedType.LPStruct)]
|
|
AlternateStreamNativeData lpFindStreamData, uint dwFlags);
|
|
|
|
[DllImport(PinvokeDllNames.FindNextStreamDllName, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "AlternateStreamNativeData.Name")]
|
|
internal static extern bool FindNextStreamW(
|
|
SafeFindHandle hndFindFile,
|
|
[In, Out, MarshalAs(UnmanagedType.LPStruct)]
|
|
AlternateStreamNativeData lpFindStreamData);
|
|
}
|
|
|
|
internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid
|
|
{
|
|
private SafeFindHandle() : base(true) { }
|
|
|
|
protected override bool ReleaseHandle()
|
|
{
|
|
return FindClose(this.handle);
|
|
}
|
|
|
|
[DllImport(PinvokeDllNames.FindCloseDllName)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static extern bool FindClose(IntPtr handle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents alternate stream data retrieved from a file.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
internal class AlternateStreamNativeData
|
|
{
|
|
/// <summary>
|
|
/// The length of this stream.
|
|
/// </summary>
|
|
public long Length;
|
|
|
|
/// <summary>
|
|
/// The name of this stream.
|
|
/// </summary>
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)]
|
|
public string Name;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
#endif
|
|
|
|
#region CopyFileFromRemoteUtils
|
|
|
|
internal static class CopyFileRemoteUtils
|
|
{
|
|
private const string functionToken = "function ";
|
|
private const string nameToken = "Name";
|
|
private const string definitionToken = "Definition";
|
|
|
|
#region PSCopyToSessionHelper
|
|
|
|
internal const string PSCopyToSessionHelperName = @"PSCopyToSessionHelper";
|
|
private static string s_driveMaxSizeErrorFormatString = FileSystemProviderStrings.DriveMaxSizeError;
|
|
private static string s_PSCopyToSessionHelperDefinition = StringUtil.Format(PSCopyToSessionHelperDefinitionFormat, @"[ValidateNotNullOrEmpty()]", s_driveMaxSizeErrorFormatString);
|
|
private static string s_PSCopyToSessionHelperDefinitionRestricted = StringUtil.Format(PSCopyToSessionHelperDefinitionFormat, @"[ValidateUserDrive()]", s_driveMaxSizeErrorFormatString);
|
|
private const string PSCopyToSessionHelperDefinitionFormat = @"
|
|
param (
|
|
[Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")]
|
|
[Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"")]
|
|
{0}
|
|
[string] $copyToFilePath,
|
|
|
|
[Parameter(ParameterSetName=""PSCopyFileToRemoteSession"", Mandatory=$false)]
|
|
[Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"")]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $b64Fragment,
|
|
|
|
[Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")]
|
|
[switch] $createFile = $false,
|
|
|
|
[Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")]
|
|
[switch] $emptyFile = $false,
|
|
|
|
[Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"", Mandatory=$true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $streamName,
|
|
|
|
[Parameter(ParameterSetName=""PSTargetSupportsAlternateStreams"", Mandatory=$true)]
|
|
{0}
|
|
[string] $supportAltStreamPath,
|
|
|
|
[Parameter(ParameterSetName=""PSSetFileMetadata"", Mandatory=$true)]
|
|
{0}
|
|
[string] $metaDataFilePath,
|
|
|
|
[Parameter(ParameterSetName=""PSSetFileMetadata"", Mandatory=$true)]
|
|
[ValidateNotNull()]
|
|
[hashtable] $metaDataToSet,
|
|
|
|
[Parameter(ParameterSetName=""PSRemoteDestinationPathIsFile"", Mandatory=$true)]
|
|
{0}
|
|
[string] $isFilePath,
|
|
|
|
[Parameter(ParameterSetName=""PSGetRemotePathInfo"", Mandatory=$true)]
|
|
{0}
|
|
[string] $remotePath,
|
|
|
|
[Parameter(ParameterSetName=""PSCreateDirectoryOnRemoteSession"", Mandatory=$true)]
|
|
{0}
|
|
[string] $createDirectoryPath,
|
|
|
|
[Parameter(ParameterSetName=""PSCreateDirectoryOnRemoteSession"")]
|
|
[switch] $force
|
|
)
|
|
|
|
# Checks if path drive specifies max size and if max size is exceeded
|
|
#
|
|
function CheckPSDriveSize
|
|
{{
|
|
param (
|
|
[System.Management.Automation.PathInfo] $resolvedPath,
|
|
[int] $fragmentLength
|
|
)
|
|
|
|
if (($null -ne $resolvedPath.Drive) -and ($null -ne $resolvedPath.Drive.MaximumSize))
|
|
{{
|
|
$maxUserSize = $resolvedPath.Drive.MaximumSize
|
|
$dirSize = 0
|
|
Microsoft.PowerShell.Management\Get-ChildItem -LiteralPath ($resolvedPath.Drive.Name + "":"") -Recurse | ForEach-Object {{
|
|
Microsoft.PowerShell.Management\Get-Item -LiteralPath $_.FullName -Stream * | ForEach-Object {{ $dirSize += $_.Length }}
|
|
}}
|
|
if (($dirSize + $fragmentLength) -gt $maxUserSize)
|
|
{{
|
|
$msg = ""{1}"" -f $maxUserSize
|
|
throw $msg
|
|
}}
|
|
}}
|
|
}}
|
|
|
|
# Return a hashtable with the following members:
|
|
# BytesWritten - the number of bytes written to a file
|
|
#
|
|
function PSCopyFileToRemoteSession
|
|
{{
|
|
param(
|
|
[string] $copyToFilePath,
|
|
[string] $b64Fragment,
|
|
[switch] $createFile = $false,
|
|
[switch] $emptyFile = $false
|
|
)
|
|
|
|
$op = @{{
|
|
BytesWritten = $null
|
|
}}
|
|
|
|
$wstream = $null
|
|
|
|
try
|
|
{{
|
|
$filePathExists = Microsoft.PowerShell.Management\Test-Path -Path $copyToFilePath
|
|
|
|
if ($createFile -or (! $filePathExists))
|
|
{{
|
|
# If the file already exists, try to delete it.
|
|
if ($filePathExists)
|
|
{{
|
|
Microsoft.PowerShell.Management\Remove-Item -Path $copyToFilePath -Force -ea SilentlyContinue
|
|
}}
|
|
|
|
# Create the new file.
|
|
$fileInfo = Microsoft.PowerShell.Management\New-Item -Path $copyToFilePath -Type File -Force
|
|
|
|
if ($emptyFile)
|
|
{{
|
|
# Handle the empty file scenario.
|
|
$op['BytesWritten'] = 0
|
|
return $op
|
|
}}
|
|
}}
|
|
|
|
# Resolve path in case it is a PSDrive
|
|
$resolvedPath = Microsoft.PowerShell.Management\Resolve-Path -literal $copyToFilePath
|
|
|
|
# Decode
|
|
$fragment = [System.Convert]::FromBase64String($b64Fragment)
|
|
|
|
# Check if drive specifies max size and if max size is exceeded
|
|
CheckPSDriveSize $resolvedPath $fragment.Length
|
|
|
|
# Write fragment
|
|
$wstream = Microsoft.PowerShell.Utility\New-Object -TypeName IO.FileStream -ArgumentList ($resolvedPath.ProviderPath), ([System.IO.FileMode]::Append)
|
|
$wstream.Write($fragment, 0, $fragment.Length)
|
|
|
|
$op['BytesWritten'] = $fragment.Length
|
|
}}
|
|
catch
|
|
{{
|
|
if ($_.Exception.InnerException)
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
|
|
}}
|
|
else
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
|
|
}}
|
|
}}
|
|
finally
|
|
{{
|
|
if ($null -ne $wstream)
|
|
{{
|
|
$wstream.Dispose()
|
|
}}
|
|
}}
|
|
|
|
return $op
|
|
}}
|
|
|
|
# Returns a hashtable with the following members:
|
|
# BytesWritten - number of bytes written to an alternate file stream
|
|
#
|
|
function PSCopyFileAlternateStreamToRemoteSession
|
|
{{
|
|
param (
|
|
[string] $copyToFilePath,
|
|
[string] $b64Fragment,
|
|
[string] $streamName
|
|
)
|
|
|
|
$op = @{{
|
|
BytesWritten = $null
|
|
}}
|
|
|
|
try
|
|
{{
|
|
# Resolve path in case it is a PSDrive
|
|
$resolvedPath = Microsoft.PowerShell.Management\Resolve-Path -literal $copyToFilePath
|
|
|
|
# Decode
|
|
$fragment = [System.Convert]::FromBase64String($b64Fragment)
|
|
|
|
# Check if drive specifies max size and if max size is exceeded
|
|
CheckPSDriveSize $resolvedPath $fragment.Length
|
|
|
|
# Write the stream
|
|
Microsoft.PowerShell.Management\Add-Content -Path ($resolvedPath.ProviderPath) -Value $fragment -Encoding Byte -Stream $streamName -ErrorAction Stop
|
|
$op['BytesWritten'] = $fragment.Length
|
|
}}
|
|
catch
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
|
|
}}
|
|
|
|
return $op
|
|
}}
|
|
|
|
# Returns a hashtable with the following member:
|
|
# TargetSupportsAlternateStreams - boolean to keep track of whether the target supports Alternate data streams.
|
|
#
|
|
function PSTargetSupportsAlternateStreams
|
|
{{
|
|
param (
|
|
[string] $supportAltStreamPath
|
|
)
|
|
|
|
$result = @{{
|
|
TargetSupportsAlternateStreams = $false
|
|
}}
|
|
|
|
# Resolve path in case it is a PSDrive
|
|
$resolvedPath = Microsoft.PowerShell.Management\Resolve-Path -literal $supportAltStreamPath
|
|
|
|
$targetDrive = [IO.Path]::GetPathRoot($resolvedPath.ProviderPath)
|
|
if (-not $targetDrive)
|
|
{{
|
|
return $result
|
|
}}
|
|
|
|
# Check if the target drive is NTFS
|
|
$driveFormat = 'NTFS'
|
|
foreach ($drive in [System.IO.DriveInfo]::GetDrives())
|
|
{{
|
|
if (($drive.Name -eq $targetDrive) -and ($drive.DriveFormat -eq $driveFormat))
|
|
{{
|
|
# Now, check if the target supports Add-Command -Stream. This functionality was introduced in version 3.0.
|
|
$addContentCmdlet = Microsoft.PowerShell.Core\Get-Command Microsoft.PowerShell.Management\Add-Content -ErrorAction SilentlyContinue
|
|
if ($addContentCmdlet.Parameters.Keys -contains 'Stream')
|
|
{{
|
|
$result['TargetSupportsAlternateStreams'] = $true
|
|
break
|
|
}}
|
|
}}
|
|
}}
|
|
|
|
return $result
|
|
}}
|
|
|
|
# Sets the metadata for the given file.
|
|
#
|
|
function PSSetFileMetadata
|
|
{{
|
|
param (
|
|
[string] $metaDataFilePath,
|
|
[hashtable] $metaDataToSet
|
|
)
|
|
|
|
$item = Microsoft.PowerShell.Management\get-item $metaDataFilePath -ea SilentlyContinue -Force
|
|
|
|
if ($item)
|
|
{{
|
|
# LastWriteTime
|
|
if ($metaDataToSet['LastWriteTimeUtc'])
|
|
{{
|
|
$item.LastWriteTimeUtc = $metaDataToSet['LastWriteTimeUtc']
|
|
}}
|
|
if ($metaDataToSet['LastWriteTime'])
|
|
{{
|
|
$item.LastWriteTime = $metaDataToSet['LastWriteTime']
|
|
}}
|
|
|
|
# Attributes
|
|
if ($metaDataToSet['Attributes'])
|
|
{{
|
|
$item.Attributes = $metaDataToSet['Attributes']
|
|
}}
|
|
}}
|
|
}}
|
|
|
|
# Returns a hashtable with the following member:
|
|
# IsFileInfo - boolean to keep track of whether the given path is a remote file.
|
|
# IsDirectoryInfo - boolean to keep track of whether the given path is a remote directory.
|
|
# ParentIsDirectoryInfo - boolean to keep track of whether the given parent path is a remote directory.
|
|
#
|
|
function PSGetRemotePathInfo
|
|
{{
|
|
param (
|
|
[string] $remotePath
|
|
)
|
|
|
|
try
|
|
{{
|
|
|
|
try
|
|
{{
|
|
$parentPath = Microsoft.PowerShell.Management\Split-Path $remotePath
|
|
}}
|
|
# catch everything and ignore the error.
|
|
catch {{}}
|
|
|
|
$result = @{{
|
|
IsFileInfo = (Microsoft.PowerShell.Management\Test-Path $remotePath -PathType Leaf)
|
|
IsDirectoryInfo = (Microsoft.PowerShell.Management\Test-Path $remotePath -PathType Container)
|
|
}}
|
|
|
|
if ($parentPath)
|
|
{{
|
|
$result['ParentIsDirectoryInfo'] = (Microsoft.PowerShell.Management\Test-Path $parentPath -PathType Container)
|
|
}}
|
|
|
|
}}
|
|
catch
|
|
{{
|
|
if ($_.Exception.InnerException)
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
|
|
}}
|
|
else
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
|
|
}}
|
|
}}
|
|
|
|
return $result
|
|
}}
|
|
|
|
# Returns a hashtable with the following information:
|
|
# - IsFileInfotrue bool to keep track if the given destination is a FileInfo type.
|
|
function PSRemoteDestinationPathIsFile
|
|
{{
|
|
param (
|
|
[string] $isFilePath
|
|
)
|
|
|
|
$op = @{{
|
|
IsFileInfo = $null
|
|
}}
|
|
|
|
try
|
|
{{
|
|
$op['IsFileInfo'] = (Microsoft.PowerShell.Management\Test-Path $isFilePath -PathType Leaf)
|
|
}}
|
|
catch
|
|
{{
|
|
if ($_.Exception.InnerException)
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
|
|
}}
|
|
else
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
|
|
}}
|
|
}}
|
|
|
|
return $op
|
|
}}
|
|
|
|
# Return a hash table in the following format:
|
|
# DirectoryPath is the directory to be created.
|
|
# PathExists is a bool to to keep track of whether the directory already exist.
|
|
#
|
|
# 1) If DirectoryPath already exists:
|
|
# a) If -Force is specified, force create the directory. Set DirectoryPath to the created directory path.
|
|
# b) If not -Force is specified, then set PathExists to $true.
|
|
# 2) If DirectoryPath does not exist, create it. Set DirectoryPath to the created directory path.
|
|
function PSCreateDirectoryOnRemoteSession
|
|
{{
|
|
param (
|
|
[string] $createDirectoryPath,
|
|
[switch] $force = $false
|
|
)
|
|
|
|
$op = @{{
|
|
DirectoryPath = $null
|
|
PathExists = $false
|
|
}}
|
|
|
|
try
|
|
{{
|
|
if (Microsoft.PowerShell.Management\Test-Path $createDirectoryPath)
|
|
{{
|
|
# -Force is specified, then force create the directory.
|
|
if ($force)
|
|
{{
|
|
Microsoft.PowerShell.Management\New-Item $createDirectoryPath -ItemType Directory -Force | Out-Null
|
|
$op['DirectoryPath'] = $createDirectoryPath
|
|
}}
|
|
else
|
|
{{
|
|
$op['PathExists'] = $true
|
|
}}
|
|
}}
|
|
else
|
|
{{
|
|
Microsoft.PowerShell.Management\New-Item $createDirectoryPath -ItemType Directory | Out-Null
|
|
$op['DirectoryPath'] = $createDirectoryPath
|
|
}}
|
|
}}
|
|
catch
|
|
{{
|
|
if ($_.Exception.InnerException)
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
|
|
}}
|
|
else
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
|
|
}}
|
|
}}
|
|
|
|
return $op
|
|
}}
|
|
|
|
#
|
|
# Call helper function based on bound parameter set
|
|
#
|
|
$params = $PSCmdlet.MyInvocation.BoundParameters
|
|
switch ($PSCmdlet.ParameterSetName)
|
|
{{
|
|
""PSCopyFileToRemoteSession""
|
|
{{
|
|
return PSCopyFileToRemoteSession @params
|
|
}}
|
|
|
|
""PSCopyAlternateStreamToRemoteSession""
|
|
{{
|
|
return PSCopyFileAlternateStreamToRemoteSession @params
|
|
}}
|
|
|
|
""PSTargetSupportsAlternateStreams""
|
|
{{
|
|
return PSTargetSupportsAlternateStreams @params
|
|
}}
|
|
|
|
""PSSetFileMetadata""
|
|
{{
|
|
return PSSetFileMetadata @params
|
|
}}
|
|
|
|
""PSRemoteDestinationPathIsFile""
|
|
{{
|
|
return PSRemoteDestinationPathIsFile @params
|
|
}}
|
|
|
|
""PSGetRemotePathInfo""
|
|
{{
|
|
return PSGetRemotePathInfo @params
|
|
}}
|
|
|
|
""PSCreateDirectoryOnRemoteSession""
|
|
{{
|
|
return PSCreateDirectoryOnRemoteSession @params
|
|
}}
|
|
}}
|
|
";
|
|
|
|
private static string s_PSCopyToSessionHelper = functionToken + PSCopyToSessionHelperName + @"
|
|
{
|
|
" + s_PSCopyToSessionHelperDefinition + @"
|
|
}
|
|
";
|
|
|
|
private static Hashtable s_PSCopyToSessionHelperFunction = new Hashtable() {
|
|
{nameToken, PSCopyToSessionHelperName},
|
|
{definitionToken, s_PSCopyToSessionHelperDefinitionRestricted}
|
|
};
|
|
|
|
#endregion
|
|
|
|
#region PSCopyFromSessionHelper
|
|
|
|
internal const string PSCopyFromSessionHelperName = @"PSCopyFromSessionHelper";
|
|
private static string s_PSCopyFromSessionHelperDefinition = StringUtil.Format(PSCopyFromSessionHelperDefinitionFormat, @"[ValidateNotNullOrEmpty()]");
|
|
private static string s_PSCopyFromSessionHelperDefinitionRestricted = StringUtil.Format(PSCopyFromSessionHelperDefinitionFormat, @"[ValidateUserDrive()]");
|
|
private const string PSCopyFromSessionHelperDefinitionFormat = @"
|
|
param (
|
|
[Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"", Mandatory=$true)]
|
|
{0}
|
|
[string] $copyFromFilePath,
|
|
|
|
[Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"", Mandatory=$true)]
|
|
[ValidateRange(0, [long]::MaxValue)]
|
|
[long] $copyFromStart,
|
|
|
|
[Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"", Mandatory=$true)]
|
|
[ValidateRange(0, [long]::MaxValue)]
|
|
[long] $copyFromNumBytes,
|
|
|
|
[Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"")]
|
|
[switch] $force,
|
|
|
|
[Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"")]
|
|
[switch] $isAlternateStream,
|
|
|
|
[Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"")]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string] $streamName,
|
|
|
|
[Parameter(ParameterSetName=""PSSourceSupportsAlternateStreams"", Mandatory=$true)]
|
|
{0}
|
|
[string] $supportAltStreamPath,
|
|
|
|
[Parameter(ParameterSetName=""PSGetFileMetadata"", Mandatory=$true)]
|
|
{0}
|
|
[string] $getMetaFilePath,
|
|
|
|
[Parameter(ParameterSetName=""PSGetPathItems"", Mandatory=$true)]
|
|
{0}
|
|
[string] $getPathItems,
|
|
|
|
[Parameter(ParameterSetName=""PSGetPathDirAndFiles"", Mandatory=$true)]
|
|
{0}
|
|
[string] $getPathDir
|
|
)
|
|
|
|
# A hash table with the following members is returned:
|
|
# - moreAvailable bool to keep track of whether there is more data available
|
|
# - b64Fragment to track of the number of bytes.
|
|
# - ExceptionThrown bool to keep track if an exception was thrown
|
|
function PSCopyFileFromRemoteSession
|
|
{{
|
|
param(
|
|
[string] $copyFromFilePath,
|
|
[long] $copyFromStart,
|
|
[long] $copyFromNumbytes,
|
|
[switch] $force = $false,
|
|
[switch] $isAlternateStream = $false,
|
|
[string] $streamName
|
|
)
|
|
|
|
$finalResult = @{{
|
|
b64Fragment = $null
|
|
moreAvailable = $null
|
|
ExceptionThrown = $false
|
|
}}
|
|
|
|
function PerformCopyFileFromRemoteSession
|
|
{{
|
|
param(
|
|
[string] $filePath,
|
|
[long] $start,
|
|
[long] $numBytes,
|
|
[switch] $isAlternateStream,
|
|
[string] $streamName
|
|
)
|
|
|
|
$op = @{{
|
|
b64Fragment = $null
|
|
moreAvailable = $false
|
|
}}
|
|
|
|
# Ensure bytes read is less than Max allowed
|
|
$maxBytes = 10 * 1024 * 1024
|
|
$numBytes = [Math]::Min($numBytes, $maxBytes)
|
|
|
|
$rstream = $null
|
|
try
|
|
{{
|
|
if ($isAlternateStream)
|
|
{{
|
|
$content = Microsoft.PowerShell.Management\Get-Content $filePath -stream $streamName -Encoding Byte -Raw
|
|
$rstream = [System.IO.MemoryStream]::new($content)
|
|
}}
|
|
else
|
|
{{
|
|
$rstream = [System.IO.File]::OpenRead($filePath)
|
|
}}
|
|
|
|
# Create a new array to hold the file content
|
|
if ($start -lt $rstream.Length)
|
|
{{
|
|
$o = $rstream.Seek($start, 0)
|
|
$toRead = [Math]::Min($numBytes, $rstream.Length - $start)
|
|
$fragment = Microsoft.PowerShell.Utility\New-Object byte[] $toRead
|
|
$readsoFar = 0
|
|
while ($readsoFar -lt $toRead)
|
|
{{
|
|
$read = $rstream.Read($fragment, $readSoFar, $toRead - $readsoFar)
|
|
$readsoFar += $read
|
|
}}
|
|
|
|
$op['b64Fragment'] = [System.Convert]::ToBase64String($fragment)
|
|
if (($start + $readsoFar) -lt $rstream.Length)
|
|
{{
|
|
$op['moreAvailable'] = $true
|
|
}}
|
|
}}
|
|
$op
|
|
}}
|
|
finally
|
|
{{
|
|
if ($null -ne $rstream)
|
|
{{
|
|
$rstream.Dispose()
|
|
}}
|
|
}}
|
|
}}
|
|
|
|
function WriteException
|
|
{{
|
|
param ($ex)
|
|
|
|
if ($ex.Exception.InnerException)
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $ex.Exception.InnerException
|
|
}}
|
|
else
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $ex.Exception
|
|
}}
|
|
|
|
$finalResult.ExceptionThrown = $true
|
|
}}
|
|
|
|
# Resolve path in case it is a PSDrive
|
|
$resolvedFilePath = (Microsoft.PowerShell.Management\Resolve-Path -literal $copyFromFilePath).ProviderPath
|
|
|
|
$unAuthorizedAccessException = $null
|
|
$result = $null
|
|
|
|
$isReadOnly = $false
|
|
$isHidden = $false
|
|
try
|
|
{{
|
|
$result = PerformCopyFileFromRemoteSession -filePath $resolvedFilePath -start $copyFromStart -numBytes $copyFromNumBytes -isAlternateStream:$isAlternateStream -streamName $streamName
|
|
$finalResult.b64Fragment = $result.b64Fragment
|
|
$finalResult.moreAvailable = $result.moreAvailable
|
|
}}
|
|
catch [System.UnauthorizedAccessException]
|
|
{{
|
|
$unAuthorizedAccessException = $_
|
|
if ($force)
|
|
{{
|
|
$exception = $null
|
|
try
|
|
{{
|
|
# Disable the readonly and hidden attributes and try again
|
|
$item = Microsoft.PowerShell.Management\Get-Item $resolvedFilePath
|
|
|
|
if ($item.Attributes.HasFlag([System.IO.FileAttributes]::Hidden))
|
|
{{
|
|
$isHidden = $true
|
|
$item.Attributes = $item.Attributes -band (-bnot ([System.IO.FileAttributes]::Hidden))
|
|
}}
|
|
|
|
if ($item.Attributes.HasFlag([System.IO.FileAttributes]::ReadOnly))
|
|
{{
|
|
$isReadOnly = $true
|
|
$item.Attributes = $item.Attributes -band (-bnot ([System.IO.FileAttributes]::ReadOnly))
|
|
}}
|
|
|
|
$result = PerformCopyFileFromRemoteSession -filePath $resolvedFilePath -start $copyFromStart -numBytes $copyFromNumBytes -isAlternateStream:$isAlternateStream
|
|
$finalResult.b64Fragment = $result.b64Fragment
|
|
$finalResult.moreAvailable = $result.moreAvailable
|
|
}}
|
|
catch
|
|
{{
|
|
$e = $_
|
|
if (($e.Exception.InnerException -is [System.IO.FileNotFoundException]) -or
|
|
($e.Exception.InnerException -is [System.IO.DirectoryNotFoundException]) -or
|
|
($e.Exception.InnerException -is [System.Security.SecurityException] ) -or
|
|
($e.Exception.InnerException -is [System.ArgumentException]) -or
|
|
($e.Exception.InnerException -is [System.IO.IOException]))
|
|
{{
|
|
# Write out the original error since we failed to force the copy
|
|
WriteException $unAuthorizedAccessException
|
|
}}
|
|
else
|
|
{{
|
|
WriteException $e
|
|
}}
|
|
$finalResult.ExceptionThrown = $true
|
|
}}
|
|
}}
|
|
else
|
|
{{
|
|
$finalResult.ExceptionThrown = $true
|
|
WriteException $unAuthorizedAccessException
|
|
}}
|
|
}}
|
|
catch
|
|
{{
|
|
WriteException $_
|
|
}}
|
|
finally
|
|
{{
|
|
if ($isReadOnly)
|
|
{{
|
|
$item.Attributes = $item.Attributes -bor [System.IO.FileAttributes]::ReadOnly
|
|
}}
|
|
|
|
if ($isHidden)
|
|
{{
|
|
$item.Attributes = $item.Attributes -bor [System.IO.FileAttributes]::Hidden
|
|
}}
|
|
}}
|
|
|
|
return $finalResult
|
|
}}
|
|
|
|
# Returns a hashtable with the following members:
|
|
# SourceSupportsAlternateStreams - boolean to keep track of whether the source supports Alternate data streams.
|
|
# Streams - the list of alternate streams
|
|
#
|
|
function PSSourceSupportsAlternateStreams
|
|
{{
|
|
param ([string]$supportAltStreamPath)
|
|
|
|
$result = @{{
|
|
SourceSupportsAlternateStreams = $false
|
|
Streams = @()
|
|
}}
|
|
|
|
# Check if the source supports 'Get-Content -Stream'. This functionality was introduced in version 3.0.
|
|
$getContentCmdlet = Microsoft.PowerShell.Core\Get-Command Microsoft.PowerShell.Management\Get-Content -ErrorAction SilentlyContinue
|
|
if ($getContentCmdlet.Parameters.Keys -notcontains 'Stream')
|
|
{{
|
|
return $result
|
|
}}
|
|
|
|
$result['SourceSupportsAlternateStreams'] = $true
|
|
|
|
# Check if the file has any alternate data streams.
|
|
$item = Microsoft.PowerShell.Management\Get-Item -Path $supportAltStreamPath -Stream * -ea SilentlyContinue
|
|
if (-not $item)
|
|
{{
|
|
return $result
|
|
}}
|
|
|
|
foreach ($streamName in $item.Stream)
|
|
{{
|
|
if ($streamName -ne ':$DATA')
|
|
{{
|
|
$result['Streams'] += $streamName
|
|
}}
|
|
}}
|
|
|
|
return $result
|
|
}}
|
|
|
|
# Returns a hash table with metadata info about the file for the given path.
|
|
#
|
|
function PSGetFileMetadata
|
|
{{
|
|
param ($getMetaFilePath)
|
|
|
|
if (-not (Microsoft.PowerShell.Management\Test-Path $getMetaFilePath))
|
|
{{
|
|
return
|
|
}}
|
|
|
|
$item = Microsoft.PowerShell.Management\Get-Item $getMetaFilePath -Force -ea SilentlyContinue
|
|
if ($item)
|
|
{{
|
|
$metadata = @{{}}
|
|
|
|
# Attributes
|
|
$attributes = @($item.Attributes.ToString().Split(',').Trim())
|
|
if ($attributes.Count -gt 0)
|
|
{{
|
|
$metadata.Add('Attributes', $attributes)
|
|
}}
|
|
|
|
# LastWriteTime
|
|
$metadata.Add('LastWriteTime', $item.LastWriteTime)
|
|
$metadata.Add('LastWriteTimeUtc', $item.LastWriteTimeUtc)
|
|
|
|
return $metadata
|
|
}}
|
|
}}
|
|
|
|
# Converts file system path to PSDrive path
|
|
# Returns converted path or original path if not conversion is needed.
|
|
function ConvertToPSDrivePath
|
|
{{
|
|
param (
|
|
[System.Management.Automation.PSDriveInfo] $driveInfo,
|
|
[string] $pathToConvert
|
|
)
|
|
|
|
if (!($driveInfo) -or !($driveInfo.Name) -or !($driveInfo.Root))
|
|
{{
|
|
return $pathToConvert
|
|
}}
|
|
|
|
if (! ($driveInfo.Root.StartsWith($driveInfo.Name)))
|
|
{{
|
|
return $pathToConvert.ToUpper().Replace($driveInfo.Root.ToUpper(), (($driveInfo.Name) + "":""))
|
|
}}
|
|
|
|
return $pathToConvert
|
|
}}
|
|
|
|
## A hashtable is returned in the following format:
|
|
## Exists - Boolean to keep track if the given path exists.
|
|
## Items - The items that Get-Item -Path $path resolves to.
|
|
function PSGetPathItems
|
|
{{
|
|
param (
|
|
[string] $getPathItems
|
|
)
|
|
|
|
$op = @{{
|
|
Exists = $null
|
|
Items = $null
|
|
}}
|
|
|
|
try
|
|
{{
|
|
if (-not (Microsoft.PowerShell.Management\Test-Path $getPathItems))
|
|
{{
|
|
$op['Exists'] = $false
|
|
return $op
|
|
}}
|
|
|
|
$items = @(Microsoft.PowerShell.Management\Get-Item -Path $getPathItems | Microsoft.PowerShell.Core\ForEach-Object {{
|
|
@{{
|
|
FullName = ConvertToPSDrivePath $_.PSDrive $_.FullName;
|
|
Name = $_.Name;
|
|
FileSize = $_.Length; IsDirectory = $_ -is [System.IO.DirectoryInfo]
|
|
}}
|
|
}})
|
|
$op['Exists'] = $true
|
|
$op['Items'] = $items
|
|
|
|
return $op
|
|
}}
|
|
catch
|
|
{{
|
|
if ($_.Exception.InnerException)
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
|
|
}}
|
|
else
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
|
|
}}
|
|
}}
|
|
|
|
return $op
|
|
}}
|
|
|
|
# Return a hashtable with the following members:
|
|
# Files - Array with file fullnames, and their sizes
|
|
# Directories - Array of child directory fullnames
|
|
function PSGetPathDirAndFiles
|
|
{{
|
|
param (
|
|
[string] $getPathDir
|
|
)
|
|
|
|
$result = @()
|
|
|
|
$op = @{{
|
|
Files = $null
|
|
Directories = $null
|
|
}}
|
|
|
|
try
|
|
{{
|
|
$item = Microsoft.PowerShell.Management\Get-Item $getPathDir
|
|
|
|
if ($item -isnot [System.IO.DirectoryInfo])
|
|
{{
|
|
return $op
|
|
}}
|
|
|
|
$files = @(Microsoft.PowerShell.Management\Get-ChildItem -Path $getPathDir -File | Microsoft.PowerShell.Core\ForEach-Object {{
|
|
@{{ FileName = $_.Name;
|
|
FilePath = (ConvertToPSDrivePath $_.PSDrive $_.FullName);
|
|
FileSize = $_.Length
|
|
}}
|
|
}})
|
|
|
|
$directories = @(Microsoft.PowerShell.Management\Get-ChildItem -Path $getPathDir -Directory | Microsoft.PowerShell.Core\ForEach-Object {{
|
|
@{{ Name = $_.Name;
|
|
FullName = (ConvertToPSDrivePath $_.PSDrive $_.FullName)
|
|
}}
|
|
}})
|
|
|
|
if ($files.count -gt 0)
|
|
{{
|
|
$op['Files'] = $files
|
|
}}
|
|
|
|
if ($directories.count -gt 0)
|
|
{{
|
|
$op['Directories'] = $directories
|
|
}}
|
|
}}
|
|
catch
|
|
{{
|
|
if ($_.Exception.InnerException)
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
|
|
}}
|
|
else
|
|
{{
|
|
Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
|
|
}}
|
|
}}
|
|
|
|
return $op
|
|
}}
|
|
|
|
#
|
|
# Call helper function based on bound parameter set
|
|
#
|
|
$params = $PSCmdlet.MyInvocation.BoundParameters
|
|
switch ($PSCmdlet.ParameterSetName)
|
|
{{
|
|
""PSCopyFileFromRemoteSession""
|
|
{{
|
|
return PSCopyFileFromRemoteSession @params
|
|
}}
|
|
|
|
""PSSourceSupportsAlternateStreams""
|
|
{{
|
|
PSSourceSupportsAlternateStreams @params
|
|
}}
|
|
|
|
""PSGetFileMetadata""
|
|
{{
|
|
PSGetFileMetadata @params
|
|
}}
|
|
|
|
""PSGetPathItems""
|
|
{{
|
|
PSGetPathItems @params
|
|
}}
|
|
|
|
""PSGetPathDirAndFiles""
|
|
{{
|
|
PSGetPathDirAndFiles @params
|
|
}}
|
|
}}
|
|
";
|
|
|
|
internal static string PSCopyFromSessionHelper = functionToken + PSCopyFromSessionHelperName + @"
|
|
{
|
|
" + s_PSCopyFromSessionHelperDefinition + @"
|
|
}
|
|
";
|
|
|
|
private static Hashtable s_PSCopyFromSessionHelperFunction = new Hashtable() {
|
|
{nameToken, PSCopyFromSessionHelperName},
|
|
{definitionToken, s_PSCopyFromSessionHelperDefinitionRestricted}
|
|
};
|
|
|
|
#endregion
|
|
|
|
#region PSCopyRemoteUtils
|
|
|
|
internal const string PSCopyRemoteUtilsName = @"PSCopyRemoteUtils";
|
|
internal static string PSCopyRemoteUtilsDefinition = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateNotNullOrEmpty()]", PSValidatePathFunction);
|
|
private static string s_PSCopyRemoteUtilsDefinitionRestricted = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateUserDrive()]", PSValidatePathFunction);
|
|
private const string PSCopyRemoteUtilsDefinitionFormat = @"
|
|
param (
|
|
[Parameter(ParameterSetName=""PSRemoteDirectoryExist"", Mandatory=$true)]
|
|
{0}
|
|
[string] $dirPathExists,
|
|
|
|
[Parameter(ParameterSetName=""PSValidatePath"", Mandatory=$true)]
|
|
{0}
|
|
[string] $pathToValidate,
|
|
|
|
[Parameter(ParameterSetName=""PSValidatePath"")]
|
|
[switch] $sourceIsRemote
|
|
)
|
|
|
|
# Returns a hashtable with the following member:
|
|
# Exists - boolean to keep track of whether the given path exists for a remote directory.
|
|
#
|
|
function PSRemoteDirectoryExist
|
|
{{
|
|
param (
|
|
[string] $dirPathExists
|
|
)
|
|
|
|
$result = @{{ Exists = (Microsoft.PowerShell.Management\Test-Path $dirPathExists -PathType Container) }}
|
|
return $result
|
|
}}
|
|
|
|
{1}
|
|
|
|
#
|
|
# Call helper function based on bound parameter set
|
|
#
|
|
$params = $PSCmdlet.MyInvocation.BoundParameters
|
|
switch ($PSCmdlet.ParameterSetName)
|
|
{{
|
|
""PSRemoteDirectoryExist""
|
|
{{
|
|
return PSRemoteDirectoryExist @params
|
|
}}
|
|
|
|
""PSValidatePath""
|
|
{{
|
|
return PSValidatePath @params
|
|
}}
|
|
}}
|
|
";
|
|
|
|
private const string PSValidatePathFunction = functionToken + "PSValidatePath" + @"
|
|
{
|
|
" + PSValidatePathDefinition + @"
|
|
}
|
|
";
|
|
|
|
internal const string PSValidatePathDefinition = @"
|
|
# Return hashtable in the following format:
|
|
# Exists - boolean to keep track if the given path exists
|
|
# Root - the root for the given path. If wildcards are used, it returns the first drive root.
|
|
# IsAbsolute - boolean to keep track of whether the given path is absolute
|
|
param (
|
|
[string] $pathToValidate,
|
|
[switch] $sourceIsRemote
|
|
)
|
|
|
|
function SafeGetDriveRoot
|
|
{
|
|
param (
|
|
[System.Management.Automation.PSDriveInfo] $driveInfo
|
|
)
|
|
|
|
if (! ($driveInfo.Root.StartsWith($driveInfo.Name)))
|
|
{
|
|
return (($driveInfo.Name) + "":"")
|
|
}
|
|
else
|
|
{
|
|
$driveInfo.Root
|
|
}
|
|
}
|
|
|
|
$result = @{
|
|
Exists = $null
|
|
Root = $null
|
|
IsAbsolute = $null
|
|
}
|
|
|
|
# Validate if the path is absolute
|
|
$result['IsAbsolute'] = (Microsoft.PowerShell.Management\Split-Path $pathToValidate -IsAbsolute)
|
|
if (-not $result['IsAbsolute'])
|
|
{
|
|
return $result
|
|
}
|
|
|
|
# Check if the given path exists.
|
|
$result['Exists'] = (Microsoft.PowerShell.Management\Test-Path $pathToValidate)
|
|
|
|
# If $pathToValidate is a remote source, and it does not exist, return.
|
|
if ($sourceIsRemote -and (-not $result['Exists']))
|
|
{
|
|
return $result
|
|
}
|
|
|
|
# If the path does not exist, check if we can find its root.
|
|
if (-not (Microsoft.PowerShell.Management\Test-Path $pathToValidate))
|
|
{
|
|
$possibleRoot = $null
|
|
|
|
try
|
|
{
|
|
$possibleRoot = [System.IO.Path]::GetPathRoot($pathToValidate)
|
|
}
|
|
# Catch everything and ignore the error.
|
|
catch {}
|
|
|
|
if (-not $possibleRoot)
|
|
{
|
|
return $result
|
|
}
|
|
|
|
# Now use this path to find its root.
|
|
$pathToValidate = $possibleRoot
|
|
}
|
|
|
|
# Get the root path using Get-Item
|
|
$item = Microsoft.PowerShell.Management\Get-Item $pathToValidate -ea SilentlyContinue
|
|
if (($null -ne $item) -and ($item[0].PSProvider.Name -eq 'FileSystem'))
|
|
{
|
|
$result['Root'] = SafeGetDriveRoot $item[0].PSDrive
|
|
return $result
|
|
}
|
|
|
|
# If this fails, try to get them via Get-PSDrive
|
|
$fileSystemDrives = @(Microsoft.PowerShell.Management\Get-PSDrive -PSProvider FileSystem -ea SilentlyContinue)
|
|
|
|
# If this fails, try to get them via Get-PSProvider
|
|
if ($fileSystemDrives.Count -eq 0)
|
|
{
|
|
$fileSystemDrives = @((Microsoft.PowerShell.Management\Get-PSProvider -PSProvider FileSystem -ea SilentlyContinue).Drives)
|
|
}
|
|
|
|
foreach ($drive in $fileSystemDrives)
|
|
{
|
|
if ($pathToValidate.StartsWith($drive.Root))
|
|
{
|
|
$result['Root'] = SafeGetDriveRoot $drive
|
|
break
|
|
}
|
|
}
|
|
|
|
return $result
|
|
";
|
|
|
|
internal static string PSCopyRemoteUtils = functionToken + PSCopyRemoteUtilsName + @"
|
|
{
|
|
" + PSCopyRemoteUtilsDefinition + @"
|
|
}
|
|
";
|
|
|
|
internal static Hashtable PSCopyRemoteUtilsFunction = new Hashtable() {
|
|
{nameToken, PSCopyRemoteUtilsName},
|
|
{definitionToken, s_PSCopyRemoteUtilsDefinitionRestricted}
|
|
};
|
|
|
|
#endregion
|
|
|
|
internal static string AllCopyToRemoteScripts = s_PSCopyToSessionHelper + PSCopyRemoteUtils;
|
|
internal static IEnumerable<Hashtable> GetAllCopyToRemoteScriptFunctions()
|
|
{
|
|
yield return s_PSCopyToSessionHelperFunction;
|
|
yield return PSCopyRemoteUtilsFunction;
|
|
}
|
|
|
|
internal static string AllCopyFromRemoteScripts = PSCopyFromSessionHelper + PSCopyRemoteUtils;
|
|
internal static IEnumerable<Hashtable> GetAllCopyFromRemoteScriptFunctions()
|
|
{
|
|
yield return s_PSCopyFromSessionHelperFunction;
|
|
yield return PSCopyRemoteUtilsFunction;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|