2018-02-13 09:23:53 -08:00
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
2016-03-30 14:20:52 -07:00
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 ;
2019-04-30 10:25:11 +05:00
using System.Management.Automation.Runspaces ;
using System.Runtime.InteropServices ;
2016-03-30 14:20:52 -07:00
using System.Security ;
using System.Security.AccessControl ;
using System.Text ;
using System.Xml ;
using System.Xml.XPath ;
2019-04-30 10:25:11 +05:00
2016-03-30 14:20:52 -07:00
using Microsoft.Win32.SafeHandles ;
2019-04-30 10:25:11 +05:00
2016-03-30 14:20:52 -07:00
using Dbg = System . Management . Automation ;
namespace Microsoft.PowerShell.Commands
{
#region FileSystemProvider
/// <summary>
/// Defines the implementation of a File System Provider. This provider
/// allows for stateless namespace navigation of the file system.
/// </summary>
[CmdletProvider(FileSystemProvider.ProviderName, ProviderCapabilities.Credentials | ProviderCapabilities.Filter | ProviderCapabilities.ShouldProcess)]
[OutputType(typeof(FileSecurity), ProviderCmdlet = ProviderCmdlet.SetAcl)]
2019-01-07 23:46:00 +05:00
[OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)]
2016-03-30 14:20:52 -07:00
[OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)]
2019-01-07 23:46:00 +05:00
[OutputType(typeof(Byte), typeof(string), ProviderCmdlet = ProviderCmdlet.GetContent)]
2016-03-30 14:20:52 -07:00
[OutputType(typeof(FileInfo), ProviderCmdlet = ProviderCmdlet.GetItem)]
[OutputType(typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetChildItem)]
[OutputType(typeof(FileSecurity), typeof(DirectorySecurity), ProviderCmdlet = ProviderCmdlet.GetAcl)]
2019-01-08 00:40:07 +05:00
[OutputType(typeof(bool), typeof(string), typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItem)]
[OutputType(typeof(bool), typeof(string), typeof(DateTime), typeof(System.IO.FileInfo), typeof(System.IO.DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItemProperty)]
2019-01-07 23:46:00 +05:00
[OutputType(typeof(string), typeof(System.IO.FileInfo), ProviderCmdlet = ProviderCmdlet.NewItem)]
2016-03-30 14:20:52 -07:00
[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.
2017-01-16 13:31:14 -08:00
// 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
2016-03-30 14:20:52 -07:00
// copy script will accomodate the new value.
private const int FILETRANSFERSIZE = 4 * 1024 * 1024 ;
2017-04-21 16:09:53 -07:00
// The name of the key in an exception's Data dictionary when attempting
// to copy an item onto itself.
private const string SelfCopyDataKey = "SelfCopy" ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// An instance of the PSTraceSource class used for trace output
/// using "FileSystemProvider" as the category.
/// </summary>
2016-07-29 13:02:49 -07:00
[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" );
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets the name of the provider.
2016-03-30 14:20:52 -07:00
/// </summary>
public const string ProviderName = "FileSystem" ;
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// Initializes a new instance of the FileSystemProvider class. Since this
/// object needs to be stateless, the constructor does nothing.
/// </summary>
public FileSystemProvider ()
{
}
2016-07-29 13:02:49 -07:00
private Collection < WildcardPattern > _excludeMatcher = null ;
2018-10-27 21:48:49 -07:00
private static System . IO . EnumerationOptions _enumerationOptions = new System . IO . EnumerationOptions
{
2019-05-06 17:20:08 -07:00
MatchType = MatchType . Win32 ,
2018-10-27 21:48:49 -07:00
MatchCasing = MatchCasing . CaseInsensitive ,
AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior
};
2016-03-30 14:20:52 -07:00
/// <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 )
{
2019-03-29 13:22:52 -07:00
return GetCorrectCasedPath ( path . Replace ( StringLiterals . AlternatePathSeparator , StringLiterals . DefaultPathSeparator ));
}
/// <summary>
/// Get the correct casing for a path. This method assumes it's being called by NormalizePath()
/// so that the path is already normalized.
/// </summary>
/// <param name="path">
/// The path to retrieve.
/// </param>
/// <returns>
/// The path with accurate casing if item exists, otherwise it returns path that was passed in.
/// </returns>
private static string GetCorrectCasedPath ( string path )
{
2019-03-31 12:06:38 -07:00
// Only apply to directories where there are issues with some tools if the casing
// doesn't match the source like git
if ( Directory . Exists ( path ))
2019-03-29 13:22:52 -07:00
{
2019-04-01 10:41:03 -07:00
string exactPath = string . Empty ;
2019-03-29 22:48:11 -07:00
int itemsToSkip = 0 ;
if ( Utils . PathIsUnc ( path ))
{
// With the Split method, a UNC path like \\server\share, we need to skip
// trying to enumerate the server and share, so skip the first two empty
// strings, then server, and finally share name.
itemsToSkip = 4 ;
}
2019-03-29 13:22:52 -07:00
foreach ( string item in path . Split ( StringLiterals . DefaultPathSeparator ))
{
2019-04-01 12:05:45 -07:00
if ( itemsToSkip -- > 0 )
2019-03-29 13:22:52 -07:00
{
2019-04-01 10:41:03 -07:00
// This handles the UNC server and share and 8.3 short path syntax
2019-03-29 22:48:11 -07:00
exactPath += item + StringLiterals . DefaultPathSeparator ;
continue ;
}
else if ( string . IsNullOrEmpty ( exactPath ))
{
// This handles the drive letter or / root path start
2019-03-29 13:22:52 -07:00
exactPath = item + StringLiterals . DefaultPathSeparator ;
}
else if ( string . IsNullOrEmpty ( item ))
{
// This handles the trailing slash case
continue ;
}
2019-04-01 12:05:45 -07:00
else if ( item . Contains ( '~' ))
{
// This handles short path names
exactPath += StringLiterals . DefaultPathSeparator + item ;
}
2019-03-29 13:22:52 -07:00
else
{
exactPath = Directory . GetFileSystemEntries ( exactPath , item ). First ();
}
}
2019-03-29 22:48:11 -07:00
2019-03-30 07:10:40 -07:00
if ( path . EndsWith ( StringLiterals . DefaultPathSeparator ))
{
return exactPath + StringLiterals . DefaultPathSeparator ;
}
2019-03-29 13:22:52 -07:00
return exactPath ;
}
else
{
return path ;
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2018-07-25 22:02:09 +05:00
/// Checks if the item exist at the specified path. if it exists then creates
2016-03-30 14:20:52 -07:00
/// appropriate directoryinfo or fileinfo object.
/// </summary>
2017-01-16 13:31:14 -08:00
/// <param name="path">
2018-07-25 22:02:09 +05:00
/// Refers to the item for which we are checking for existence and creating filesysteminfo object.
2016-03-30 14:20:52 -07:00
/// </param>
/// <param name="isContainer">
2018-07-25 22:02:09 +05:00
/// Return true if path points to a directory else returns false.
2016-03-30 14:20:52 -07:00
/// </param>
2018-07-25 22:02:09 +05:00
/// <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 )
2016-03-30 14:20:52 -07:00
{
2018-07-25 22:02:09 +05:00
// 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 );
2016-03-30 14:20:52 -07:00
2018-07-25 22:02:09 +05:00
if ( exists )
2016-03-30 14:20:52 -07:00
{
2018-07-25 22:02:09 +05:00
if ( isContainer )
{
return new DirectoryInfo ( path );
}
else
{
return fsinfo ;
}
2016-03-30 14:20:52 -07:00
}
return null ;
}
/// <summary>
2019-01-10 02:07:41 +05:00
/// Overrides the method of CmdletProvider, considering the additional
2019-01-06 04:40:24 +05:00
/// dynamic parameters of FileSystemProvider.
2016-03-30 14:20:52 -07:00
/// </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 = (
2018-06-26 02:37:09 +05:00
( fspDynamicParam . Attributes != null )
2016-03-30 14:20:52 -07:00
|| ( fspDynamicParam . Directory )
|| ( fspDynamicParam . File )
|| ( fspDynamicParam . Hidden )
|| ( fspDynamicParam . ReadOnly )
|| ( fspDynamicParam . System ));
}
return ( attributeFilterSet || base . IsFilterSet ());
}
/// <summary>
2017-01-16 13:31:14 -08:00
/// Gets the dynamic parameters for get-childnames on the
/// FileSystemProvider.
2016-03-30 14:20:52 -07:00
/// We currently only support one dynamic parameter,
2017-01-16 13:31:14 -08:00
/// "Attributes" that returns an enum evaluator for the
2016-03-30 14:20:52 -07:00
/// 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>
2017-01-16 13:31:14 -08:00
/// Gets the dynamic parameters for get-childitems on the
/// FileSystemProvider.
2016-03-30 14:20:52 -07:00
/// We currently only support one dynamic parameter,
2017-01-16 13:31:14 -08:00
/// "Attributes" that returns an enum evaluator for the
2016-03-30 14:20:52 -07:00
/// 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.
2019-01-06 04:40:24 +05:00
/// Gets provider-specific help content for the corresponding cmdlet.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <param name="helpItemName">
/// Name of command that the help is requested for.
/// </param>
/// <param name="path">
2017-01-16 13:31:14 -08:00
/// Not used here.
2016-03-30 14:20:52 -07:00
/// </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
{
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( helpItemName ))
2016-03-30 14:20:52 -07:00
{
CmdletInfo . SplitCmdletName ( helpItemName , out verb , out noun );
}
else
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( verb ) || string . IsNullOrEmpty ( noun ))
2016-03-30 14:20:52 -07:00
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
2017-01-16 13:31:14 -08:00
// Load the help file from the current UI culture subfolder
2016-03-30 14:20:52 -07:00
XmlDocument document = new XmlDocument ();
CultureInfo currentUICulture = CultureInfo . CurrentUICulture ;
string fullHelpPath = Path . Combine (
2018-06-13 00:30:10 +05:00
string . IsNullOrEmpty ( this . ProviderInfo . ApplicationBase ) ? string . Empty : this . ProviderInfo . ApplicationBase ,
2016-03-30 14:20:52 -07:00
currentUICulture . ToString (),
2018-06-13 00:30:10 +05:00
string . IsNullOrEmpty ( this . ProviderInfo . HelpFile ) ? string . Empty : this . ProviderInfo . HelpFile );
2016-03-30 14:20:52 -07:00
XmlReaderSettings settings = new XmlReaderSettings ();
settings . XmlResolver = null ;
reader = XmlReader . Create ( fullHelpPath , settings );
document . Load ( reader );
2016-08-27 02:46:25 +04:00
// Add "msh" and "command" namespaces from the MAML schema
2016-03-30 14:20:52 -07:00
XmlNamespaceManager nsMgr = new XmlNamespaceManager ( document . NameTable );
2016-08-27 02:46:25 +04:00
nsMgr . AddNamespace ( "msh" , HelpCommentsParser . mshURI );
2016-03-30 14:20:52 -07:00
nsMgr . AddNamespace ( "command" , HelpCommentsParser . commandURI );
// Compose XPath query to select the appropriate node based on the cmdlet
2018-12-31 20:44:16 +05:00
string xpathQuery = string . Format (
2016-03-30 14:20:52 -07:00
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 )
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
catch ( PathTooLongException )
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
catch ( IOException )
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
catch ( UnauthorizedAccessException )
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
catch ( NotSupportedException )
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
catch ( SecurityException )
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
catch ( XPathException )
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
finally
{
2018-06-26 02:37:09 +05:00
if ( reader != null )
2016-03-30 14:20:52 -07:00
{
(( IDisposable ) reader ). Dispose ();
}
}
2018-12-23 22:20:06 -08:00
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
#endregion
#region CmdletProvider members
2019-04-02 12:59:36 -04:00
2016-03-30 14:20:52 -07:00
/// <summary>
2019-04-02 12:59:36 -04:00
/// Starts the File System provider. This method sets the Home for the
2016-03-30 14:20:52 -07:00
/// 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>
2016-07-29 13:02:49 -07:00
protected override ProviderInfo Start ( ProviderInfo providerInfo )
2016-03-30 14:20:52 -07:00
{
// Set the home folder for the user
2019-04-24 19:27:22 +05:00
if ( providerInfo != null && string . IsNullOrEmpty ( providerInfo . Home ))
2016-03-30 14:20:52 -07:00
{
// %USERPROFILE% - indicate where a user's home directory is located in the file system.
2016-08-10 10:37:42 -07:00
string homeDirectory = Environment . GetEnvironmentVariable ( Platform . CommonEnvVariableNames . Home );
2016-03-30 14:20:52 -07:00
if (! string . IsNullOrEmpty ( homeDirectory ))
{
if ( Directory . Exists ( homeDirectory ))
{
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "Home = {0}" , homeDirectory );
2016-03-30 14:20:52 -07:00
providerInfo . Home = homeDirectory ;
}
else
2019-02-28 10:00:23 -05:00
{
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "Not setting home directory {0} - does not exist" , homeDirectory );
2019-02-28 10:00:23 -05:00
}
2016-03-30 14:20:52 -07:00
}
}
2018-12-23 22:20:06 -08:00
2019-02-04 21:11:46 +01:00
// OneDrive placeholder support (issue #8315)
// make it so OneDrive placeholders are perceived as such with *all* their attributes accessible
#if ! UNIX
// The placeholder mode management APIs Rtl(Set|Query)(Process|Thread)PlaceholderCompatibilityMode
// are only supported starting with Windows 10 version 1803 (build 17134)
Version minBuildForPlaceHolderAPIs = new Version ( 10 , 0 , 17134 , 0 );
if ( Environment . OSVersion . Version >= minBuildForPlaceHolderAPIs )
{
// let's be safe, don't change the PlaceHolderCompatibilityMode if the current one is not what we expect
if ( NativeMethods . PHCM_DISGUISE_PLACEHOLDER == NativeMethods . RtlQueryProcessPlaceholderCompatibilityMode ())
{
NativeMethods . RtlSetProcessPlaceholderCompatibilityMode ( NativeMethods . PHCM_EXPOSE_PLACEHOLDERS );
}
}
#endif
2016-03-30 14:20:52 -07:00
return providerInfo ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#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" );
}
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( drive . Root ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "drive.Root" );
}
// -Persist switch parameter is supported only for Network paths.
2016-04-06 12:01:07 -07:00
if ( drive . Persist && ! PathIsNetworkPath ( drive . Root ))
2016-03-30 14:20:52 -07:00
{
ErrorRecord er = new ErrorRecord ( new NotSupportedException ( FileSystemProviderStrings . PersistNotSupported ), "DriveRootNotNetworkPath" , ErrorCategory . InvalidArgument , drive );
ThrowTerminatingError ( er );
}
if ( IsNetworkMappedDrive ( drive ))
{
2017-01-16 13:31:14 -08:00
// MapNetworkDrive facilitates to map the newly
2016-03-30 14:20:52 -07:00
// 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.
2018-07-25 22:02:09 +05:00
validDrive = Directory . Exists ( drive . Root );
2016-03-30 14:20:52 -07:00
}
if ( validDrive )
{
result = drive ;
}
else
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . DriveRootError , drive . Root );
2016-07-29 13:02:49 -07:00
Exception e = new IOException ( error );
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( e , "DriveRootError" , ErrorCategory . ReadError , drive ));
}
drive . Trace ();
return result ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// MapNetworkDrive facilitates to map the newly created PS Drive to a network share.
/// </summary>
2016-08-28 05:51:23 +00:00
/// <param name="drive">The PSDrive info that would be used to create a new PS drive.</param>
2016-03-30 14:20:52 -07:00
private void MapNetworkDrive ( PSDriveInfo drive )
2016-04-06 12:01:07 -07:00
{
// Porting note: mapped network drives are only supported on Windows
2016-04-08 14:54:41 -07:00
if ( Platform . IsWindows )
2016-04-06 12:01:07 -07:00
{
WinMapNetworkDrive ( drive );
}
else
{
2016-07-13 21:15:45 -07:00
throw new PlatformNotSupportedException ();
2016-04-06 12:01:07 -07:00
}
}
private void WinMapNetworkDrive ( PSDriveInfo drive )
2016-03-30 14:20:52 -07:00
{
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 ;
2016-08-28 05:51:23 +00:00
// By default the connection is not persisted.
2016-03-30 14:20:52 -07:00
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
{
2019-01-06 06:51:20 +05:00
// error.
2016-03-30 14:20:52 -07:00
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 ;
2017-01-16 13:31:14 -08:00
// PsDrive.Root is updated to the name of the Drive for
2016-03-30 14:20:52 -07:00
// drives targeting network path and being persisted.
drive . Root = driveName + @"\" ;
}
}
finally
{
2016-08-28 05:51:23 +00:00
// Clear the password in the memory.
2016-03-30 14:20:52 -07:00
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 )
{
2016-04-06 12:01:07 -07:00
bool shouldMapNetworkDrive = ( drive != null && ! string . IsNullOrEmpty ( drive . Root ) && PathIsNetworkPath ( drive . Root )) &&
2016-03-30 14:20:52 -07:00
( 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 )
2016-04-06 12:01:07 -07:00
{
2016-07-22 10:49:45 -07:00
#if UNIX
2016-07-14 15:46:59 -07:00
return drive ;
#else
return WinRemoveDrive ( drive );
#endif
2016-04-06 12:01:07 -07:00
}
private PSDriveInfo WinRemoveDrive ( PSDriveInfo drive )
2016-03-30 14:20:52 -07:00
{
if ( IsNetworkMappedDrive ( drive ))
{
const int CONNECT_UPDATE_PROFILE = 0x00000001 ;
int flags = 0 ;
string driveName ;
if ( drive . IsNetworkDrive )
{
2017-01-16 13:31:14 -08:00
// Here we are removing only persisted network drives.
2016-03-30 14:20:52 -07:00
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>
2017-01-16 13:31:14 -08:00
/// IsSupportedDriveForPersistence is a helper method used to
2016-03-30 14:20:52 -07:00
/// 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 );
2019-01-08 00:40:07 +05:00
if ( char . ToUpperInvariant ( driveChar ) >= 'A' && char . ToUpperInvariant ( driveChar ) <= 'Z' )
2016-03-30 14:20:52 -07:00
{
isSupportedDriveForPersistence = true ;
}
}
return isSupportedDriveForPersistence ;
}
/// <summary>
/// Return the UNC path for a given network drive
2019-01-06 04:40:24 +05:00
/// using the Windows API.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <param name="driveName"></param>
/// <returns></returns>
internal static string GetUNCForNetworkDrive ( string driveName )
2016-04-06 12:01:07 -07:00
{
2016-07-22 10:49:45 -07:00
#if UNIX
2016-07-14 14:51:59 -07:00
return driveName ;
#else
return WinGetUNCForNetworkDrive ( driveName );
#endif
2016-04-06 12:01:07 -07:00
}
private static string WinGetUNCForNetworkDrive ( string driveName )
2016-03-30 14:20:52 -07:00
{
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
2017-01-16 13:31:14 -08:00
// 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
2016-03-30 14:20:52 -07:00
// allocated buffer size.
bufferSize = 3 ;
#endif
StringBuilder uncBuffer = new StringBuilder ( bufferSize );
driveName += ':' ;
// Call the windows API
int errorCode = NativeMethods . WNetGetConnection ( driveName , uncBuffer , ref bufferSize );
2017-01-16 13:31:14 -08:00
// error code 234 is returned whenever the required buffer size is greater
2016-03-30 14:20:52 -07:00
// than the specified buffer size.
if ( errorCode == 234 )
{
uncBuffer = new StringBuilder ( bufferSize );
errorCode = NativeMethods . WNetGetConnection ( driveName , uncBuffer , ref bufferSize );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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 )
2016-04-06 12:01:07 -07:00
{
2016-07-22 10:49:45 -07:00
#if UNIX
2016-07-14 15:41:17 -07:00
throw new PlatformNotSupportedException ();
#else
return WinGetSubstitutedPathForNetworkDosDevice ( driveName );
#endif
2016-04-06 12:01:07 -07:00
}
private static string WinGetSubstitutedPathForNetworkDosDevice ( string driveName )
2016-03-30 14:20:52 -07:00
{
string associatedPath = null ;
if (! string . IsNullOrEmpty ( driveName ) && driveName . Length == 1 )
{
2016-08-28 05:51:23 +00:00
// By default buffer size is set to 300 which would generally be sufficient in most of the cases.
2016-03-30 14:20:52 -07:00
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 )
{
2017-01-16 13:31:14 -08:00
// If the drive letter is a substed path, the result will be in the format of
2016-03-30 14:20:52 -07:00
// - "\??\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 ;
}
2019-06-25 23:02:31 +05:00
else if ( associatedPath . EndsWith ( ':' ))
2016-03-30 14:20:52 -07:00
{
// 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 );
}
2017-01-16 13:31:14 -08:00
// We got the "insufficient buffer" error. In this case we extend
2016-03-30 14:20:52 -07:00
// the buffer size, unless it's unreasonably too large.
if ( bufferSize >= 32767 )
{
2017-01-16 13:31:14 -08:00
// "The Windows API has many functions that also have Unicode versions to permit
2016-03-30 14:20:52 -07:00
// an extended-length path for a maximum total path length of 32,767 characters"
2019-01-24 20:50:11 +00:00
// See https://msdn.microsoft.com/library/aa365247.aspx#maxpath
2016-03-30 14:20:52 -07:00
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
2017-01-16 13:31:14 -08:00
// - substitutes a network location directly, or
2016-03-30 14:20:52 -07:00
// - 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 ;
}
2017-01-16 13:31:14 -08:00
// cover everything by the try-catch block, because some of the
2016-04-06 12:01:07 -07:00
// DriveInfo properties may throw exceptions
2016-03-30 14:20:52 -07:00
try
{
2016-04-06 12:01:07 -07:00
string newDriveName = newDrive . Name . Substring ( 0 , 1 );
2018-12-31 20:44:16 +05:00
string description = string . Empty ;
2016-04-06 12:01:07 -07:00
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
2016-07-14 15:41:17 -07:00
// points on non-Windows are enumerated as drives by .NET, but
2016-04-06 12:01:07 -07:00
// the platform itself then has no real network drive support
// as required by this context. Solution: check for network
// drive support before using it.
2016-07-22 10:49:45 -07:00
#if UNIX
2016-07-14 15:41:17 -07:00
continue ;
#else
2016-04-06 12:01:07 -07:00
displayRoot = GetRootPathForNetworkDriveOrDosDevice ( newDrive );
2016-07-14 15:41:17 -07:00
#endif
2016-04-06 12:01:07 -07:00
}
2016-03-30 14:20:52 -07:00
if ( newDrive . DriveType == DriveType . Fixed )
{
if (! newDrive . RootDirectory . Exists )
{
continue ;
}
root = newDrive . RootDirectory . FullName ;
}
2016-07-22 10:49:45 -07:00
#if UNIX
2016-11-15 10:27:12 -08:00
// Porting notes: On platforms with single root filesystems, ensure
// that we add a filesystem with the root "/" to the initial drive list,
2016-04-06 12:01:07 -07:00
// otherwise path handling will not work correctly because there
// is no : available to separate the filesystems from each other
2016-11-15 10:27:12 -08:00
if ( root != StringLiterals . DefaultPathSeparatorString
&& newDriveName == StringLiterals . DefaultPathSeparatorString )
{
root = StringLiterals . DefaultPathSeparatorString ;
}
2016-07-14 14:36:41 -07:00
#endif
2016-04-06 12:01:07 -07:00
// 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 ;
}
}
2018-12-23 22:20:06 -08:00
2016-04-06 12:01:07 -07:00
if ( skipDuplicate )
2019-02-28 10:00:23 -05:00
{
2016-04-06 12:01:07 -07:00
continue ;
2019-02-28 10:00:23 -05:00
}
2016-04-06 12:01:07 -07:00
2016-03-30 14:20:52 -07:00
// Create a new VirtualDrive for each logical drive
PSDriveInfo newPSDriveInfo =
new PSDriveInfo (
newDriveName ,
ProviderInfo ,
root ,
description ,
null ,
displayRoot );
2017-01-16 13:31:14 -08:00
// The network drive is detected when PowerShell is launched.
2016-03-30 14:20:52 -07:00
// 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 ;
}
2016-04-06 12:01:07 -07:00
// Porting notes: on the non-Windows platforms, the drive never
// uses : as a separator between drive and path
2016-04-08 14:54:41 -07:00
if (! Platform . IsWindows )
2016-04-06 12:01:07 -07:00
{
newPSDriveInfo . VolumeSeparatedByColon = false ;
}
2016-03-30 14:20:52 -07:00
results . Add ( newPSDriveInfo );
}
2016-04-06 12:01:07 -07:00
// If there are issues accessing properties of the DriveInfo, do
// not add the drive
2016-03-30 14:20:52 -07:00
catch ( IOException )
{
}
catch ( System . Security . SecurityException )
{
}
catch ( System . UnauthorizedAccessException )
{
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2019-06-13 13:05:37 -07:00
results . Add (
new PSDriveInfo (
DriveNames . TempDrive ,
ProviderInfo ,
Path . GetTempPath (),
SessionStateStrings . TempDriveDescription ,
credential : null ,
displayRoot : null )
);
2019-01-23 13:53:04 -08:00
2016-03-30 14:20:52 -07:00
return results ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#endregion DriveCmdletProvider methods
#region ItemCmdletProvider methods
/// <summary>
2019-01-06 04:40:24 +05:00
/// Retrieves the dynamic parameters required for the Get-Item cmdlet.
2016-03-30 14:20:52 -07:00
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="path">The path of the file to process.</param>
2016-03-30 14:20:52 -07:00
/// <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
2019-01-06 04:40:24 +05:00
/// C:\WINNT\Media\chimes.wav.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <param name="path">
/// The fully qualified path to validate.
/// </param>
/// <returns>
/// True if the path is valid, false otherwise.
/// </returns>
2016-07-29 13:02:49 -07:00
protected override bool IsValidPath ( string path )
2016-03-30 14:20:52 -07:00
{
2019-01-06 06:51:20 +05:00
// Path passed should be fully qualified path.
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
return false ;
}
2019-01-06 06:51:20 +05:00
// Normalize the path
2016-03-30 14:20:52 -07:00
path = NormalizePath ( path );
path = EnsureDriveIsRooted ( path );
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
// 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 );
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
2019-01-06 06:51:20 +05:00
// Make sure the path is either drive rooted or UNC Path
2019-01-23 09:37:45 -08:00
if (! IsAbsolutePath ( path ) && ! Utils . PathIsUnc ( path ))
2016-03-30 14:20:52 -07:00
{
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
2019-02-28 10:00:23 -05:00
{
2016-03-30 14:20:52 -07:00
throw ;
2019-02-28 10:00:23 -05:00
}
2016-03-30 14:20:52 -07:00
}
return true ;
}
/// <summary>
/// Gets the item at the specified path.
/// </summary>
/// <param name="path">
2017-01-16 13:31:14 -08:00
/// A fully qualified path representing a file or directory in the
2016-03-30 14:20:52 -07:00
/// file system.
/// </param>
/// <returns>
2017-01-16 13:31:14 -08:00
/// Nothing. FileInfo and DirectoryInfo objects are written to the
2016-03-30 14:20:52 -07:00
/// 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 ;
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
// The parameter was null, throw an exception
throw PSTraceSource . NewArgumentException ( "path" );
}
try
{
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
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 };
}
}
}
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
FileSystemInfo result = GetFileSystemItem ( path , ref isContainer , false );
if ( result != null )
{
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
// 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 ;
}
2016-07-29 13:02:49 -07:00
if ((! WildcardPattern . ContainsWildcardCharacters ( desiredStream )) && (! foundStream ))
2016-03-30 14:20:52 -07:00
{
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
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
{
// Otherwise, return the item itself.
WriteItemObject ( result , result . FullName , isContainer );
}
}
else
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . ItemNotFound , path );
2016-03-30 14:20:52 -07:00
Exception e = new IOException ( error );
WriteError ( new ErrorRecord (
e ,
"ItemNotFound" ,
ErrorCategory . ObjectNotFound ,
path ));
}
}
catch ( IOException ioError )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
ErrorRecord er = new ErrorRecord ( ioError , "GetItemIOError" , ErrorCategory . ReadError , path );
2016-07-29 13:02:49 -07:00
WriteError ( er );
2016-03-30 14:20:52 -07:00
}
catch ( UnauthorizedAccessException accessException )
{
WriteError ( new ErrorRecord ( accessException , "GetItemUnauthorizedAccessError" , ErrorCategory . PermissionDenied , path ));
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
private FileSystemInfo GetFileSystemItem ( string path , ref bool isContainer , bool showHidden )
{
path = NormalizePath ( path );
2018-06-04 10:00:56 +05:00
FileInfo result = new FileInfo ( path );
2016-03-30 14:20:52 -07:00
2018-06-04 21:51:04 -07:00
// FileInfo.Exists is always false for a directory path, so we check the attribute for existence.
2018-06-04 10:00:56 +05:00
var attributes = result . Attributes ;
2018-06-04 21:51:04 -07:00
if (( int ) attributes == - 1 ) { /* Path doesn't exist. */ return null ; }
2018-06-04 10:00:56 +05:00
bool hidden = attributes . HasFlag ( FileAttributes . Hidden );
isContainer = attributes . HasFlag ( FileAttributes . Directory );
2016-03-30 14:20:52 -07:00
FlagsExpression < FileAttributes > evaluator = null ;
FlagsExpression < FileAttributes > switchEvaluator = null ;
GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters ;
if ( fspDynamicParam != null )
{
evaluator = fspDynamicParam . Attributes ;
2016-09-01 16:03:02 +00:00
switchEvaluator = FormatAttributeSwitchParameters ();
2016-03-30 14:20:52 -07:00
}
bool filterHidden = false ; // "Hidden" is specified somewhere in the expression
2016-08-28 05:51:23 +00:00
bool switchFilterHidden = false ; // "Hidden" is specified somewhere in the parameters
2016-03-30 14:20:52 -07:00
2018-06-26 02:37:09 +05:00
if ( evaluator != null )
2016-03-30 14:20:52 -07:00
{
filterHidden = evaluator . ExistsInExpression ( FileAttributes . Hidden );
}
2018-12-23 22:20:06 -08:00
2018-06-26 02:37:09 +05:00
if ( switchEvaluator != null )
2016-03-30 14:20:52 -07:00
{
switchFilterHidden = switchEvaluator . ExistsInExpression ( FileAttributes . Hidden );
}
2016-08-28 05:51:23 +00:00
// if "Hidden" is specified in the attribute filter dynamic parameters
2016-03-30 14:20:52 -07:00
// also return the object
2018-06-04 10:00:56 +05:00
if (! isContainer )
2016-03-30 14:20:52 -07:00
{
2018-06-04 21:51:04 -07:00
if (! hidden || Force || showHidden || filterHidden || switchFilterHidden )
2018-06-04 10:00:56 +05:00
{
s_tracer . WriteLine ( "Got file info: {0}" , result );
return result ;
}
2016-03-30 14:20:52 -07:00
}
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 =
2018-12-31 20:44:16 +05:00
string . Compare (
2016-03-30 14:20:52 -07:00
Path . GetPathRoot ( path ),
2018-06-04 10:00:56 +05:00
result . FullName ,
2016-03-30 14:20:52 -07:00
StringComparison . OrdinalIgnoreCase ) == 0 ;
2016-08-28 05:51:23 +00:00
// if "Hidden" is specified in the attribute filter dynamic parameters
2016-03-30 14:20:52 -07:00
// also return the object
2018-06-04 21:51:04 -07:00
if ( isRootPath || ! hidden || Force || showHidden || filterHidden || switchFilterHidden )
2016-03-30 14:20:52 -07:00
{
2018-06-04 10:00:56 +05:00
s_tracer . WriteLine ( "Got directory info: {0}" , result );
return new DirectoryInfo ( path );
2016-03-30 14:20:52 -07:00
}
}
2018-12-23 22:20:06 -08:00
2018-06-04 10:00:56 +05:00
return null ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <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 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
path = NormalizePath ( path );
string action = FileSystemProviderStrings . InvokeItemAction ;
string resource = StringUtil . Format ( FileSystemProviderStrings . InvokeItemResourceFileTemplate , path );
if ( ShouldProcess ( resource , action ))
{
2017-07-27 22:17:38 -07:00
var invokeProcess = new System . Diagnostics . Process ();
invokeProcess . StartInfo . FileName = path ;
2017-08-10 13:32:57 -07:00
#if UNIX
2018-07-02 12:50:19 -07:00
bool useShellExecute = false ;
2017-08-10 13:32:57 -07:00
if ( Directory . Exists ( path ))
2017-03-14 11:11:41 -07:00
{
2017-09-07 10:34:40 -07:00
// Path points to a directory. We have to use xdg-open/open on Linux/macOS.
2018-07-02 12:50:19 -07:00
useShellExecute = true ;
2017-03-14 11:11:41 -07:00
}
2017-07-27 22:17:38 -07:00
else
2017-03-14 11:11:41 -07:00
{
2017-07-27 22:17:38 -07:00
try
{
// Try Process.Start first. This works for executables on Win/Unix platforms
invokeProcess . Start ();
}
2017-08-10 13:32:57 -07:00
catch ( Win32Exception ex ) when ( ex . NativeErrorCode == 13 )
2017-07-27 22:17:38 -07:00
{
2017-08-10 13:32:57 -07:00
// Error code 13 -- Permission denied
2017-09-07 10:34:40 -07:00
// The file is possibly not an executable. We try xdg-open/open on Linux/macOS.
2018-07-02 12:50:19 -07:00
useShellExecute = true ;
2017-07-27 22:17:38 -07:00
}
}
2018-07-02 12:50:19 -07:00
if ( useShellExecute )
2017-07-27 22:17:38 -07:00
{
2018-07-02 12:50:19 -07:00
invokeProcess . StartInfo . UseShellExecute = true ;
2017-05-24 13:30:54 -07:00
invokeProcess . Start ();
2017-07-27 22:17:38 -07:00
}
2017-08-10 13:32:57 -07:00
#else
// Use ShellExecute when it's not a headless SKU
invokeProcess . StartInfo . UseShellExecute = Platform . IsWindowsDesktop ;
invokeProcess . Start ();
#endif
2016-03-30 14:20:52 -07:00
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#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>
2017-01-16 13:31:14 -08:00
/// Nothing. FileInfo and DirectoryInfo objects that match the filter are written to the
2016-03-30 14:20:52 -07:00
/// 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 );
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#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 );
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#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
2017-01-16 13:31:14 -08:00
/// if the path contains directory separators. Those are not supported by the
2016-03-30 14:20:52 -07:00
/// 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.
2018-12-31 15:10:15 +05:00
if ((! string . IsNullOrEmpty ( filter )) ||
2018-10-31 08:26:15 +05:00
( path . Contains ( StringLiterals . DefaultPathSeparator , StringComparison . Ordinal )) ||
( path . Contains ( StringLiterals . AlternatePathSeparator , StringComparison . Ordinal )) ||
2016-03-30 14:20:52 -07:00
( 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
2017-01-16 13:31:14 -08:00
// mapped to a [6][~n].[3] disambiguation bucket
2016-03-30 14:20:52 -07:00
// *.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 ,
2016-07-29 13:02:49 -07:00
bool recurse ,
uint depth ,
bool nameOnly ,
2016-03-30 14:20:52 -07:00
ReturnContainers returnContainers )
{
// Verify parameters
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
path = NormalizePath ( path );
2018-07-25 22:02:09 +05:00
var fsinfo = GetFileSystemInfo ( path , out bool isDirectory );
if ( fsinfo != null )
2016-03-30 14:20:52 -07:00
{
if ( isDirectory )
{
2017-07-06 12:28:27 -07:00
InodeTracker tracker = null ;
if ( recurse )
{
GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters ;
if ( fspDynamicParam != null && fspDynamicParam . FollowSymlink )
{
2018-07-25 22:02:09 +05:00
tracker = new InodeTracker ( fsinfo . FullName );
2017-07-06 12:28:27 -07:00
}
}
2016-03-30 14:20:52 -07:00
// Enumerate the directory
2018-07-25 22:02:09 +05:00
Dir (( DirectoryInfo ) fsinfo , recurse , depth , nameOnly , returnContainers , tracker );
2016-03-30 14:20:52 -07:00
}
else
{
FlagsExpression < FileAttributes > evaluator = null ;
FlagsExpression < FileAttributes > switchEvaluator = null ;
GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters ;
if ( fspDynamicParam != null )
{
evaluator = fspDynamicParam . Attributes ;
2016-09-01 16:03:02 +00:00
switchEvaluator = FormatAttributeSwitchParameters ();
2016-03-30 14:20:52 -07:00
}
bool attributeFilter = true ;
bool switchAttributeFilter = true ;
bool filterHidden = false ; // "Hidden" is specified somewhere in the expression
2016-08-28 05:51:23 +00:00
bool switchFilterHidden = false ; // "Hidden" is specified somewhere in the parameters
2016-03-30 14:20:52 -07:00
2018-06-26 02:37:09 +05:00
if ( evaluator != null )
2016-03-30 14:20:52 -07:00
{
2018-07-25 22:02:09 +05:00
attributeFilter = evaluator . Evaluate ( fsinfo . Attributes ); // expressions
2016-03-30 14:20:52 -07:00
filterHidden = evaluator . ExistsInExpression ( FileAttributes . Hidden );
}
2018-12-23 22:20:06 -08:00
2018-06-26 02:37:09 +05:00
if ( switchEvaluator != null )
2016-03-30 14:20:52 -07:00
{
2018-07-25 22:02:09 +05:00
switchAttributeFilter = switchEvaluator . Evaluate ( fsinfo . Attributes ); // switch parameters
2016-03-30 14:20:52 -07:00
switchFilterHidden = switchEvaluator . ExistsInExpression ( FileAttributes . Hidden );
}
2018-07-25 22:02:09 +05:00
bool hidden = ( fsinfo . Attributes & FileAttributes . Hidden ) != 0 ;
2016-03-30 14:20:52 -07:00
// 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 (
2018-07-25 22:02:09 +05:00
fsinfo . Name ,
fsinfo . FullName ,
2016-03-30 14:20:52 -07:00
false );
}
else
2019-02-28 10:00:23 -05:00
{
2018-07-25 22:02:09 +05:00
WriteItemObject ( fsinfo , path , false );
2019-02-28 10:00:23 -05:00
}
2016-03-30 14:20:52 -07:00
}
}
}
else
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . ItemDoesNotExist , path );
2016-03-30 14:20:52 -07:00
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 ,
2017-07-06 12:28:27 -07:00
ReturnContainers returnContainers ,
InodeTracker tracker ) // tracker will be non-null only if the user invoked the -FollowSymLinks and -Recurse switch parameters.
2016-03-30 14:20:52 -07:00
{
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
2018-10-27 21:48:49 -07:00
target . Add ( directory . EnumerateDirectories ( Filter , _enumerationOptions ));
2016-03-30 14:20:52 -07:00
}
// Making sure to obey the StopProcessing.
if ( Stopping )
{
return ;
}
// Use the specified filter when retrieving the
// children
2018-10-27 21:48:49 -07:00
target . Add ( directory . EnumerateFiles ( Filter , _enumerationOptions ));
2016-03-30 14:20:52 -07:00
}
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 ;
2016-09-01 16:03:02 +00:00
switchEvaluator = FormatAttributeSwitchParameters ();
2016-03-30 14:20:52 -07:00
}
// Write out the items
foreach ( IEnumerable < FileSystemInfo > childList in target )
{
2016-04-06 12:01:07 -07:00
// On some systems, this is already sorted. For consistency, always sort again.
IEnumerable < FileSystemInfo > sortedChildList = childList . OrderBy ( c => c . Name , StringComparer . CurrentCultureIgnoreCase );
2016-07-29 13:02:49 -07:00
foreach ( FileSystemInfo filesystemInfo in sortedChildList )
2016-03-30 14:20:52 -07:00
{
// Making sure to obey the StopProcessing.
if ( Stopping )
{
return ;
}
2017-05-25 17:24:47 -07:00
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 ;
2018-06-26 02:37:09 +05:00
if ( evaluator != null )
2017-05-25 17:24:47 -07:00
{
attributeFilter = evaluator . Evaluate ( filesystemInfo . Attributes );
filterHidden = evaluator . ExistsInExpression ( FileAttributes . Hidden );
}
2018-12-23 22:20:06 -08:00
2018-06-26 02:37:09 +05:00
if ( switchEvaluator != null )
2017-05-25 17:24:47 -07:00
{
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 );
}
2016-03-30 14:20:52 -07:00
else
2017-05-25 17:24:47 -07:00
{
if ( filesystemInfo is FileInfo )
2019-02-28 10:00:23 -05:00
{
2017-05-25 17:24:47 -07:00
WriteItemObject ( filesystemInfo , filesystemInfo . FullName , false );
2019-02-28 10:00:23 -05:00
}
2017-05-25 17:24:47 -07:00
else
2019-02-28 10:00:23 -05:00
{
2017-05-25 17:24:47 -07:00
WriteItemObject ( filesystemInfo , filesystemInfo . FullName , true );
2019-02-28 10:00:23 -05:00
}
2017-05-25 17:24:47 -07:00
}
2016-03-30 14:20:52 -07:00
}
}
2017-05-25 17:24:47 -07:00
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 ));
}
2018-12-20 22:54:10 -08:00
}
}
2016-03-30 14:20:52 -07:00
bool isFilterHiddenSpecified = false ; // "Hidden" is specified somewhere in the expression
bool isSwitchFilterHiddenSpecified = false ; // "Hidden" is specified somewhere in the parameters
2018-06-26 02:37:09 +05:00
if ( evaluator != null )
2016-03-30 14:20:52 -07:00
{
isFilterHiddenSpecified = evaluator . ExistsInExpression ( FileAttributes . Hidden );
}
2018-12-23 22:20:06 -08:00
2018-06-26 02:37:09 +05:00
if ( switchEvaluator != null )
2016-03-30 14:20:52 -07:00
{
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 ;
}
2017-07-06 12:28:27 -07:00
bool hidden = false ;
if (! Force )
2016-03-30 14:20:52 -07:00
{
2017-07-06 12:28:27 -07:00
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.
2019-08-13 17:45:41 -07:00
// c) it is not a reparse point with a target
2017-07-06 12:28:27 -07:00
if ( tracker == null )
2017-05-15 14:09:46 -07:00
{
2019-08-13 17:45:41 -07:00
if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( recursiveDirectory ))
2017-07-06 12:28:27 -07:00
{
continue ;
}
}
else if (! tracker . TryVisitPath ( recursiveDirectory . FullName ))
{
WriteWarning ( StringUtil . Format ( FileSystemProviderStrings . AlreadyListedDirectory ,
recursiveDirectory . FullName ));
continue ;
2017-05-15 14:09:46 -07:00
}
2017-07-06 12:28:27 -07:00
Dir ( recursiveDirectory , recurse , depth - 1 , nameOnly , returnContainers , tracker );
2016-03-30 14:20:52 -07:00
}
2018-12-20 22:54:10 -08:00
}
}
}
2016-03-30 14:20:52 -07:00
}
catch ( ArgumentException argException )
{
WriteError ( new ErrorRecord ( argException , "DirArgumentError" , ErrorCategory . InvalidArgument , directory . FullName ));
}
catch ( IOException e )
{
// 2004/10/13-JonN removed ResourceActionFailedException wrapper
2016-07-29 13:02:49 -07:00
WriteError ( new ErrorRecord ( e , "DirIOError" , ErrorCategory . ReadError , directory . FullName ));
2016-03-30 14:20:52 -07:00
}
catch ( UnauthorizedAccessException uae )
{
// 2004/10/13-JonN removed ResourceActionFailedException wrapper
WriteError ( new ErrorRecord ( uae , "DirUnauthorizedAccessError" , ErrorCategory . PermissionDenied , directory . FullName ));
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Create an enum expression evaluator for user-specified attribute filtering
2017-01-16 13:31:14 -08:00
/// switch parameters.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <returns>
/// If any attribute filtering switch parameters are set,
/// returns an evaluator that evaluates these parameters.
/// Otherwise,
/// returns NULL
/// </returns>
2016-09-01 16:03:02 +00:00
private FlagsExpression < FileAttributes > FormatAttributeSwitchParameters ()
2016-03-30 14:20:52 -07:00
{
FlagsExpression < FileAttributes > switchParamEvaluator = null ;
StringBuilder sb = new StringBuilder ();
if ((( GetChildDynamicParameters ) DynamicParameters ). Directory )
{
sb . Append ( "+Directory" );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if ((( GetChildDynamicParameters ) DynamicParameters ). File )
{
sb . Append ( "+!Directory" );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if ((( GetChildDynamicParameters ) DynamicParameters ). System )
{
sb . Append ( "+System" );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if ((( GetChildDynamicParameters ) DynamicParameters ). ReadOnly )
{
sb . Append ( "+ReadOnly" );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if ((( GetChildDynamicParameters ) DynamicParameters ). Hidden )
{
sb . Append ( "+Hidden" );
}
string switchParamString = sb . ToString ();
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( switchParamString ))
2016-03-30 14:20:52 -07:00
{
// Remove unnecessary PLUS sign
switchParamEvaluator = new FlagsExpression < FileAttributes >( switchParamString . Substring ( 1 ));
}
return switchParamEvaluator ;
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Provides a mode property for FileSystemInfo.
2016-03-30 14:20:52 -07:00
/// </summary>
2018-12-29 08:25:49 +05:00
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
2019-03-12 23:49:16 +01:00
/// <returns>A string representation of the FileAttributes, with one letter per attribute.</returns>
2016-03-30 14:20:52 -07:00
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
2019-03-12 23:49:16 +01:00
public static string Mode ( PSObject instance ) => Mode ( instance , excludeHardLink : false );
/// <summary>
/// Provides a ModeWithoutHardLink property for FileSystemInfo, without HardLinks for performance reasons.
/// </summary>
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
/// <returns>A string representation of the FileAttributes, with one letter per attribute.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
public static string ModeWithoutHardLink ( PSObject instance ) => Mode ( instance , excludeHardLink : true );
private static string Mode ( PSObject instance , bool excludeHardLink )
2016-03-30 14:20:52 -07:00
{
2019-03-12 23:49:16 +01:00
string ToModeString ( FileSystemInfo fileSystemInfo )
2019-02-28 10:00:23 -05:00
{
2019-03-12 23:49:16 +01:00
FileAttributes fileAttributes = fileSystemInfo . Attributes ;
bool isReparsePoint = InternalSymbolicLinkLinkCodeMethods . IsReparsePoint ( fileSystemInfo );
bool isLink = isReparsePoint || ( excludeHardLink ? false : InternalSymbolicLinkLinkCodeMethods . IsHardLink ( fileSystemInfo ));
if (! isLink )
{
// special casing for the common cases - no allocations
switch ( fileAttributes )
{
case FileAttributes . Archive :
return "-a---" ;
case FileAttributes . Directory :
return "d----" ;
case FileAttributes . Normal :
return "-----" ;
case FileAttributes . Directory | FileAttributes . ReadOnly :
return "d-r--" ;
case FileAttributes . Archive | FileAttributes . ReadOnly :
return "-ar--" ;
}
}
bool isDirectory = fileAttributes . HasFlag ( FileAttributes . Directory );
ReadOnlySpan < char > mode = stackalloc char []
{
isLink ? 'l' : isDirectory ? 'd' : '-' ,
2019-04-24 19:27:22 +05:00
fileAttributes . HasFlag ( FileAttributes . Archive ) ? 'a' : '-' ,
2019-03-12 23:49:16 +01:00
fileAttributes . HasFlag ( FileAttributes . ReadOnly ) ? 'r' : '-' ,
2019-04-24 19:27:22 +05:00
fileAttributes . HasFlag ( FileAttributes . Hidden ) ? 'h' : '-' ,
fileAttributes . HasFlag ( FileAttributes . System ) ? 's' : '-' ,
2019-03-12 23:49:16 +01:00
};
return new string ( mode );
2019-02-28 10:00:23 -05:00
}
2016-03-30 14:20:52 -07:00
2019-03-12 23:49:16 +01:00
return instance ?. BaseObject is FileSystemInfo fileInfo
? ToModeString ( fileInfo )
: string . Empty ;
}
2016-03-30 14:20:52 -07:00
2019-03-12 23:49:16 +01:00
/// <summary>
/// Provides a NameString property for FileSystemInfo.
/// </summary>
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
/// <returns>Name if a file or directory, Name -> Target if symlink.</returns>
public static string NameString ( PSObject instance )
{
return instance ?. BaseObject is FileSystemInfo fileInfo
2019-08-13 17:45:41 -07:00
? InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( fileInfo )
2019-03-12 23:49:16 +01:00
? $"{fileInfo.Name} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}"
: fileInfo . Name
: string . Empty ;
}
2016-03-30 14:20:52 -07:00
2019-03-12 23:49:16 +01:00
/// <summary>
/// Provides a LengthString property for FileSystemInfo.
/// </summary>
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
/// <returns>Length as a string.</returns>
public static string LengthString ( PSObject instance )
{
return instance ?. BaseObject is FileInfo fileInfo
? fileInfo . Attributes . HasFlag ( FileAttributes . Offline )
? $"({fileInfo.Length})"
: fileInfo . Length . ToString ()
: string . Empty ;
}
/// <summary>
/// Provides a LastWriteTimeString property for FileSystemInfo.
/// </summary>
/// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
/// <returns>LastWriteTime formatted as short date + short time.</returns>
public static string LastWriteTimeString ( PSObject instance )
{
return instance ?. BaseObject is FileSystemInfo fileInfo
? string . Format ( CultureInfo . CurrentCulture , "{0,10:d} {0,8:t}" , fileInfo . LastWriteTime )
: string . Empty ;
2016-03-30 14:20:52 -07:00
}
#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>
2017-01-16 13:31:14 -08:00
/// Nothing. The renamed DirectoryInfo or FileInfo object is
2016-03-30 14:20:52 -07:00
/// 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
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
path = NormalizePath ( path );
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( newName ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "newName" );
}
// Clean up "newname" to fix some common usability problems:
// Rename .\foo.txt .\bar.txt
2019-03-24 13:07:25 -04:00
// Rename C:\temp\foo.txt C:\temp\bar.txt
2016-07-29 13:02:49 -07:00
if ( newName . StartsWith ( ".\\" , StringComparison . OrdinalIgnoreCase ) ||
2016-03-30 14:20:52 -07:00
newName . StartsWith ( "./" , StringComparison . OrdinalIgnoreCase ))
{
newName = newName . Remove ( 0 , 2 );
}
2018-12-31 20:44:16 +05:00
else if ( string . Equals ( Path . GetDirectoryName ( path ), Path . GetDirectoryName ( newName ), StringComparison . OrdinalIgnoreCase ))
2016-03-30 14:20:52 -07:00
{
newName = Path . GetFileName ( newName );
}
2019-01-06 06:51:20 +05:00
// 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.
2018-12-31 20:44:16 +05:00
if ( string . Compare ( Path . GetFileName ( newName ), newName , StringComparison . OrdinalIgnoreCase ) != 0 )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "newName" , FileSystemProviderStrings . RenameError );
}
// Verify that the target doesn't represent a device name
2016-04-28 13:41:25 -07:00
if ( PathIsReservedDeviceName ( newName , "RenameError" ))
2016-03-30 14:20:52 -07:00
{
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 ;
2016-07-29 13:02:49 -07:00
string resource = StringUtil . Format ( FileSystemProviderStrings . RenameItemResourceFileTemplate , dir . FullName , newPath );
2016-03-30 14:20:52 -07:00
if ( ShouldProcess ( resource , action ))
{
// Now move the file
dir . MoveTo ( newPath );
result = dir ;
2016-07-29 13:02:49 -07:00
WriteItemObject ( result , result . FullName , isContainer );
2016-03-30 14:20:52 -07:00
}
}
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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "RenameItemIOError" , ErrorCategory . WriteError , path ));
}
catch ( UnauthorizedAccessException accessException )
{
WriteError ( new ErrorRecord ( accessException , "RenameItemUnauthorizedAccessError" , ErrorCategory . PermissionDenied , path ));
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#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">
2017-01-16 13:31:14 -08:00
/// Specify "file" to create a file.
2016-03-30 14:20:52 -07:00
/// 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>
2017-01-16 13:31:14 -08:00
/// Nothing. The new DirectoryInfo or FileInfo object is
2016-03-30 14:20:52 -07:00
/// 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
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( type ))
2016-03-30 14:20:52 -07:00
{
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 ))
{
2017-01-16 13:31:14 -08:00
// Create the file with read/write access and
2016-03-30 14:20:52 -07:00
// 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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
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 ;
2016-07-29 13:02:49 -07:00
if ( itemType == ItemType . SymbolicLink )
2019-02-28 10:00:23 -05:00
{
2016-03-30 14:20:52 -07:00
action = FileSystemProviderStrings . NewItemActionSymbolicLink ;
2019-02-28 10:00:23 -05:00
}
2016-07-29 13:02:49 -07:00
else if ( itemType == ItemType . HardLink )
2019-02-28 10:00:23 -05:00
{
2016-03-30 14:20:52 -07:00
action = FileSystemProviderStrings . NewItemActionHardLink ;
2019-02-28 10:00:23 -05:00
}
2016-03-30 14:20:52 -07:00
string resource = StringUtil . Format ( FileSystemProviderStrings . NewItemActionTemplate , path );
if ( ShouldProcess ( resource , action ))
{
bool isDirectory = false ;
2019-02-13 16:51:05 +05:00
string strTargetPath = value ?. ToString ();
2016-03-30 14:20:52 -07:00
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( strTargetPath ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentNullException ( "value" );
}
bool exists = false ;
2016-04-09 18:23:45 -07:00
// 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.
2016-03-30 14:20:52 -07:00
try
{
2018-07-25 22:02:09 +05:00
exists = GetFileSystemInfo ( strTargetPath , out isDirectory ) != null ;
// Pretend the target exists if we're making a symbolic link.
2017-04-14 14:23:14 -07:00
if ( itemType == ItemType . SymbolicLink )
2018-07-25 22:02:09 +05:00
{
exists = true ;
}
2016-03-30 14:20:52 -07:00
}
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 )
{
2019-01-06 06:51:20 +05:00
// Hard links can only be to files, not directories.
2016-03-30 14:20:52 -07:00
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
{
2018-07-25 22:02:09 +05:00
symLinkExists = GetFileSystemInfo ( path , out isSymLinkDirectory ) != null ;
2016-03-30 14:20:52 -07:00
}
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
2019-02-28 10:00:23 -05:00
{
2016-03-30 14:20:52 -07:00
throw ;
2019-02-28 10:00:23 -05:00
}
2016-03-30 14:20:52 -07:00
}
}
else
{
2016-07-29 13:02:49 -07:00
if ( symLinkExists )
2016-03-30 14:20:52 -07:00
{
2017-05-05 10:09:34 -07:00
string message = StringUtil . Format ( FileSystemProviderStrings . SymlinkItemExists , path );
WriteError ( new ErrorRecord ( new IOException ( message ), "SymLinkExists" , ErrorCategory . ResourceExists , path ));
2016-03-30 14:20:52 -07:00
return ;
}
}
bool success = false ;
2016-07-29 13:02:49 -07:00
if ( itemType == ItemType . SymbolicLink )
2016-03-30 14:20:52 -07:00
{
2016-10-03 15:28:58 -07:00
#if UNIX
success = Platform . NonWindowsCreateSymbolicLink ( path , strTargetPath );
#else
success = WinCreateSymbolicLink ( path , strTargetPath , isDirectory );
#endif
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
else if ( itemType == ItemType . HardLink )
2016-03-30 14:20:52 -07:00
{
2016-10-03 13:50:57 -07:00
#if UNIX
success = Platform . NonWindowsCreateHardLink ( path , strTargetPath );
#else
success = WinCreateHardLink ( path , strTargetPath );
#endif
2016-03-30 14:20:52 -07:00
}
if (! success )
{
2016-10-03 15:22:28 -07:00
// Porting note: The Win32Exception will report the correct error on Linux
2016-03-30 14:20:52 -07:00
int errorCode = Marshal . GetLastWin32Error ();
Win32Exception w32Exception = new Win32Exception (( int ) errorCode );
2016-10-03 15:41:38 -07:00
#if UNIX
if ( Platform . Unix . GetErrorCategory ( errorCode ) == ErrorCategory . PermissionDenied )
#else
2019-01-06 06:51:20 +05:00
if ( errorCode == 1314 ) // ERROR_PRIVILEGE_NOT_HELD
2016-10-03 15:41:38 -07:00
#endif
2016-03-30 14:20:52 -07:00
{
string message = FileSystemProviderStrings . ElevationRequired ;
WriteError ( new ErrorRecord ( new UnauthorizedAccessException ( message , w32Exception ), "NewItemSymbolicLinkElevationRequired" , ErrorCategory . PermissionDenied , value . ToString ()));
return ;
}
2019-01-06 06:51:20 +05:00
if ( errorCode == 1 ) // ERROR_INVALID_FUNCTION
2016-03-30 14:20:52 -07:00
{
string message = null ;
if ( itemType == ItemType . SymbolicLink )
2019-03-24 13:07:25 -04:00
{
2016-03-30 14:20:52 -07:00
message = FileSystemProviderStrings . SymbolicLinkNotSupported ;
2019-03-24 13:07:25 -04:00
}
2016-07-29 13:02:49 -07:00
else
2019-03-24 13:07:25 -04:00
{
2016-03-30 14:20:52 -07:00
message = FileSystemProviderStrings . HardLinkNotSupported ;
2019-03-24 13:07:25 -04:00
}
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( new InvalidOperationException ( message , w32Exception ), "NewItemInvalidOperation" , ErrorCategory . InvalidOperation , value . ToString ()));
return ;
}
throw w32Exception ;
}
else
{
2016-07-29 13:02:49 -07:00
if ( isDirectory )
2016-03-30 14:20:52 -07:00
{
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 ;
2019-02-13 16:51:05 +05:00
string strTargetPath = value ?. ToString ();
2016-03-30 14:20:52 -07:00
bool exists = false ;
try
{
2018-07-25 22:02:09 +05:00
exists = GetFileSystemInfo ( strTargetPath , out isDirectory ) != null ;
2016-03-30 14:20:52 -07:00
}
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 ;
}
2019-01-06 06:51:20 +05:00
// Junctions can only be directories.
2016-03-30 14:20:52 -07:00
if (! isDirectory )
{
string message = StringUtil . Format ( FileSystemProviderStrings . ItemNotDirectory , value );
WriteError ( new ErrorRecord ( new InvalidOperationException ( message ), "ItemNotDirectory" , ErrorCategory . InvalidOperation , value ));
return ;
}
bool isPathDirectory = false ;
2018-07-25 22:02:09 +05:00
FileSystemInfo pathDirInfo ;
2016-03-30 14:20:52 -07:00
try
{
2018-07-25 22:02:09 +05:00
pathDirInfo = GetFileSystemInfo ( path , out isPathDirectory );
2016-03-30 14:20:52 -07:00
}
catch ( Exception e )
{
WriteError ( new ErrorRecord ( e , "AccessException" , ErrorCategory . PermissionDenied , strTargetPath ));
return ;
}
2018-07-25 22:02:09 +05:00
bool pathExists = pathDirInfo != null ;
2016-03-30 14:20:52 -07:00
if ( pathExists )
{
2019-01-06 06:51:20 +05:00
// Junctions can only be directories.
2016-03-30 14:20:52 -07:00
if (! isPathDirectory )
{
string message = StringUtil . Format ( FileSystemProviderStrings . ItemNotDirectory , path );
WriteError ( new ErrorRecord ( new InvalidOperationException ( message ), "ItemNotDirectory" , ErrorCategory . InvalidOperation , path ));
return ;
}
2019-01-06 06:51:20 +05:00
// Junctions cannot have files
2018-07-25 22:02:09 +05:00
if ( DirectoryInfoHasChildItems (( DirectoryInfo ) pathDirInfo ))
2016-03-30 14:20:52 -07:00
{
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
2019-02-28 10:00:23 -05:00
{
2016-03-30 14:20:52 -07:00
throw ;
2019-02-28 10:00:23 -05:00
}
2016-03-30 14:20:52 -07:00
}
}
}
else
{
CreateDirectory ( path , false );
2018-07-25 22:02:09 +05:00
pathDirInfo = new DirectoryInfo ( path );
2016-03-30 14:20:52 -07:00
}
try
{
2016-04-06 12:01:07 -07:00
bool junctionCreated = WinCreateJunction ( path , strTargetPath );
2016-03-30 14:20:52 -07:00
if ( junctionCreated )
{
WriteItemObject ( pathDirInfo , path , true );
}
2019-01-06 06:51:20 +05:00
else // rollback the directory creation if we created it.
2016-03-30 14:20:52 -07:00
{
if (! pathExists )
{
pathDirInfo . Delete ();
}
}
}
catch ( Exception exception )
{
2019-01-06 06:51:20 +05:00
// rollback the directory creation if it was created.
2016-03-30 14:20:52 -07:00
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
2019-02-28 10:00:23 -05:00
{
2016-03-30 14:20:52 -07:00
throw ;
2019-02-28 10:00:23 -05:00
}
2016-03-30 14:20:52 -07:00
}
}
}
else
{
throw PSTraceSource . NewArgumentException ( "type" , FileSystemProviderStrings . UnknownType );
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
2016-04-06 12:01:07 -07:00
private static bool WinCreateSymbolicLink ( string path , string strTargetPath , bool isDirectory )
{
2019-01-08 10:43:24 -08:00
// The new AllowUnprivilegedCreate is only available on Win10 build 14972 or newer
var flags = isDirectory ? NativeMethods . SymbolicLinkFlags . Directory : NativeMethods . SymbolicLinkFlags . File ;
2019-01-25 13:14:29 -08:00
Version minBuildOfDeveloperMode = new Version ( 10 , 0 , 14972 , 0 );
if ( Environment . OSVersion . Version >= minBuildOfDeveloperMode )
2019-01-08 10:43:24 -08:00
{
flags |= NativeMethods . SymbolicLinkFlags . AllowUnprivilegedCreate ;
}
2019-03-12 23:49:16 +01:00
var created = NativeMethods . CreateSymbolicLink ( path , strTargetPath , flags );
return created ;
2016-04-06 12:01:07 -07:00
}
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 ;
}
2016-03-30 14:20:52 -07:00
private enum ItemType
{
Unknown ,
File ,
Directory ,
SymbolicLink ,
Junction ,
HardLink
};
private static ItemType GetItemType ( string input )
{
ItemType itemType = ItemType . Unknown ;
WildcardPattern typeEvaluator =
2016-07-29 13:02:49 -07:00
WildcardPattern . Get ( input + "*" ,
2016-03-30 14:20:52 -07:00
WildcardOptions . IgnoreCase |
2016-07-29 13:02:49 -07:00
WildcardOptions . Compiled );
2016-03-30 14:20:52 -07:00
if ( typeEvaluator . IsMatch ( "directory" ) ||
typeEvaluator . IsMatch ( "container" ))
{
itemType = ItemType . Directory ;
}
else if ( typeEvaluator . IsMatch ( "file" ))
{
itemType = ItemType . File ;
}
2016-07-29 13:02:49 -07:00
else if ( typeEvaluator . IsMatch ( "symboliclink" ))
2016-03-30 14:20:52 -07:00
{
itemType = ItemType . SymbolicLink ;
}
else if ( typeEvaluator . IsMatch ( "junction" ))
{
itemType = ItemType . Junction ;
}
else if ( typeEvaluator . IsMatch ( "hardlink" ))
{
itemType = ItemType . HardLink ;
}
return itemType ;
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Creates a directory at the specified path.
2016-03-30 14:20:52 -07:00
/// </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 (
2018-12-31 15:10:15 +05:00
! string . IsNullOrEmpty ( path ),
2016-03-30 14:20:52 -07:00
"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 ))
{
2018-12-22 01:26:06 -08:00
string errorMessage = StringUtil . Format ( FileSystemProviderStrings . DirectoryExist , path );
2016-03-30 14:20:52 -07:00
Exception e = new IOException ( errorMessage );
2016-07-29 13:02:49 -07:00
WriteError ( new ErrorRecord (
2016-03-30 14:20:52 -07:00
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 ))
{
2018-06-11 23:06:08 +00:00
var result = Directory . CreateDirectory ( Path . Combine ( parentPath , childName ));
2016-03-30 14:20:52 -07:00
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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "CreateDirectoryIOError" , ErrorCategory . WriteError , path ));
}
}
catch ( UnauthorizedAccessException accessException )
{
WriteError ( new ErrorRecord ( accessException , "CreateDirectoryUnauthorizedAccessError" , ErrorCategory . PermissionDenied , path ));
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
private bool CreateIntermediateDirectories ( string path )
{
bool result = false ;
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
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.
2018-12-22 01:26:06 -08:00
Stack < string > missingDirectories = new Stack < string >();
2016-03-30 14:20:52 -07:00
string previousParent = path ;
do
{
2018-12-31 20:44:16 +05:00
string root = string . Empty ;
2016-03-30 14:20:52 -07:00
if ( PSDriveInfo != null )
{
root = PSDriveInfo . Root ;
}
string parentPath = GetParentPath ( path , root );
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( parentPath ) &&
2018-12-31 20:44:16 +05:00
string . Compare (
2016-03-30 14:20:52 -07:00
parentPath ,
previousParent ,
StringComparison . OrdinalIgnoreCase ) != 0 )
{
if (! ItemExists ( parentPath ))
{
missingDirectories . Push ( parentPath );
}
else
{
break ;
}
}
else
{
break ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
previousParent = parentPath ;
2018-12-31 15:10:15 +05:00
} while (! string . IsNullOrEmpty ( previousParent ));
2016-03-30 14:20:52 -07:00
// Now create the missing directories
foreach ( string directoryPath in missingDirectories )
{
CreateDirectory ( directoryPath , false );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
result = true ;
}
catch ( ArgumentException argException )
{
WriteError ( new ErrorRecord ( argException , "CreateIntermediateDirectoriesArgumentError" , ErrorCategory . InvalidArgument , path ));
}
catch ( IOException ioException )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "CreateIntermediateDirectoriesIOError" , ErrorCategory . WriteError , path ));
}
catch ( UnauthorizedAccessException accessException )
{
WriteError ( new ErrorRecord ( accessException , "CreateIntermediateDirectoriesUnauthorizedAccessError" , ErrorCategory . PermissionDenied , path ));
}
return result ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#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 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
try
{
path = NormalizePath ( path );
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
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 };
}
}
}
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
2018-07-25 22:02:09 +05:00
FileSystemInfo fsinfo = GetFileSystemInfo ( path , out bool iscontainer );
2016-03-30 14:20:52 -07:00
if ( fsinfo == null )
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . ItemDoesNotExist , path );
2016-03-30 14:20:52 -07:00
Exception e = new IOException ( error );
WriteError ( new ErrorRecord ( e , "ItemDoesNotExist" , ErrorCategory . ObjectNotFound , path ));
return ;
}
2017-08-28 15:39:42 -07:00
#if UNIX
if ( iscontainer )
{
RemoveDirectoryInfoItem (( DirectoryInfo ) fsinfo , recurse , Force , true );
}
else
{
RemoveFileInfoItem (( FileInfo ) fsinfo , Force );
}
#else
2016-07-29 13:02:49 -07:00
if ((! removeStreams ) && iscontainer )
2016-03-30 14:20:52 -07:00
{
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 ; }
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
foundStream = true ;
2018-12-31 20:44:16 +05:00
string action = string . Format (
2016-03-30 14:20:52 -07:00
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 );
}
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
}
catch ( IOException exception )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( exception , "RemoveItemIOError" , ErrorCategory . WriteError , path ));
}
catch ( UnauthorizedAccessException accessException )
{
WriteError ( new ErrorRecord ( accessException , "RemoveItemUnauthorizedAccessError" , ErrorCategory . PermissionDenied , path ));
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Retrieves the dynamic parameters required for the Remove-Item cmdlet.
2016-03-30 14:20:52 -07:00
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="path">The path of the file to process.</param>
/// <param name="recurse">Whether to recurse into containers.</param>
2016-03-30 14:20:52 -07:00
/// <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 ;
2016-04-26 15:53:51 -07:00
// 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 )
2016-03-30 14:20:52 -07:00
{
2016-04-26 15:53:51 -07:00
// Confirm the user wants to remove the directory
string action = FileSystemProviderStrings . RemoveItemActionDirectory ;
continueRemoval = ShouldProcess ( directory . FullName , action );
2016-03-30 14:20:52 -07:00
}
2018-07-01 12:08:29 +05:00
if ( directory . Attributes . HasFlag ( FileAttributes . ReparsePoint ))
2016-03-30 14:20:52 -07:00
{
2018-06-14 23:35:44 +05:00
try
2016-03-30 14:20:52 -07:00
{
2018-07-01 12:08:29 +05:00
directory . Delete ();
2016-03-30 14:20:52 -07:00
}
2018-07-01 12:08:29 +05:00
catch ( Exception e )
2016-03-30 14:20:52 -07:00
{
2018-07-01 12:08:29 +05:00
string error = StringUtil . Format ( FileSystemProviderStrings . CannotRemoveItem , directory . FullName );
Exception exception = new IOException ( error , e );
WriteError ( new ErrorRecord ( exception , "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ));
2016-03-30 14:20:52 -07:00
}
2018-07-01 12:08:29 +05:00
return ;
2016-03-30 14:20:52 -07:00
}
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 ;
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( Filter ))
2016-03-30 14:20:52 -07:00
{
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 )
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . DirectoryNotEmpty , directory . FullName );
2016-03-30 14:20:52 -07:00
Exception e = new IOException ( error );
2016-07-29 13:02:49 -07:00
WriteError ( new ErrorRecord ( e , "DirectoryNotEmpty" , ErrorCategory . WriteError , directory ));
2016-03-30 14:20:52 -07:00
}
else // !hasChildren || force
{
// Finally, remove the directory
RemoveFileSystemItem ( directory , force );
}
2018-12-08 18:53:30 +02:00
}
}
2016-03-30 14:20:52 -07:00
/// <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 );
2018-12-08 18:53:30 +02:00
}
}
2016-03-30 14:20:52 -07:00
/// <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" );
2019-01-06 06:51:20 +05:00
// First check if we can delete this file when force is not specified.
2016-03-30 14:20:52 -07:00
if (! Force &&
( fileSystemInfo . Attributes & ( FileAttributes . Hidden | FileAttributes . System | FileAttributes . ReadOnly )) != 0 )
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . PermissionError );
2016-03-30 14:20:52 -07:00
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 ;
}
}
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#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 );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
return result ;
}
/// <summary>
/// Implementation of ItemExists for the provider. This implementation
/// allows the caller to decide if it wants to WriteError or not based
2019-01-06 04:40:24 +05:00
/// on the returned ErrorRecord.
2016-03-30 14:20:52 -07:00
/// </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 ;
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
bool result = false ;
path = NormalizePath ( path );
try
{
2018-07-25 22:02:09 +05:00
var fsinfo = GetFileSystemInfo ( path , out bool _ );
result = fsinfo != null ;
2016-03-30 14:20:52 -07:00
FileSystemItemProviderDynamicParameters itemExistsDynamicParameters =
DynamicParameters as FileSystemItemProviderDynamicParameters ;
// If the items see if we need to check the age of the file...
if ( result && itemExistsDynamicParameters != null )
{
2018-07-25 22:02:09 +05:00
DateTime lastWriteTime = fsinfo . LastWriteTime ;
2016-03-30 14:20:52 -07:00
if ( itemExistsDynamicParameters . OlderThan . HasValue )
{
2016-09-01 16:03:02 +00:00
result = lastWriteTime < itemExistsDynamicParameters . OlderThan . Value ;
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if ( itemExistsDynamicParameters . NewerThan . HasValue )
{
2016-09-01 16:03:02 +00:00
result = lastWriteTime > itemExistsDynamicParameters . NewerThan . Value ;
2016-03-30 14:20:52 -07:00
}
}
}
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 ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <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
2017-01-16 13:31:14 -08:00
/// parsing attributes similar to a cmdlet class or a
2016-03-30 14:20:52 -07:00
/// <see cref="System.Management.Automation.RuntimeDefinedParameterDictionary"/>.
2017-01-16 13:31:14 -08:00
///
2016-08-28 05:51:23 +00:00
/// The default implementation returns null. (no additional parameters)
2016-03-30 14:20:52 -07:00
/// </returns>
protected override object ItemExistsDynamicParameters ( string path )
{
using ( PSTransactionManager . GetEngineProtectionScope ())
{
return new FileSystemItemProviderDynamicParameters ();
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#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
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
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
2017-04-27 17:47:24 -07:00
// see if we should proceed and if it contains any children
if (( directory . Attributes & FileAttributes . Directory ) != FileAttributes . Directory )
return false ;
2016-03-30 14:20:52 -07:00
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 ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
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 ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#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 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( destinationPath ))
2016-03-30 14:20:52 -07:00
{
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 ;
}
}
2016-07-29 13:02:49 -07:00
_excludeMatcher = SessionStateUtilities . CreateWildcardsFromStrings ( Exclude , WildcardOptions . IgnoreCase );
2016-03-30 14:20:52 -07:00
// if the source and destination path are same (for a local copy) then flag it as error.
2017-04-21 16:09:53 -07:00
if (( toSession == null ) && ( fromSession == null ) && InternalSymbolicLinkLinkCodeMethods . IsSameFileSystemItem ( path , destinationPath ))
2016-03-30 14:20:52 -07:00
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . CopyError , path );
2016-03-30 14:20:52 -07:00
Exception e = new IOException ( error );
2017-04-21 16:09:53 -07:00
e . Data [ SelfCopyDataKey ] = destinationPath ;
2016-03-30 14:20:52 -07:00
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 );
}
}
2018-12-20 23:23:04 -08:00
else // Copy-Item local
2016-03-30 14:20:52 -07:00
{
CopyItemLocalOrToSession ( path , destinationPath , recurse , Force , null );
}
}
2016-07-29 13:02:49 -07:00
_excludeMatcher . Clear ();
_excludeMatcher = null ;
2018-12-20 22:54:10 -08:00
}
2016-03-30 14:20:52 -07:00
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 ;
2016-09-01 16:03:02 +00:00
InitializeFunctionPSCopyFileFromRemoteSession ( ps );
2016-03-30 14:20:52 -07:00
try
{
// get info on source
ps . AddCommand ( CopyFileRemoteUtils . PSCopyFromSessionHelperName );
ps . AddParameter ( "getPathItems" , path );
Hashtable op = SafeInvokeCommand . Invoke ( ps , this , null );
if ( op == null )
{
2018-12-31 20:44:16 +05:00
Exception e = new IOException ( string . Format ( CultureInfo . InvariantCulture , FileSystemProviderStrings . CopyItemRemotelyFailedToReadFile , path ));
2016-03-30 14:20:52 -07:00
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 )
{
2018-07-25 22:02:09 +05:00
if ( File . Exists ( destinationPath ))
2016-03-30 14:20:52 -07:00
{
2018-12-31 20:44:16 +05:00
Exception e = new IOException ( string . Format (
2016-03-30 14:20:52 -07:00
CultureInfo . InvariantCulture ,
FileSystemProviderStrings . CopyItemRemotelyDestinationIsFile ,
path ,
destinationPath ));
WriteError ( new ErrorRecord ( e , "CopyError" , ErrorCategory . WriteError , destinationPath ));
return ;
}
CopyDirectoryFromRemoteSession (
2016-07-29 13:02:49 -07:00
itemName ,
2016-03-30 14:20:52 -07:00
itemFullName ,
2016-07-29 13:02:49 -07:00
destinationPath ,
force ,
recurse ,
2016-03-30 14:20:52 -07:00
ps );
}
else
{
2016-07-29 13:02:49 -07:00
bool excludeFile = SessionStateUtilities . MatchesAnyWildcardPattern ( itemName , _excludeMatcher , false );
2016-03-30 14:20:52 -07:00
if (! excludeFile )
{
long itemSize = ( long ) ItemInfo [ "FileSize" ];
CopyFileFromRemoteSession ( itemName , itemFullName , destinationPath , force , ps , itemSize );
}
}
}
}
}
finally
{
RemoveFunctionsPSCopyFileFromRemoteSession ( ps );
}
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
private void CopyItemLocalOrToSession ( string path , string destinationPath , bool recurse , bool Force , System . Management . Automation . PowerShell ps )
{
bool isContainer = IsItemContainer ( path );
2016-09-01 16:03:02 +00:00
InitializeFunctionsPSCopyFileToRemoteSession ( ps );
2016-03-30 14:20:52 -07:00
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 );
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
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 );
}
}
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "destination = {0}" , destination );
2016-03-30 14:20:52 -07:00
// 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 ))
{
2018-12-31 20:44:16 +05:00
Exception e = new IOException ( string . Format ( CultureInfo . InvariantCulture ,
2016-03-30 14:20:52 -07:00
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 ;
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( Filter ))
2016-03-30 14:20:52 -07:00
{
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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "CopyDirectoryInfoItemIOError" , ErrorCategory . WriteError , file ));
}
catch ( UnauthorizedAccessException accessException )
{
WriteError ( new ErrorRecord ( accessException , "CopyDirectoryInfoItemUnauthorizedAccessError" , ErrorCategory . PermissionDenied , file ));
}
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
// 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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "CopyDirectoryInfoItemIOError" , ErrorCategory . WriteError , childDir ));
}
catch ( UnauthorizedAccessException accessException )
{
WriteError ( new ErrorRecord ( accessException , "CopyDirectoryInfoItemUnauthorizedAccessError" , ErrorCategory . PermissionDenied , childDir ));
}
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
}
2018-12-08 18:53:30 +02:00
}
}
2016-03-30 14:20:52 -07:00
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 );
}
2019-01-06 06:51:20 +05:00
// if the source and destination path are same then flag it as error.
2017-04-21 16:09:53 -07:00
if ( InternalSymbolicLinkLinkCodeMethods . IsSameFileSystemItem ( destinationPath , file . FullName ))
2016-03-30 14:20:52 -07:00
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . CopyError , destinationPath );
2016-03-30 14:20:52 -07:00
Exception e = new IOException ( error );
2017-04-21 16:09:53 -07:00
e . Data [ SelfCopyDataKey ] = file . FullName ;
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( e , "CopyError" , ErrorCategory . WriteError , destinationPath ));
return ;
}
// Verify that the target doesn't represent a device name
2016-04-28 13:41:25 -07:00
if ( PathIsReservedDeviceName ( destinationPath , "CopyError" ))
2016-03-30 14:20:52 -07:00
{
return ;
}
}
// Confirm the copy with the user
string action = FileSystemProviderStrings . CopyItemActionFile ;
string resource = StringUtil . Format ( FileSystemProviderStrings . CopyItemResourceFileTemplate , file . FullName , destinationPath );
2016-07-29 13:02:49 -07:00
bool excludeFile = SessionStateUtilities . MatchesAnyWildcardPattern ( file . Name , _excludeMatcher , defaultValue : false );
2016-03-30 14:20:52 -07:00
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
2019-02-28 10:00:23 -05:00
{
2016-03-30 14:20:52 -07:00
throw ;
2019-02-28 10:00:23 -05:00
}
2016-03-30 14:20:52 -07:00
}
file . CopyTo ( destinationPath , true );
FileInfo result = new FileInfo ( destinationPath );
WriteItemObject ( result , destinationPath , false );
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
else
{
WriteError ( new ErrorRecord ( unAuthorizedAccessException , "CopyFileInfoItemUnauthorizedAccessError" , ErrorCategory . PermissionDenied , file ));
}
}
2018-12-08 18:53:30 +02:00
}
2018-12-20 22:54:10 -08:00
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
private void CopyDirectoryFromRemoteSession (
2016-07-29 13:02:49 -07:00
string sourceDirectoryName ,
2016-03-30 14:20:52 -07:00
string sourceDirectoryFullName ,
2016-07-29 13:02:49 -07:00
string destination ,
bool force ,
bool recurse ,
2016-03-30 14:20:52 -07:00
System . Management . Automation . PowerShell ps )
{
Dbg . Diagnostics . Assert (( sourceDirectoryName != null && sourceDirectoryFullName != null ), "The caller should verify directory." );
if ( IsItemContainer ( destination ))
{
destination = MakePath ( destination , sourceDirectoryName );
}
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "destination = {0}" , destination );
2016-03-30 14:20:52 -07:00
// 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
2018-07-25 22:02:09 +05:00
if (! Directory . Exists ( destination ))
2016-03-30 14:20:52 -07:00
{
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 )
{
2018-12-31 20:44:16 +05:00
Exception e = new IOException ( string . Format (
2016-03-30 14:20:52 -07:00
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 ;
}
2016-07-29 13:02:49 -07:00
bool excludeFile = SessionStateUtilities . MatchesAnyWildcardPattern ( fileName , _excludeMatcher , defaultValue : false );
2016-03-30 14:20:52 -07:00
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 );
}
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
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" ];
2017-01-16 13:31:14 -08:00
// Making sure to obey the StopProcessing.
2016-03-30 14:20:52 -07:00
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 );
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
}
2018-12-08 18:53:30 +02:00
}
}
2016-03-30 14:20:52 -07:00
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 ;
}
2016-09-01 16:03:02 +00:00
private void InitializeFunctionPSCopyFileFromRemoteSession ( System . Management . Automation . PowerShell ps )
2016-03-30 14:20:52 -07:00
{
2019-02-28 10:00:23 -05:00
if (( ps == null ) || ! ValidRemoteSessionForScripting ( ps . Runspace ))
{
return ;
}
2016-03-30 14:20:52 -07:00
ps . AddScript ( CopyFileRemoteUtils . AllCopyFromRemoteScripts );
SafeInvokeCommand . Invoke ( ps , this , null , false );
}
private void RemoveFunctionsPSCopyFileFromRemoteSession ( System . Management . Automation . PowerShell ps )
{
2019-02-28 10:00:23 -05:00
if (( ps == null ) || ! ValidRemoteSessionForScripting ( ps . Runspace ))
{
return ;
}
2016-03-30 14:20:52 -07:00
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 )
{
2019-02-28 10:00:23 -05:00
if (!( runspace is RemoteRunspace ))
{
return false ;
}
2016-03-30 14:20:52 -07:00
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" ];
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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 )
{
2018-12-31 20:44:16 +05:00
if ( string . Equals ( value , "ReadOnly" , StringComparison . OrdinalIgnoreCase ))
2016-03-30 14:20:52 -07:00
{
destinationFile . Attributes = destinationFile . Attributes | FileAttributes . ReadOnly ;
}
2018-12-31 20:44:16 +05:00
else if ( string . Equals ( value , "Hidden" , StringComparison . OrdinalIgnoreCase ))
2016-03-30 14:20:52 -07:00
{
destinationFile . Attributes = destinationFile . Attributes | FileAttributes . Hidden ;
}
2018-12-31 20:44:16 +05:00
else if ( string . Equals ( value , "Archive" , StringComparison . OrdinalIgnoreCase ))
2016-03-30 14:20:52 -07:00
{
destinationFile . Attributes = destinationFile . Attributes | FileAttributes . Archive ;
}
2018-12-31 20:44:16 +05:00
else if ( string . Equals ( value , "System" , StringComparison . OrdinalIgnoreCase ))
2016-03-30 14:20:52 -07:00
{
destinationFile . Attributes = destinationFile . Attributes | FileAttributes . System ;
}
}
}
}
}
private void CopyFileFromRemoteSession (
string sourceFileName ,
string sourceFileFullName ,
2016-07-29 13:02:49 -07:00
string destinationPath ,
bool force ,
System . Management . Automation . PowerShell ps ,
2016-03-30 14:20:52 -07:00
long fileSize = 0 )
{
Dbg . Diagnostics . Assert ( sourceFileFullName != null , "The caller should verify file." );
2016-04-28 13:41:25 -07:00
// 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" ))
2016-03-30 14:20:52 -07:00
{
return ;
}
2016-04-28 13:41:25 -07:00
FileInfo destinationFile = new FileInfo ( destinationPath );
2016-03-30 14:20:52 -07:00
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 );
2016-08-28 05:51:23 +00:00
// Copying the file from the remote session completed successfully
2016-03-30 14:20:52 -07:00
if ( result )
{
// Check if the remote source file has any alternate data streams
ArrayList remoteFileStreams = GetRemoteSourceAlternateStreams ( ps , sourceFileFullName );
2016-11-17 11:20:04 -08:00
if (( remoteFileStreams != null ) && ( remoteFileStreams . Count > 0 ))
2016-03-30 14:20:52 -07:00
{
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 );
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
}
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 ;
2018-12-31 20:44:16 +05:00
string activity = string . Format ( CultureInfo . InvariantCulture ,
2016-03-30 14:20:52 -07:00
FileSystemProviderStrings . CopyItemRemotelyProgressActivity ,
sourceFileFullName ,
destinationFile . FullName );
2018-12-31 20:44:16 +05:00
string statusDescription = string . Format ( CultureInfo . InvariantCulture ,
2016-03-30 14:20:52 -07:00
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 );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
wStream = new FileStream ( destinationFile . FullName , FileMode . Create );
}
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
// an alternate stream
else
{
wStream = AlternateDataStreamUtilities . CreateFileStream ( destinationFile . FullName , streamName , FileMode . Append , FileAccess . Write , FileShare . ReadWrite );
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
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 );
}
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
if ( isAlternateDataStream )
{
ps . AddParameter ( "isAlternateStream" , true );
ps . AddParameter ( "streamName" , streamName );
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
Hashtable op = SafeInvokeCommand . Invoke ( ps , this , null );
// Check if there was an exception when reading the remote file.
if ( op == null )
{
errorWhileCopyRemoteFile = true ;
2018-12-31 20:44:16 +05:00
Exception e = new IOException ( string . Format ( CultureInfo . InvariantCulture ,
2016-03-30 14:20:52 -07:00
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
2018-12-22 01:26:06 -08:00
string content = string . Empty ;
2016-03-30 14:20:52 -07:00
if ( op [ "b64Fragment" ] != null )
{
2018-12-22 01:26:06 -08:00
content = ( string ) op [ "b64Fragment" ];
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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
2016-07-29 13:02:49 -07:00
{
2016-03-30 14:20:52 -07:00
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 );
}
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
return success ;
}
2016-09-01 16:03:02 +00:00
private void InitializeFunctionsPSCopyFileToRemoteSession ( System . Management . Automation . PowerShell ps )
2016-03-30 14:20:52 -07:00
{
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 )
{
2016-07-29 13:02:49 -07:00
supportsAlternateStreams = ( bool ) op [ "TargetSupportsAlternateStreams" ];
2016-03-30 14:20:52 -07:00
}
return supportsAlternateStreams ;
}
2016-04-28 13:41:25 -07:00
// 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 )
{
2016-07-29 13:02:49 -07:00
isDirectoryInfo = ( bool ) op [ "IsDirectoryInfo" ];
2016-04-28 13:41:25 -07:00
}
2018-12-23 22:20:06 -08:00
2016-04-28 13:41:25 -07:00
if ( op [ "IsFileInfo" ] != null )
{
2016-07-29 13:02:49 -07:00
isFileInfo = ( bool ) op [ "IsFileInfo" ];
2016-04-28 13:41:25 -07:00
}
2018-12-23 22:20:06 -08:00
2016-04-28 13:41:25 -07:00
if ( op [ "ParentIsDirectoryInfo" ] != null )
{
parentIsDirectoryInfo = ( bool ) op [ "ParentIsDirectoryInfo" ];
}
}
if ( isFileInfo )
{
// The destination is a file, so we are going to overwrite it.
2016-07-29 13:02:49 -07:00
path = remotePath ;
2016-04-28 13:41:25 -07:00
}
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.
2017-01-16 13:31:14 -08:00
// 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.,
2016-04-28 13:41:25 -07:00
// 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 ;
}
2016-03-30 14:20:52 -07:00
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 )
{
2018-12-31 20:44:16 +05:00
string activity = string . Format ( CultureInfo . InvariantCulture ,
2016-03-30 14:20:52 -07:00
FileSystemProviderStrings . CopyItemRemotelyProgressActivity ,
file . FullName ,
destinationPath );
2018-12-31 20:44:16 +05:00
string statusDescription = string . Format ( CultureInfo . InvariantCulture ,
2016-03-30 14:20:52 -07:00
FileSystemProviderStrings . CopyItemRemotelyStatusDescription ,
"localhost" ,
ps . Runspace . ConnectionInfo . ComputerName );
ProgressRecord progress = new ProgressRecord ( 0 , activity , statusDescription );
progress . PercentComplete = 0 ;
progress . RecordType = ProgressRecordType . Processing ;
WriteProgress ( progress );
2017-01-16 13:31:14 -08:00
// 4MB gives the best results without spiking the resources on the remote connection.
2016-03-30 14:20:52 -07:00
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 );
}
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
else
{
fStream = AlternateDataStreamUtilities . CreateFileStream ( file . FullName , streamName , FileMode . Open , FileAccess . Read , FileShare . ReadWrite );
}
2017-08-28 15:39:42 -07:00
#endif
2019-02-13 16:51:05 +05:00
long remainingFileSize = fStream != null ? fStream . Length : 0 ;
2016-03-30 14:20:52 -07:00
do
{
if ( Stopping )
{
return false ;
}
iteration ++;
int toRead = fragmentSize ;
if ( toRead > remainingFileSize )
{
toRead = ( int ) remainingFileSize ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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 );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
remainingFileSize -= readSoFar ;
string b64Fragment = System . Convert . ToBase64String ( fragment );
// Main data stream
if (! isAlternateStream )
{
ps . AddCommand ( CopyFileRemoteUtils . PSCopyToSessionHelperName );
2016-04-28 13:41:25 -07:00
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 );
}
2016-03-30 14:20:52 -07:00
}
else
{
ps . AddCommand ( CopyFileRemoteUtils . PSCopyToSessionHelperName );
2016-04-28 13:41:25 -07:00
ps . AddParameter ( "copyToFilePath" , destinationPath );
2016-03-30 14:20:52 -07:00
ps . AddParameter ( "b64Fragment" , b64Fragment );
ps . AddParameter ( "streamName" , streamName );
}
Hashtable op = SafeInvokeCommand . Invoke ( ps , this , null );
if ( op == null || op [ "BytesWritten" ] == null )
{
2019-01-06 06:51:20 +05:00
// write error to stream
2018-12-31 20:44:16 +05:00
Exception e = new IOException ( string . Format ( CultureInfo . InvariantCulture , FileSystemProviderStrings . CopyItemRemotelyFailed , file ));
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( e , "CopyError" , ErrorCategory . WriteError , file . FullName ));
return false ;
}
if (( int )( op [ "BytesWritten" ]) != toRead )
{
2018-12-31 20:44:16 +05:00
Exception e = new IOException ( string . Format ( CultureInfo . InvariantCulture , FileSystemProviderStrings . CopyItemRemotelyFailed , file ));
2016-03-30 14:20:52 -07:00
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 ();
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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 ;
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
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 )
{
2016-04-28 13:41:25 -07:00
// Make the remote path
var remoteFilePath = MakeRemotePath ( ps , destinationPath , file . Name );
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( remoteFilePath ))
2016-03-30 14:20:52 -07:00
{
2018-12-31 20:44:16 +05:00
Exception e = new ArgumentException ( string . Format ( CultureInfo . InvariantCulture , SessionStateStrings . PathNotFound , destinationPath ));
2016-04-28 13:41:25 -07:00
WriteError ( new ErrorRecord ( e , "RemotePathNotFound" , ErrorCategory . WriteError , destinationPath ));
2016-03-30 14:20:52 -07:00
return false ;
}
2016-04-28 13:41:25 -07:00
bool result = CopyFileStreamToRemoteSession ( file , remoteFilePath , ps , false , null );
2016-03-30 14:20:52 -07:00
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-04-28 13:41:25 -07:00
bool targetSupportsAlternateStreams = RemoteTargetSupportsAlternateStreams ( ps , remoteFilePath );
2016-03-30 14:20:52 -07:00
2016-08-28 05:51:23 +00:00
// Once the file is copied successfully, check if the file has any alternate data streams
2016-03-30 14:20:52 -07:00
if ( result && targetSupportsAlternateStreams )
{
foreach ( AlternateStreamData stream in AlternateDataStreamUtilities . GetStreams ( file . FullName ))
{
2018-12-31 20:44:16 +05:00
if (!( string . Equals ( ":$DATA" , stream . Stream , StringComparison . OrdinalIgnoreCase )))
2016-03-30 14:20:52 -07:00
{
2016-04-28 13:41:25 -07:00
result = CopyFileStreamToRemoteSession ( file , remoteFilePath , ps , true , stream . Stream );
2016-03-30 14:20:52 -07:00
if (! result )
{
break ;
}
}
}
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
if ( result )
{
SetRemoteFileMetadata ( file , Path . Combine ( destinationPath , file . Name ), ps );
}
return result ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
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 )
{
2018-12-31 20:44:16 +05:00
Exception e = new IOException ( string . Format (
2016-03-30 14:20:52 -07:00
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 ))
{
2018-12-31 20:44:16 +05:00
Exception e = new IOException ( string . Format ( CultureInfo . InvariantCulture ,
2016-03-30 14:20:52 -07:00
FileSystemProviderStrings . CopyItemRemotelyFailedToCreateDirectory ,
destination ));
WriteError ( new ErrorRecord ( e , "FailedToCreateDirectory" , ErrorCategory . WriteError , destination ));
return null ;
}
2018-12-23 22:20:06 -08:00
2018-12-22 01:26:06 -08:00
string path = ( string )( op [ "DirectoryPath" ]);
2016-03-30 14:20:52 -07:00
if ((! force ) && ( bool ) op [ "PathExists" ])
{
2016-07-29 13:02:49 -07:00
Exception e = new IOException ( StringUtil . Format ( FileSystemProviderStrings . DirectoryExist , path ));
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( e , "DirectoryExist" , ErrorCategory . ResourceExists , path ));
return null ;
}
return path ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
2016-04-28 13:41:25 -07:00
// 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 ;
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . TargetCannotContainDeviceName , destinationPath );
2016-04-28 13:41:25 -07:00
Exception e = new IOException ( error );
WriteError ( new ErrorRecord ( e , errorId , ErrorCategory . WriteError , destinationPath ));
}
return pathIsReservedDeviceName ;
}
2016-03-30 14:20:52 -07:00
#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>
2016-07-29 13:02:49 -07:00
protected override string GetParentPath ( string path , string root )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
string parentPath = base . GetParentPath ( path , root );
2019-01-23 09:37:45 -08:00
if (! Utils . PathIsUnc ( path ))
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
parentPath = EnsureDriveIsRooted ( parentPath );
2016-03-30 14:20:52 -07:00
}
2017-10-09 14:05:16 -07:00
#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
2016-03-30 14:20:52 -07:00
return parentPath ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
2016-06-08 10:57:35 -07:00
// Note: we don't use IO.Path.IsPathRooted as this deals with "invalid" i.e. unnormalized paths
2016-03-30 14:20:52 -07:00
private static bool IsAbsolutePath ( string path )
{
bool result = false ;
2016-06-08 10:57:35 -07:00
// check if we're on a single root filesystem and it's an absolute path
if ( LocationGlobber . IsSingleFileSystemAbsolutePath ( path ))
2016-04-06 12:01:07 -07:00
{
return true ;
}
2016-03-30 14:20:52 -07:00
// Find the drive separator
int index = path . IndexOf ( ':' );
if ( index != - 1 )
{
result = true ;
}
return result ;
}
/// <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>
2016-07-29 13:02:49 -07:00
private static bool IsUNCRoot ( string path )
2016-03-30 14:20:52 -07:00
{
bool result = false ;
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
2019-01-23 09:37:45 -08:00
if ( Utils . PathIsUnc ( path ))
2016-03-30 14:20:52 -07:00
{
int lastIndex = path . Length - 1 ;
if ( path [ path . Length - 1 ] == '\\' )
{
lastIndex --;
}
int separatorsFound = 0 ;
do
{
lastIndex = path . LastIndexOf ( '\\' , lastIndex );
if ( lastIndex == - 1 )
{
break ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
-- lastIndex ;
if ( lastIndex < 3 )
{
break ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
++ separatorsFound ;
} while ( lastIndex > 3 );
if ( separatorsFound == 1 )
{
result = true ;
}
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
return result ;
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Determines if the specified path is either a drive root or a UNC root.
2016-03-30 14:20:52 -07:00
/// </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>
2016-07-29 13:02:49 -07:00
private static bool IsPathRoot ( string path )
2016-03-30 14:20:52 -07:00
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
return false ;
}
2018-12-31 20:44:16 +05:00
bool isDriveRoot = string . Equals ( path , Path . GetPathRoot ( path ), StringComparison . OrdinalIgnoreCase );
2016-07-29 13:02:49 -07:00
bool isUNCRoot = IsUNCRoot ( path );
2016-03-30 14:20:52 -07:00
bool result = isDriveRoot || isUNCRoot ;
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "result = {0}; isDriveRoot = {1}; isUNCRoot = {2}" , result , isDriveRoot , isUNCRoot );
2016-03-30 14:20:52 -07:00
return result ;
}
/// <summary>
2017-01-16 13:31:14 -08:00
/// Normalizes the path that was passed in and returns it as a normalized
2016-03-30 14:20:52 -07:00
/// 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 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ) || ! IsValidPath ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
if ( basePath == null )
{
2018-12-31 20:44:16 +05:00
basePath = string . Empty ;
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "basePath = {0}" , basePath );
2016-03-30 14:20:52 -07:00
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 ;
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( result ))
2016-03-30 14:20:52 -07:00
{
break ;
}
try
{
string originalPathComparison = path ;
2018-10-31 08:26:15 +05:00
if (! originalPathComparison . EndsWith ( StringLiterals . DefaultPathSeparator ))
2016-03-30 14:20:52 -07:00
{
originalPathComparison += StringLiterals . DefaultPathSeparator ;
}
string basePathComparison = basePath ;
2018-10-31 08:26:15 +05:00
if (! basePathComparison . EndsWith ( StringLiterals . DefaultPathSeparator ))
2016-03-30 14:20:52 -07:00
{
basePathComparison += StringLiterals . DefaultPathSeparator ;
}
if ( originalPathComparison . StartsWith ( basePathComparison , StringComparison . OrdinalIgnoreCase ))
{
2019-01-23 09:37:45 -08:00
if (! Utils . PathIsUnc ( result ))
2016-03-30 14:20:52 -07:00
{
// 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
2018-12-31 20:44:16 +05:00
string directoryPath = GetParentPath ( result , string . Empty );
2016-03-30 14:20:52 -07:00
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( directoryPath ))
2016-03-30 14:20:52 -07:00
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
2017-05-02 18:42:16 -07:00
#if UNIX
2018-07-25 22:02:09 +05:00
// We don't use the Directory.EnumerateFiles() for Unix because the path
2017-05-02 18:42:16 -07:00
// may contain additional globbing patterns such as '[ab]'
// which Directory.EnumerateFiles() processes, giving undesireable
// results in this context.
2018-07-25 22:02:09 +05:00
if (! File . Exists ( result ) && ! Directory . Exists ( result ))
2017-05-02 18:42:16 -07:00
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . ItemDoesNotExist , path );
2017-05-02 18:42:16 -07:00
Exception e = new IOException ( error );
WriteError ( new ErrorRecord (
e ,
"ItemDoesNotExist" ,
ErrorCategory . ObjectNotFound ,
path ));
break ;
}
#else
2016-03-30 14:20:52 -07:00
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 ())
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . ItemDoesNotExist , path );
2016-03-30 14:20:52 -07:00
Exception e = new IOException ( error );
WriteError ( new ErrorRecord (
e ,
"ItemDoesNotExist" ,
ErrorCategory . ObjectNotFound ,
path ));
break ;
}
result = files . First ();
2017-05-02 18:42:16 -07:00
#endif
2016-03-30 14:20:52 -07:00
if ( result . StartsWith ( basePath , StringComparison . CurrentCulture ))
{
result = result . Substring ( basePath . Length );
}
else
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . PathOutSideBasePath , path );
2016-03-30 14:20:52 -07:00
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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
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 ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <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.
2017-01-16 13:31:14 -08:00
///
2016-08-28 05:51:23 +00:00
/// Note, the base class implementation uses GetParentPath, GetChildName, and MakePath
2016-03-30 14:20:52 -07:00
/// to normalize the path and then make it relative to basePath. All string comparisons
2016-08-28 05:51:23 +00:00
/// are done using StringComparison.InvariantCultureIgnoreCase.
2016-03-30 14:20:52 -07:00
/// </remarks>
2016-07-29 13:02:49 -07:00
private string NormalizeRelativePathHelper ( string path , string basePath )
2016-03-30 14:20:52 -07:00
{
if ( path == null )
{
2016-07-29 13:02:49 -07:00
throw PSTraceSource . NewArgumentNullException ( "path" );
2016-03-30 14:20:52 -07:00
}
if ( path . Length == 0 )
{
2018-12-31 20:44:16 +05:00
return string . Empty ;
2016-03-30 14:20:52 -07:00
}
if ( basePath == null )
{
2018-12-31 20:44:16 +05:00
basePath = string . Empty ;
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "basePath = {0}" , basePath );
2016-03-30 14:20:52 -07:00
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
// Remove alternate data stream references
// See if they've used the inline stream syntax. They have more than one colon.
2018-12-31 20:44:16 +05:00
string alternateDataStream = string . Empty ;
2016-03-30 14:20:52 -07:00
int firstColon = path . IndexOf ( ':' );
int secondColon = path . IndexOf ( ':' , firstColon + 1 );
if ( secondColon > 0 )
{
string newPath = path . Substring ( 0 , secondColon );
2018-06-13 00:30:10 +05:00
alternateDataStream = path . Replace ( newPath , string . Empty );
2016-03-30 14:20:52 -07:00
path = newPath ;
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
string result = path ;
do // false loop
{
// Convert to the correct path separators and trim trailing separators
2016-07-29 13:02:49 -07:00
path = path . Replace ( StringLiterals . AlternatePathSeparator , StringLiterals . DefaultPathSeparator );
2016-03-30 14:20:52 -07:00
string originalPath = path ;
2016-07-29 13:02:49 -07:00
path = path . TrimEnd ( StringLiterals . DefaultPathSeparator );
basePath = basePath . Replace ( StringLiterals . AlternatePathSeparator , StringLiterals . DefaultPathSeparator );
basePath = basePath . TrimEnd ( StringLiterals . DefaultPathSeparator );
2016-03-30 14:20:52 -07:00
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.
2018-12-31 20:44:16 +05:00
if ( string . Equals ( path , basePath , StringComparison . OrdinalIgnoreCase ) &&
2018-10-31 08:26:15 +05:00
(! originalPath . EndsWith ( StringLiterals . DefaultPathSeparator )))
2016-03-30 14:20:52 -07:00
{
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 ((
2016-07-29 13:02:49 -07:00
!( path + StringLiterals . DefaultPathSeparator ). StartsWith (
2016-03-30 14:20:52 -07:00
basePath + StringLiterals . DefaultPathSeparator , StringComparison . OrdinalIgnoreCase )) &&
2018-12-31 15:10:15 +05:00
(! string . IsNullOrEmpty ( basePath ))
2016-03-30 14:20:52 -07:00
)
{
2018-12-31 20:44:16 +05:00
result = string . Empty ;
2016-03-30 14:20:52 -07:00
string commonBase = GetCommonBase ( path , basePath );
Stack < string > parentNavigationStack = TokenizePathToStack ( basePath , commonBase );
int parentPopCount = parentNavigationStack . Count ;
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( commonBase ))
2016-03-30 14:20:52 -07:00
{
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
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( commonBase ))
2016-03-30 14:20:52 -07:00
{
2018-12-31 20:44:16 +05:00
if ( string . Equals ( path , commonBase , StringComparison . OrdinalIgnoreCase ) &&
2018-10-31 08:26:15 +05:00
(! path . EndsWith ( StringLiterals . DefaultPathSeparator )))
2016-03-30 14:20:52 -07:00
{
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 ))
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( basePath ))
2016-03-30 14:20:52 -07:00
{
result = path ;
break ;
}
else
{
2018-12-31 20:44:16 +05:00
result = string . Empty ;
2016-03-30 14:20:52 -07:00
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 );
2017-08-28 15:39:42 -07:00
#if ! UNIX
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( alternateDataStream ))
2016-03-30 14:20:52 -07:00
{
result = result + alternateDataStream ;
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
return result ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
private string RemoveRelativeTokens ( string path )
{
2016-06-08 11:20:58 -07:00
string testPath = path . Replace ( '/' , '\\' );
2016-03-30 14:20:52 -07:00
if (
2019-06-26 11:02:07 +05:00
! testPath . Contains ( '\\' ) ||
testPath . StartsWith ( ".\\" , StringComparison . Ordinal ) ||
testPath . StartsWith ( "..\\" , StringComparison . Ordinal ) ||
testPath . EndsWith ( "\\." , StringComparison . Ordinal ) ||
testPath . EndsWith ( "\\.." , StringComparison . Ordinal ) ||
testPath . Contains ( "\\.\\" , StringComparison . Ordinal ) ||
testPath . Contains ( "\\..\\" , StringComparison . Ordinal ))
2016-03-30 14:20:52 -07:00
{
try
{
2018-06-13 00:30:10 +05:00
Stack < string > tokenizedPathStack = TokenizePathToStack ( path , string . Empty );
Stack < string > normalizedPath = NormalizeThePath ( string . Empty , tokenizedPathStack );
2016-03-30 14:20:52 -07:00
return CreateNormalizedRelativePathFromStack ( normalizedPath );
}
catch ( UnauthorizedAccessException )
{
// Catch any errors here, as we may be in an AppContainer
}
}
return path ;
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Get the common base path of two paths.
2016-03-30 14:20:52 -07:00
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="path1">One path.</param>
/// <param name="path2">Another path.</param>
2016-03-30 14:20:52 -07:00
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.
2018-12-31 20:44:16 +05:00
while (! string . Equals ( path1 , path2 , StringComparison . OrdinalIgnoreCase ))
2016-03-30 14:20:52 -07:00
{
if ( path2 . Length > path1 . Length )
{
path2 = GetParentPath ( path2 , null );
}
else
{
path1 = GetParentPath ( path1 , null );
}
}
return path1 ;
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Tokenizes the specified path onto a stack.
2016-03-30 14:20:52 -07:00
/// </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
2016-08-28 05:51:23 +00:00
/// of the stack and the most ancestral parent at the top.
2016-03-30 14:20:52 -07:00
/// </returns>
2016-07-29 13:02:49 -07:00
private Stack < string > TokenizePathToStack ( string path , string basePath )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
Stack < string > tokenizedPathStack = new Stack < string >();
2016-03-30 14:20:52 -07:00
string tempPath = path ;
string previousParent = path ;
while ( tempPath . Length > basePath . Length )
{
// Get the child name and push it onto the stack
// if its valid
2016-07-29 13:02:49 -07:00
string childName = GetChildName ( tempPath );
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( childName ))
2016-03-30 14:20:52 -07:00
{
// Push the parent on and then stop
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "tokenizedPathStack.Push({0})" , tempPath );
tokenizedPathStack . Push ( tempPath );
2016-03-30 14:20:52 -07:00
break ;
}
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "tokenizedPathStack.Push({0})" , childName );
tokenizedPathStack . Push ( childName );
2016-03-30 14:20:52 -07:00
// 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:\"
2016-07-29 13:02:49 -07:00
tempPath = GetParentPath ( tempPath , basePath );
2016-03-30 14:20:52 -07:00
if ( tempPath . Length >= previousParent . Length ||
IsPathRoot ( tempPath ))
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( basePath ))
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "tokenizedPathStack.Push({0})" , tempPath );
tokenizedPathStack . Push ( tempPath );
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
break ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
previousParent = tempPath ;
}
return tokenizedPathStack ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2016-08-28 05:51:23 +00:00
/// Given the tokenized path, the relative path elements are removed.
2016-03-30 14:20:52 -07:00
/// </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
2016-08-28 05:51:23 +00:00
/// the bottom of the stack and the most ancestral parent is on the top.
2016-03-30 14:20:52 -07:00
/// Generally this stack comes from TokenizePathToStack().
/// </param>
/// <returns>
/// A stack in reverse order with the path elements normalized and all relative
2016-08-28 05:51:23 +00:00
/// path tokens removed.
2016-03-30 14:20:52 -07:00
/// </returns>
2016-07-29 13:02:49 -07:00
private Stack < string > NormalizeThePath ( string basepath , Stack < string > tokenizedPathStack )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
Stack < string > normalizedPathStack = new Stack < string >();
2018-12-22 01:26:06 -08:00
string currentPath = basepath ;
2016-03-30 14:20:52 -07:00
while ( tokenizedPathStack . Count > 0 )
{
2016-07-29 13:02:49 -07:00
string childName = tokenizedPathStack . Pop ();
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "childName = {0}" , childName );
2016-03-30 14:20:52 -07:00
// Ignore the current directory token
2016-07-29 13:02:49 -07:00
if ( childName . Equals ( "." , StringComparison . OrdinalIgnoreCase ))
2016-03-30 14:20:52 -07:00
{
// 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 ();
2019-01-06 06:51:20 +05:00
// update our currentpath to reflect the change.
2016-03-30 14:20:52 -07:00
if ( currentPath . Length > poppedName . Length )
{
currentPath = currentPath . Substring ( 0 , currentPath . Length - poppedName . Length - 1 );
}
else
{
2018-06-13 00:30:10 +05:00
currentPath = string . Empty ;
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "normalizedPathStack.Pop() : {0}" , poppedName );
2016-03-30 14:20:52 -07:00
continue ;
}
else
{
throw PSTraceSource . NewArgumentException ( "path" , FileSystemProviderStrings . PathOutSideBasePath );
}
}
else
{
currentPath = MakePath ( currentPath , childName );
2018-07-25 22:02:09 +05:00
var fsinfo = GetFileSystemInfo ( currentPath , out bool _ );
2016-03-30 14:20:52 -07:00
// 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,
2017-01-16 13:31:14 -08:00
// which the .NET APIs treat as the parent directory
2016-03-30 14:20:52 -07:00
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
2018-07-25 22:02:09 +05:00
if ( tokenizedPathStack . Count == 0 )
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" , FileSystemProviderStrings . ItemDoesNotExist , currentPath );
}
}
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "normalizedPathStack.Push({0})" , childName );
normalizedPathStack . Push ( childName );
2016-03-30 14:20:52 -07:00
}
return normalizedPathStack ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Pops each leaf element of the stack and uses MakePath to generate the relative path.
2016-03-30 14:20:52 -07:00
/// </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>
2016-07-29 13:02:49 -07:00
private string CreateNormalizedRelativePathFromStack ( Stack < string > normalizedPathStack )
2016-03-30 14:20:52 -07:00
{
2018-12-31 20:44:16 +05:00
string leafElement = string . Empty ;
2016-03-30 14:20:52 -07:00
while ( normalizedPathStack . Count > 0 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( leafElement ))
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
leafElement = normalizedPathStack . Pop ();
2016-03-30 14:20:52 -07:00
}
else
{
2016-07-29 13:02:49 -07:00
string parentElement = normalizedPathStack . Pop ();
leafElement = MakePath ( parentElement , leafElement );
2016-03-30 14:20:52 -07:00
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
return leafElement ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <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
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
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
{
2016-12-04 15:00:32 -08:00
result = path . Substring ( separatorIndex + 1 );
2016-03-30 14:20:52 -07:00
}
return result ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
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 ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <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 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
path = NormalizePath ( path );
2018-07-25 22:02:09 +05:00
return Directory . Exists ( path );
2016-03-30 14:20:52 -07:00
}
#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
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( destination ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "destination" );
}
path = NormalizePath ( path );
destination = NormalizePath ( destination );
// Verify that the target doesn't represent a device name
2016-04-28 13:41:25 -07:00
if ( PathIsReservedDeviceName ( destination , "MoveError" ))
2016-03-30 14:20:52 -07:00
{
return ;
}
try
{
bool isContainer = IsItemContainer ( path );
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "Moving {0} to {1}" , path , destination );
2016-03-30 14:20:52 -07:00
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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "MoveItemIOError" , ErrorCategory . WriteError , path ));
}
catch ( UnauthorizedAccessException accessException )
{
WriteError ( new ErrorRecord ( accessException , "MoveItemUnauthorizedAccessError" , ErrorCategory . PermissionDenied , path ));
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
private void MoveFileInfoItem (
FileInfo file ,
string destination ,
bool force ,
bool output )
{
Dbg . Diagnostics . Assert (
file != null ,
"The caller should verify file." );
Dbg . Diagnostics . Assert (
2018-12-31 15:10:15 +05:00
! string . IsNullOrEmpty ( destination ),
2016-03-30 14:20:52 -07:00
"THe caller should verify destination." );
try
{
2017-01-16 13:31:14 -08:00
// Move the file
2016-03-30 14:20:52 -07:00
file . MoveTo ( destination );
if ( output )
{
WriteItemObject (
file ,
file . FullName ,
false );
}
}
catch ( System . UnauthorizedAccessException unauthorizedAccess )
{
2017-01-16 13:31:14 -08:00
// This error is thrown when the readonly bit is set.
2016-03-30 14:20:52 -07:00
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 )
{
2019-01-06 06:51:20 +05:00
// check if destination file exists. if force is specified then we should delete the destination before moving
2016-03-30 14:20:52 -07:00
if ( force && File . Exists ( destination ))
{
FileInfo destfile = new FileInfo ( destination );
if ( destfile != null )
{
try
{
2019-01-06 06:51:20 +05:00
// Make sure the file is not read only
2016-03-30 14:20:52 -07:00
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 ))
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "MoveFileInfoItemIOError" , ErrorCategory . WriteError , destfile ));
}
else
throw ;
}
}
else
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "MoveFileInfoItemIOError" , ErrorCategory . WriteError , file ));
}
}
else
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "MoveFileInfoItemIOError" , ErrorCategory . WriteError , file ));
}
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
private void MoveDirectoryInfoItem (
DirectoryInfo directory ,
string destination ,
bool force )
{
Dbg . Diagnostics . Assert (
directory != null ,
"The caller should verify directory." );
Dbg . Diagnostics . Assert (
2018-12-31 15:10:15 +05:00
! string . IsNullOrEmpty ( destination ),
2016-03-30 14:20:52 -07:00
"The caller should verify destination." );
try
{
if (! IsSameVolume ( directory . FullName , destination ))
{
CopyAndDelete ( directory , destination , force );
}
else
{
2017-01-16 13:31:14 -08:00
// Move the file
2016-03-30 14:20:52 -07:00
directory . MoveTo ( destination );
}
WriteItemObject (
directory ,
directory . FullName ,
true );
}
catch ( System . UnauthorizedAccessException unauthorizedAccess )
{
2017-01-16 13:31:14 -08:00
// This error is thrown when the readonly bit is set.
2016-03-30 14:20:52 -07:00
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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "MoveDirectoryItemIOError" , ErrorCategory . WriteError , directory ));
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
private void CopyAndDelete ( DirectoryInfo directory , string destination , bool force )
{
if (! ItemExists ( destination ))
{
CreateDirectory ( destination , false );
}
else if ( ItemExists ( destination ) && ! IsItemContainer ( destination ))
{
2018-12-22 01:26:06 -08:00
string errorMessage = StringUtil . Format ( FileSystemProviderStrings . DirectoryExist , destination );
2016-03-30 14:20:52 -07:00
Exception e = new IOException ( errorMessage );
WriteError ( new ErrorRecord (
e ,
"DirectoryExist" ,
ErrorCategory . ResourceExists ,
destination ));
return ;
}
2016-07-29 13:02:49 -07:00
foreach ( FileInfo file in directory . EnumerateFiles ())
2016-03-30 14:20:52 -07:00
{
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,"
2017-01-16 13:31:14 -08:00
/// and other properties defined by
/// <see cref="System.IO.DirectoryInfo" /> and
2016-03-30 14:20:52 -07:00
/// <see cref="System.IO.FileInfo" />
/// </param>
public void GetProperty ( string path , Collection < string > providerSpecificPickList )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
path = NormalizePath ( path );
PSObject result = null ;
try
{
2018-07-25 22:02:09 +05:00
var fileSystemObject = GetFileSystemInfo ( path , out bool isDirectory );
2016-03-30 14:20:52 -07:00
if ( fileSystemObject == null )
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . ItemDoesNotExist , path );
2016-03-30 14:20:52 -07:00
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 ();
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
result . Properties . Add ( new PSNoteProperty ( property , value ));
}
else
{
2018-12-22 01:26:06 -08:00
string error =
2016-03-30 14:20:52 -07:00
StringUtil . Format (
FileSystemProviderStrings . PropertyNotFound ,
property );
Exception e = new IOException ( error );
WriteError ( new ErrorRecord ( e , "GetValueError" , ErrorCategory . ReadError , property ));
}
}
catch ( GetValueException exception )
{
2016-07-29 13:02:49 -07:00
WriteError ( new ErrorRecord ( exception , "GetValueError" , ErrorCategory . ReadError , property ));
2016-03-30 14:20:52 -07:00
}
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
}
}
}
catch ( ArgumentException argException )
{
WriteError ( new ErrorRecord ( argException , "GetPropertyArgumentError" , ErrorCategory . InvalidArgument , path ));
}
catch ( IOException ioException )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
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 );
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2016-08-28 05:51:23 +00:00
/// Gets the dynamic property parameters required by the get-itemproperty cmdlet.
2016-03-30 14:20:52 -07:00
/// 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">
2017-01-16 13:31:14 -08:00
/// A PSObject which contains a collection of the names and values
2016-03-30 14:20:52 -07:00
/// 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>
2016-07-29 13:02:49 -07:00
public void SetProperty ( string path , PSObject propertyToSet )
2016-03-30 14:20:52 -07:00
{
// verify parameters
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
if ( propertyToSet == null )
{
throw PSTraceSource . NewArgumentNullException ( "propertyToSet" );
}
path = NormalizePath ( path );
PSObject results = new PSObject ();
2018-07-25 22:02:09 +05:00
var fsinfo = GetFileSystemInfo ( path , out bool isDirectory );
2016-03-30 14:20:52 -07:00
2018-07-25 22:02:09 +05:00
// Create a PSObject with either a DirectoryInfo or FileInfo object at its core.
if ( fsinfo != null )
2016-03-30 14:20:52 -07:00
{
2018-07-25 22:02:09 +05:00
PSObject fileSystemInfoShell = PSObject . AsPSObject ( fsinfo );
2016-03-30 14:20:52 -07:00
bool propertySet = false ;
foreach ( PSMemberInfo property in propertyToSet . Properties )
{
object propertyValue = property . Value ;
// Get the confirmation text
string action = null ;
2018-07-25 22:02:09 +05:00
if ( isDirectory )
2016-03-30 14:20:52 -07:00
{
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 =
2018-12-31 20:44:16 +05:00
string . Format (
2016-03-30 14:20:52 -07:00
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 )
{
2018-12-22 01:26:06 -08:00
string error =
2016-03-30 14:20:52 -07:00
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
{
2018-12-22 01:26:06 -08:00
string error =
2016-03-30 14:20:52 -07:00
StringUtil . Format (
FileSystemProviderStrings . PropertyNotFound ,
property );
Exception e = new IOException ( error );
WriteError ( new ErrorRecord ( e , "SetPropertyError" , ErrorCategory . ReadError , property ));
}
2018-12-08 18:53:30 +02:00
}
}
2016-03-30 14:20:52 -07:00
if ( propertySet )
{
WritePropertyObject ( results , path );
}
}
else
{
2018-12-22 01:26:06 -08:00
string error = StringUtil . Format ( FileSystemProviderStrings . ItemDoesNotExist , path );
2016-03-30 14:20:52 -07:00
Exception e = new IOException ( error );
WriteError ( new ErrorRecord (
e ,
"ItemDoesNotExist" ,
ErrorCategory . ObjectNotFound ,
path ));
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2016-08-28 05:51:23 +00:00
/// Gets the dynamic property parameters required by the set-itemproperty cmdlet.
2016-03-30 14:20:52 -07:00
/// 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">
2017-01-16 13:31:14 -08:00
/// A PSObject which contains a collection of the name, type, value
2016-03-30 14:20:52 -07:00
/// 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 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
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 =
2018-12-31 20:44:16 +05:00
string . Format (
2016-03-30 14:20:52 -07:00
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 ();
2016-07-29 13:02:49 -07:00
result . Properties . Add ( new PSNoteProperty ( propertiesToClear [ 0 ], fileSystemInfo . Attributes ));
2016-03-30 14:20:52 -07:00
// Now write out the attribute that was cleared.
WritePropertyObject ( result , path );
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
}
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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
WriteError ( new ErrorRecord ( ioException , "ClearPropertyIOError" , ErrorCategory . WriteError , path ));
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2016-08-28 05:51:23 +00:00
/// Gets the dynamic property parameters required by the clear-itemproperty cmdlet.
2016-03-30 14:20:52 -07:00
/// 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 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
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 ;
2016-07-29 13:02:49 -07:00
if ( delimiterSpecified )
2016-03-30 14:20:52 -07:00
delimiter = dynParams . Delimiter ;
// Get the stream type
2017-10-23 19:46:27 -07:00
usingByteEncoding = dynParams . AsByteStream ;
2016-03-30 14:20:52 -07:00
streamTypeSpecified = dynParams . WasStreamTypeSpecified ;
2017-10-23 19:46:27 -07:00
if ( usingByteEncoding && streamTypeSpecified )
{
WriteWarning ( FileSystemProviderStrings . EncodingNotUsed );
}
2016-03-30 14:20:52 -07:00
if ( streamTypeSpecified )
{
2017-10-23 19:46:27 -07:00
encoding = dynParams . Encoding ;
2016-03-30 14:20:52 -07:00
}
// Get the wait value
waitForChanges = dynParams . Wait ;
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
// Get the stream name
streamName = dynParams . Stream ;
2017-08-28 15:39:42 -07:00
#endif
2018-12-08 18:53:30 +02:00
}
}
2016-03-30 14:20:52 -07:00
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
// 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 );
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
FileSystemContentReaderWriter stream = null ;
try
{
2018-09-28 21:06:13 +05:30
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 ;
}
2016-03-30 14:20:52 -07:00
// 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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
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 ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2016-08-28 05:51:23 +00:00
/// Gets the dynamic property parameters required by the get-content cmdlet.
2016-03-30 14:20:52 -07:00
/// </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 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
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 )
{
2017-10-23 19:46:27 -07:00
usingByteEncoding = dynParams . AsByteStream ;
2016-03-30 14:20:52 -07:00
streamTypeSpecified = dynParams . WasStreamTypeSpecified ;
2017-10-23 19:46:27 -07:00
if ( usingByteEncoding && streamTypeSpecified )
{
WriteWarning ( FileSystemProviderStrings . EncodingNotUsed );
}
2016-03-30 14:20:52 -07:00
if ( streamTypeSpecified )
{
2017-10-23 19:46:27 -07:00
encoding = dynParams . Encoding ;
2016-03-30 14:20:52 -07:00
}
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
streamName = dynParams . Stream ;
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
suppressNewline = dynParams . NoNewline . IsPresent ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
}
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
// 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 );
}
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
FileSystemContentReaderWriter stream = null ;
try
{
2018-09-28 21:06:13 +05:30
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 ;
}
2018-10-23 16:27:53 -07:00
stream = new FileSystemContentReaderWriter ( path , streamName , filemode , FileAccess . Write , FileShare . ReadWrite , encoding , usingByteEncoding , false , this , false , suppressNewline );
2016-03-30 14:20:52 -07:00
}
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 )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
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 ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2016-08-28 05:51:23 +00:00
/// Gets the dynamic property parameters required by the set-content and
2016-03-30 14:20:52 -07:00
/// 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 )
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
throw PSTraceSource . NewArgumentException ( "path" );
}
path = NormalizePath ( path );
2018-11-01 09:26:45 +05:30
if ( Directory . Exists ( path ))
{
string errorMsg = StringUtil . Format ( SessionStateStrings . ClearDirectoryContent , path );
WriteError ( new ErrorRecord ( new NotSupportedException ( errorMsg ), "ClearDirectoryContent" , ErrorCategory . InvalidOperation , path ));
return ;
}
2016-03-30 14:20:52 -07:00
try
{
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
bool clearStream = false ;
2017-08-28 15:39:42 -07:00
string streamName = null ;
2016-03-30 14:20:52 -07:00
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 ;
}
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( streamName ))
2016-03-30 14:20:52 -07:00
{
// 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.
2018-12-31 20:44:16 +05:00
if ( string . Equals ( ":$DATA" , streamName , StringComparison . OrdinalIgnoreCase ))
2016-03-30 14:20:52 -07:00
{
clearStream = false ;
}
2016-07-29 13:02:49 -07:00
if ( clearStream )
2016-03-30 14:20:52 -07:00
{
FileStream fileStream = null ;
2018-12-31 20:44:16 +05:00
string streamAction = string . Format (
2016-03-30 14:20:52 -07:00
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
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
{
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
2018-06-13 00:30:10 +05:00
WriteItemObject ( string . Empty , path , false );
2016-03-30 14:20:52 -07:00
}
catch ( ArgumentException argException )
{
WriteError ( new ErrorRecord ( argException , "ClearContentArgumentError" , ErrorCategory . InvalidArgument , path ));
}
catch ( IOException ioException )
{
2019-01-06 06:51:20 +05:00
// IOException contains specific message about the error occured and so no need for errordetails.
2016-03-30 14:20:52 -07:00
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 ();
2019-01-06 06:51:20 +05:00
// For filesystem once content is cleared
2018-06-13 00:30:10 +05:00
WriteItemObject ( string . Empty , path , false );
2016-03-30 14:20:52 -07:00
}
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 ));
}
}
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2016-08-28 05:51:23 +00:00
/// Gets the dynamic property parameters required by the clear-content cmdlet.
2016-03-30 14:20:52 -07:00
/// </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 );
}
}
}
2016-04-06 12:01:07 -07:00
/// <summary>
2017-01-16 13:31:14 -08:00
/// The API 'PathIsNetworkPath' is not available in CoreSystem.
2016-04-06 12:01:07 -07:00
/// This implementation is based on the 'PathIsNetworkPath' API.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
internal static bool PathIsNetworkPath ( string path )
{
2016-07-22 10:49:45 -07:00
#if UNIX
2016-07-14 15:41:17 -07:00
return false ;
#else
return WinPathIsNetworkPath ( path );
#endif
2016-04-06 12:01:07 -07:00
}
internal static bool WinPathIsNetworkPath ( string path )
{
return NativeMethods . PathIsNetworkPath ( path ); // call the native method
}
2016-07-29 13:02:49 -07:00
private static class NativeMethods
2016-03-30 14:20:52 -07:00
{
/// <summary>
2017-01-16 13:31:14 -08:00
/// WNetAddConnection2 API makes a connection to a network resource
2016-03-30 14:20:52 -07:00
/// and can redirect a local device to the network resource.
2017-01-16 13:31:14 -08:00
/// This API simulates the "new Use" functionality used to connect to
2016-03-30 14:20:52 -07:00
/// network resource.
/// </summary>
/// <param name="netResource">
2017-01-16 13:31:14 -08:00
/// The The netResource structure contains information
2016-03-30 14:20:52 -07:00
/// about a network resource.</param>
/// <param name="password">
2016-08-28 05:51:23 +00:00
/// The password used to get connected to network resource.
2016-03-30 14:20:52 -07:00
/// </param>
/// <param name="username">
/// The username used to get connected to network resource.
/// </param>
/// <param name="flags">
2017-01-16 13:31:14 -08:00
/// The flags parameter is used to indicate if the created network
2016-03-30 14:20:52 -07:00
/// resource has to be persisted or not.
/// </param>
2017-01-16 13:31:14 -08:00
/// <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
2016-03-30 14:20:52 -07:00
/// the connection is returned.</returns>
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
2016-07-29 13:02:49 -07:00
internal static extern int WNetAddConnection2 ( ref NetResource netResource , byte [] password , string username , int flags );
2016-03-30 14:20:52 -07:00
/// <summary>
/// WNetCancelConnection2 function cancels an existing network connection.
/// </summary>
/// <param name="driveName">
/// PSDrive Name.
/// </param>
/// <param name="flags">
/// Connection Type.
/// </param>
/// <param name="force">
2017-01-16 13:31:14 -08:00
/// Specifies whether the disconnection should occur if there are open files or jobs
/// on the connection. If this parameter is FALSE, the function fails
2016-03-30 14:20:52 -07:00
/// if there are open files or jobs.
/// </param>
2017-01-16 13:31:14 -08:00
/// <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.
2016-03-30 14:20:52 -07:00
/// </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 );
2019-01-06 06:51:20 +05:00
#if CORECLR // TODO:CORECLR Win32 function 'PathIsNetworkPath' is in an extension API set which is currently not on CSS.
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
/// The API 'PathIsNetworkPath' is not available in CoreSystem.
2016-03-30 14:20:52 -07:00
/// 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 ;
}
2019-01-23 09:37:45 -08:00
if ( Utils . PathIsUnc ( path ))
2016-03-30 14:20:52 -07:00
{
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 ;
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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.
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// If lpDeviceName is non-NULL, the function retrieves information about the particular MS-DOS device specified by lpDeviceName.
2017-01-16 13:31:14 -08:00
/// The first null-terminated string stored into the buffer is the current mapping for the device.
2016-03-30 14:20:52 -07:00
/// 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">
2017-01-16 13:31:14 -08:00
/// The maximum number of characters that can be stored into the buffer
2016-03-30 14:20:52 -07:00
/// </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>
2019-01-08 10:43:24 -08:00
/// <param name="symbolicLinkFlags">Flag values from SymbolicLinkFlags enum.</param>
2016-03-30 14:20:52 -07:00
/// <returns>1 on successful creation.</returns>
[DllImport(PinvokeDllNames.CreateSymbolicLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)]
2019-03-12 23:49:16 +01:00
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool CreateSymbolicLink ( string name , string destination , SymbolicLinkFlags symbolicLinkFlags );
2019-01-08 10:43:24 -08:00
/// <summary>
/// Flags used when creating a symbolic link.
/// </summary>
[Flags]
internal enum SymbolicLinkFlags
{
/// <summary>
/// Symbolic link is a file.
/// </summary>
File = 0 ,
/// <summary>
/// Symbolic link is a directory.
/// </summary>
Directory = 1 ,
/// <summary>
/// Allow creation of symbolic link without elevation. Requires Developer mode.
/// </summary>
2019-03-12 23:49:16 +01:00
AllowUnprivilegedCreate = 2 ,
2019-01-08 10:43:24 -08:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Creates a hard link using the native API.
/// </summary>
/// <param name="name">Name of the hard link.</param>
2018-12-28 16:39:06 +05:00
/// <param name="existingFileName">Path to the target of the hard link.</param>
2016-03-30 14:20:52 -07:00
/// <param name="SecurityAttributes"></param>
/// <returns></returns>
[DllImport(PinvokeDllNames.CreateHardLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)]
2019-02-27 08:33:46 +05:00
[return: MarshalAs(UnmanagedType.Bool)]
2016-03-30 14:20:52 -07:00
internal static extern bool CreateHardLink ( string name , string existingFileName , IntPtr SecurityAttributes );
2019-02-04 21:11:46 +01:00
// OneDrive placeholder support
#if ! UNIX
/// <summary>
/// Returns the placeholder compatibility mode for the current process.
/// </summary>
/// <returns>The process's placeholder compatibily mode (PHCM_xxx), or a negative value on error (PCHM_ERROR_xxx).</returns>
[DllImport("ntdll.dll")]
internal static extern sbyte RtlQueryProcessPlaceholderCompatibilityMode ();
/// <summary>
/// Sets the placeholder compatibility mode for the current process.
/// </summary>
/// <param name="pcm">The placeholder compatibility mode to set.</param>
/// <returns>The process's previous placeholder compatibily mode (PHCM_xxx), or a negative value on error (PCHM_ERROR_xxx).</returns>
[DllImport("ntdll.dll")]
internal static extern sbyte RtlSetProcessPlaceholderCompatibilityMode ( sbyte pcm );
internal const sbyte PHCM_APPLICATION_DEFAULT = 0 ;
internal const sbyte PHCM_DISGUISE_PLACEHOLDER = 1 ;
internal const sbyte PHCM_EXPOSE_PLACEHOLDERS = 2 ;
internal const sbyte PHCM_MAX = 2 ;
internal const sbyte PHCM_ERROR_INVALID_PARAMETER = - 1 ;
internal const sbyte PHCM_ERROR_NO_TEB = - 2 ;
#endif
2016-03-30 14:20:52 -07:00
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Managed equivalent of NETRESOURCE structure of WNet API.
2016-03-30 14:20:52 -07:00
/// </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 ;
}
2017-07-06 12:28:27 -07:00
#region InodeTracker
/// <summary>
/// Tracks visited files/directories by caching their device IDs and inodes.
/// </summary>
private class InodeTracker
{
private HashSet <( UInt64 , UInt64 )> _visitations ;
/// <summary>
2019-01-06 04:40:24 +05:00
/// Construct a new InodeTracker with an initial path.
2017-07-06 12:28:27 -07:00
/// </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
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
internal static class SafeInvokeCommand
{
public static Hashtable Invoke ( System . Management . Automation . PowerShell ps , FileSystemProvider fileSystemContext , CmdletProviderContext cmdletContext )
{
return Invoke ( ps , fileSystemContext , cmdletContext , true );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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 ();
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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 ))
{
2017-01-16 13:31:14 -08:00
// unexpected output
2016-03-30 14:20:52 -07:00
Dbg . Diagnostics . Assert ( output [ 0 ] != null , "Expected an output from the remote call." );
return null ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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>
2019-01-06 04:40:24 +05:00
/// Defines the container cmdlet dynamic providers.
2016-03-30 14:20:52 -07:00
/// </summary>
internal sealed class GetChildDynamicParameters
{
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the attribute filtering enum evaluator.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter]
2016-08-02 13:29:09 -07:00
public FlagsExpression < FileAttributes > Attributes { get ; set ; }
2016-03-30 14:20:52 -07:00
2017-07-06 12:28:27 -07:00
/// <summary>
/// Gets or sets the flag to follow symbolic links when recursing.
/// </summary>
[Parameter]
public SwitchParameter FollowSymlink { get ; set ; }
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the filter directory flag.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter]
[Alias("ad", "d")]
public SwitchParameter Directory
{
2016-08-02 13:29:09 -07:00
get { return _attributeDirectory ; }
2018-12-23 22:20:06 -08:00
2016-08-02 13:29:09 -07:00
set { _attributeDirectory = value ; }
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _attributeDirectory ;
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the filter file flag.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter]
[Alias("af")]
public SwitchParameter File
{
2016-08-02 13:29:09 -07:00
get { return _attributeFile ; }
2018-12-23 22:20:06 -08:00
2016-08-02 13:29:09 -07:00
set { _attributeFile = value ; }
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _attributeFile ;
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the filter hidden flag.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter]
[Alias("ah", "h")]
public SwitchParameter Hidden
{
2016-08-02 13:29:09 -07:00
get { return _attributeHidden ; }
2018-12-23 22:20:06 -08:00
2016-08-02 13:29:09 -07:00
set { _attributeHidden = value ; }
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _attributeHidden ;
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the filter readonly flag.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter]
[Alias("ar")]
public SwitchParameter ReadOnly
{
2016-08-02 13:29:09 -07:00
get { return _attributeReadOnly ; }
2018-12-23 22:20:06 -08:00
2016-08-02 13:29:09 -07:00
set { _attributeReadOnly = value ; }
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _attributeReadOnly ;
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the filter system flag.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter]
[Alias("as")]
public SwitchParameter System
{
2016-08-02 13:29:09 -07:00
get { return _attributeSystem ; }
2018-12-23 22:20:06 -08:00
2016-08-02 13:29:09 -07:00
set { _attributeSystem = value ; }
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _attributeSystem ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Defines the dynamic parameters used by both the content reader and writer.
/// </summary>
public class FileSystemContentDynamicParametersBase
{
/// <summary>
2017-01-16 13:31:14 -08:00
/// Gets or sets the encoding method used when
2016-03-30 14:20:52 -07:00
/// reading data from the file.
/// </summary>
[Parameter]
2017-10-23 19:46:27 -07:00
[ArgumentToEncodingTransformationAttribute()]
2018-09-11 06:26:12 +02:00
[ArgumentEncodingCompletionsAttribute]
2017-10-23 19:46:27 -07:00
[ValidateNotNullOrEmpty]
public Encoding Encoding
{
get
{
return _encoding ;
}
2018-12-23 22:20:06 -08:00
2017-10-23 19:46:27 -07:00
set
{
_encoding = value ;
// If an encoding was explicitly set, be sure to capture that.
WasStreamTypeSpecified = true ;
}
}
2018-12-23 22:20:06 -08:00
2017-10-23 19:46:27 -07:00
private Encoding _encoding = ClrFacade . GetDefaultEncoding ();
/// <summary>
2019-01-06 04:40:24 +05:00
/// Return file contents as a byte stream or create file from a series of bytes.
2017-10-23 19:46:27 -07:00
/// </summary>
[Parameter]
public SwitchParameter AsByteStream { get ; set ; }
2016-03-30 14:20:52 -07:00
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
/// <summary>
/// A parameter to return a stream of an item.
/// </summary>
[Parameter]
2018-12-22 01:26:06 -08:00
public string Stream { get ; set ; }
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
/// <summary>
/// Gets the status of the StreamType parameter. Returns true
/// if the stream was opened with a user-specified encoding, false otherwise.
/// </summary>
2017-10-23 19:46:27 -07:00
public bool WasStreamTypeSpecified { get ; private set ; }
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Defines the dynamic parameters used by the Clear-Content cmdlet.
/// </summary>
public class FileSystemClearContentDynamicParameters
{
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
/// <summary>
/// A parameter to return a stream of an item.
/// </summary>
[Parameter]
2018-12-22 01:26:06 -08:00
public string Stream { get ; set ; }
2017-08-28 15:39:42 -07:00
#endif
2018-12-20 22:54:10 -08:00
}
2016-03-30 14:20:52 -07:00
/// <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
{
2016-07-29 13:02:49 -07:00
return _suppressNewline ;
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
set
{
2016-07-29 13:02:49 -07:00
_suppressNewline = value ;
2016-03-30 14:20:52 -07:00
}
}
2016-07-29 13:02:49 -07:00
private bool _suppressNewline = false ;
2018-12-20 22:54:10 -08:00
}
2016-03-30 14:20:52 -07:00
/// <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
{
2016-07-29 13:02:49 -07:00
return _delimiter ;
2017-10-23 19:46:27 -07:00
}
2016-03-30 14:20:52 -07:00
set
{
2016-08-02 13:29:09 -07:00
DelimiterSpecified = true ;
2016-07-29 13:02:49 -07:00
_delimiter = value ;
2017-10-23 19:46:27 -07:00
}
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private string _delimiter = "\n" ;
2016-03-30 14:20:52 -07:00
/// <summary>
2017-01-16 13:31:14 -08:00
/// Gets or sets the Wait flag. The wait flag determines if we want
2016-03-30 14:20:52 -07:00
/// 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
{
2016-07-29 13:02:49 -07:00
return _wait ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
set
{
2016-07-29 13:02:49 -07:00
_wait = value ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _wait ;
2016-03-30 14:20:52 -07:00
/// <summary>
2016-06-27 16:12:51 -07:00
/// When the Raw switch is present, we don't do any breaks on newlines,
2016-03-30 14:20:52 -07:00
/// and only emit one object to the pipeline: all of the content.
/// </summary>
[Parameter]
public SwitchParameter Raw
{
get
{
2016-07-29 13:02:49 -07:00
return _isRaw ;
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
set
{
2016-07-29 13:02:49 -07:00
_isRaw = value ;
2016-03-30 14:20:52 -07:00
}
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _isRaw ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// Gets the status of the delimiter parameter. Returns true
/// if the delimiter was explicitly specified by the user, false otherwise.
/// </summary>
2019-02-04 21:11:46 +01:00
public bool DelimiterSpecified
{
get ; private set ;
// get
2018-12-08 18:53:30 +02:00
}
}
2016-03-30 14:20:52 -07:00
/// <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>
2019-01-06 04:40:24 +05:00
/// A parameter to test if a file is newer than a certain time or date.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter]
public DateTime ? NewerThan { get ; set ; }
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Provides the dynamic parameters for Get-Item on the file system.
/// </summary>
public class FileSystemProviderGetItemDynamicParameters
{
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
/// <summary>
/// A parameter to return the streams of an item.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public string [] Stream { get ; set ; }
2017-08-28 15:39:42 -07:00
#endif
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Provides the dynamic parameters for Remove-Item on the file system.
/// </summary>
public class FileSystemProviderRemoveItemDynamicParameters
{
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
/// <summary>
/// A parameter to return the streams of an item.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public string [] Stream { get ; set ; }
2017-08-28 15:39:42 -07:00
#endif
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
#endregion
#region Symbolic Link
/// <summary>
/// Class to find the symbolic link target.
/// </summary>
public static class InternalSymbolicLinkLinkCodeMethods
{
2019-01-06 06:51:20 +05:00
// This size comes from measuring the size of the header of REPARSE_GUID_DATA_BUFFER
2016-03-30 14:20:52 -07:00
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.
2016-07-29 13:02:49 -07:00
private const int MAX_REPARSE_SIZE = ( 16 * 1024 ) + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE ;
2016-03-30 14:20:52 -07:00
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 ;
2019-08-13 17:45:41 -07:00
private const uint IO_REPARSE_TAG_APPEXECLINK = 0x8000001B ;
2016-03-30 14:20:52 -07:00
private const string NonInterpretedPathPrefix = @"\??\" ;
2019-05-06 17:28:00 -07:00
private const int MAX_PATH = 260 ;
2016-03-30 14:20:52 -07:00
[Flags]
2019-01-06 06:51:20 +05:00
// dwDesiredAccess of CreateFile
2016-03-30 14:20:52 -07:00
internal enum FileDesiredAccess : uint
{
GenericRead = 0x80000000 ,
GenericWrite = 0x40000000 ,
GenericExecute = 0x20000000 ,
GenericAll = 0x10000000 ,
}
[Flags]
2019-01-06 06:51:20 +05:00
// dwShareMode of CreateFile
2016-03-30 14:20:52 -07:00
internal enum FileShareMode : uint
{
None = 0x00000000 ,
Read = 0x00000001 ,
Write = 0x00000002 ,
Delete = 0x00000004 ,
}
2019-01-06 06:51:20 +05:00
// dwCreationDisposition of CreateFile
2016-03-30 14:20:52 -07:00
internal enum FileCreationDisposition : uint
{
New = 1 ,
CreateAlways = 2 ,
OpenExisting = 3 ,
OpenAlways = 4 ,
TruncateExisting = 5 ,
}
[Flags]
2019-01-06 06:51:20 +05:00
// dwFlagsAndAttributes
2016-03-30 14:20:52 -07:00
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 ;
}
2019-08-13 17:45:41 -07:00
[StructLayout(LayoutKind.Sequential)]
private struct REPARSE_DATA_BUFFER_APPEXECLINK
{
public uint ReparseTag ;
public ushort ReparseDataLength ;
public ushort Reserved ;
public uint StringCount ;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
public byte [] StringList ;
}
2016-03-30 14:20:52 -07:00
[StructLayout(LayoutKind.Sequential)]
2016-07-29 13:02:49 -07:00
private struct BY_HANDLE_FILE_INFORMATION
2016-03-30 14:20:52 -07:00
{
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)]
2016-07-29 13:02:49 -07:00
private struct GUID
2016-03-30 14:20:52 -07:00
{
public uint Data1 ;
public ushort Data2 ;
public ushort Data3 ;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
2019-01-08 00:40:07 +05:00
public char [] Data4 ;
2016-03-30 14:20:52 -07:00
}
[StructLayout(LayoutKind.Sequential)]
2016-07-29 13:02:49 -07:00
private struct REPARSE_GUID_DATA_BUFFER
2016-03-30 14:20:52 -07:00
{
public uint ReparseTag ;
public ushort ReparseDataLength ;
public ushort Reserved ;
public GUID ReparseGuid ;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_REPARSE_SIZE)]
2019-01-08 00:40:07 +05:00
public char [] DataBuffer ;
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
[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 );
[DllImport(PinvokeDllNames.GetFileInformationByHandleDllName, SetLastError = true, CharSet = CharSet.Unicode)]
2019-02-27 08:33:46 +05:00
[return: MarshalAs(UnmanagedType.Bool)]
2016-03-30 14:20:52 -07:00
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 );
2019-08-29 14:50:54 -07:00
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 );
}
// SetLastError is false as the use of this API doesn't not require GetLastError() to be called
[DllImport(PinvokeDllNames.FindFirstFileDllName, EntryPoint = "FindFirstFileExW", SetLastError = false, CharSet = CharSet.Unicode)]
private static extern SafeFindHandle FindFirstFileEx ( string lpFileName , FINDEX_INFO_LEVELS fInfoLevelId , ref WIN32_FIND_DATA lpFindFileData , FINDEX_SEARCH_OPS fSearchOp , IntPtr lpSearchFilter , int dwAdditionalFlags );
2019-05-06 17:28:00 -07:00
internal enum FINDEX_INFO_LEVELS : uint
{
FindExInfoStandard = 0x0 u ,
FindExInfoBasic = 0x1 u ,
FindExInfoMaxInfoLevel = 0x2 u ,
}
internal enum FINDEX_SEARCH_OPS : uint
{
FindExSearchNameMatch = 0x0 u ,
FindExSearchLimitToDirectories = 0x1 u ,
FindExSearchLimitToDevices = 0x2 u ,
FindExSearchMaxSearchOp = 0x3 u ,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal unsafe struct WIN32_FIND_DATA
{
internal uint dwFileAttributes ;
internal System . Runtime . InteropServices . ComTypes . FILETIME ftCreationTime ;
internal System . Runtime . InteropServices . ComTypes . FILETIME ftLastAccessTime ;
internal System . Runtime . InteropServices . ComTypes . FILETIME ftLastWriteTime ;
internal uint nFileSizeHigh ;
internal uint nFileSizeLow ;
internal uint dwReserved0 ;
internal uint dwReserved1 ;
internal fixed char cFileName [ MAX_PATH ];
internal fixed char cAlternateFileName [ 14 ];
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Gets the target of the specified reparse point.
/// </summary>
/// <param name="instance">The object of FileInfo or DirectoryInfo type.</param>
2018-12-25 21:43:03 +05:00
/// <returns>The target of the reparse point.</returns>
2019-03-12 23:49:16 +01:00
public static string GetTarget ( PSObject instance )
2016-03-30 14:20:52 -07:00
{
2018-09-09 00:40:06 +05:00
if ( instance . BaseObject is FileSystemInfo fileSysInfo )
2016-03-30 14:20:52 -07:00
{
2018-09-09 00:40:06 +05:00
#if ! UNIX
using ( SafeFileHandle handle = OpenReparsePoint ( fileSysInfo . FullName , FileDesiredAccess . GenericRead ))
2016-03-30 14:20:52 -07:00
{
2018-09-09 00:40:06 +05:00
string linkTarget = WinInternalGetTarget ( handle );
2016-03-30 14:20:52 -07:00
2019-03-12 23:49:16 +01:00
return linkTarget ;
2016-03-30 14:20:52 -07:00
}
2019-03-12 23:49:16 +01:00
#else
return UnixInternalGetTarget ( fileSysInfo . FullName );
2018-09-09 00:40:06 +05:00
#endif
2016-03-30 14:20:52 -07:00
}
2018-09-09 00:40:06 +05:00
return null ;
2016-03-30 14:20:52 -07:00
}
/// <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 )
{
2016-04-06 12:01:07 -07:00
return InternalGetLinkType ( fileSysInfo );
2016-03-30 14:20:52 -07:00
}
2019-03-12 23:49:16 +01:00
return null ;
2016-03-30 14:20:52 -07:00
}
2016-10-03 17:24:28 -07:00
#if UNIX
2019-03-12 23:49:16 +01:00
private static string UnixInternalGetTarget ( string filePath )
{
2016-10-03 17:24:28 -07:00
string link = Platform . NonWindowsInternalGetTarget ( filePath );
2019-03-12 23:49:16 +01:00
if ( string . IsNullOrEmpty ( link ))
2016-10-03 17:24:28 -07:00
{
throw new Win32Exception ( Marshal . GetLastWin32Error ());
2016-04-06 12:01:07 -07:00
}
2016-03-30 14:20:52 -07:00
2019-03-12 23:49:16 +01:00
return link ;
2016-03-30 14:20:52 -07:00
}
2019-03-12 23:49:16 +01:00
#endif
2016-03-30 14:20:52 -07:00
2016-04-06 12:01:07 -07:00
private static string InternalGetLinkType ( FileSystemInfo fileInfo )
2016-03-30 14:26:05 -07:00
{
2016-04-08 14:54:41 -07:00
if ( Platform . IsWindows )
2016-04-06 12:01:07 -07:00
{
return WinInternalGetLinkType ( fileInfo . FullName );
}
else
{
return Platform . NonWindowsInternalGetLinkType ( fileInfo );
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
private static string WinInternalGetLinkType ( string filePath )
{
2016-04-08 14:54:41 -07:00
if (! Platform . IsWindows )
2016-04-06 12:01:07 -07:00
{
2016-07-13 21:15:45 -07:00
throw new PlatformNotSupportedException ();
2016-04-06 12:01:07 -07:00
}
2016-03-30 14:20:52 -07:00
using ( SafeFileHandle handle = OpenReparsePoint ( filePath , FileDesiredAccess . GenericRead ))
{
2017-07-29 17:40:19 -07:00
int outBufferSize = Marshal . SizeOf < REPARSE_DATA_BUFFER_SYMBOLICLINK >();
2016-03-30 14:20:52 -07:00
IntPtr outBuffer = Marshal . AllocHGlobal ( outBufferSize );
bool success = false ;
try
{
int bytesReturned ;
string linkType = null ;
2019-01-06 06:51:20 +05:00
// OACR warning 62001 about using DeviceIOControl has been disabled.
2016-03-30 14:20:52 -07:00
// According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
handle . DangerousAddRef ( ref success );
2019-01-06 06:51:20 +05:00
// Get Buffer size
2016-03-30 14:20:52 -07:00
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 );
}
2017-07-29 22:34:31 -07:00
REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal . PtrToStructure < REPARSE_DATA_BUFFER_SYMBOLICLINK >( outBuffer );
2016-03-30 14:20:52 -07:00
2019-08-13 17:45:41 -07:00
switch ( reparseDataBuffer . ReparseTag )
2016-03-30 14:20:52 -07:00
{
2019-08-13 17:45:41 -07:00
case IO_REPARSE_TAG_SYMLINK :
linkType = "SymbolicLink" ;
break ;
case IO_REPARSE_TAG_MOUNT_POINT :
linkType = "Junction" ;
break ;
case IO_REPARSE_TAG_APPEXECLINK :
linkType = "AppExeCLink" ;
break ;
default :
linkType = IsHardLink ( ref dangerousHandle ) ? "HardLink" : null ;
break ;
2016-03-30 14:20:52 -07:00
}
return linkType ;
}
finally
{
if ( success )
{
handle . DangerousRelease ();
}
Marshal . FreeHGlobal ( outBuffer );
}
}
}
2016-04-06 12:01:07 -07:00
internal static bool IsHardLink ( FileSystemInfo fileInfo )
{
2016-10-05 16:34:48 -07:00
#if UNIX
return Platform . NonWindowsIsHardLink ( fileInfo );
#else
return WinIsHardLink ( fileInfo );
#endif
2016-04-06 12:01:07 -07:00
}
internal static bool IsReparsePoint ( FileSystemInfo fileInfo )
{
2019-05-08 01:31:54 +05:00
return fileInfo . Attributes . HasFlag ( System . IO . FileAttributes . ReparsePoint );
2016-04-06 12:01:07 -07:00
}
2019-08-13 17:45:41 -07:00
internal static bool IsReparsePointWithTarget ( FileSystemInfo fileInfo )
2019-05-06 17:28:00 -07:00
{
2019-08-13 17:45:41 -07:00
if (! IsReparsePoint ( fileInfo ))
{
return false ;
}
2019-05-06 17:28:00 -07:00
#if ! UNIX
var data = new WIN32_FIND_DATA ();
2019-08-29 14:50:54 -07:00
using ( var handle = FindFirstFileEx ( fileInfo . FullName , FINDEX_INFO_LEVELS . FindExInfoBasic , ref data , FINDEX_SEARCH_OPS . FindExSearchNameMatch , IntPtr . Zero , 0 ))
2019-05-06 17:28:00 -07:00
{
2019-08-13 17:45:41 -07:00
// Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem (like symlinks)
2019-05-06 17:28:00 -07:00
// In the case of OneDrive, they are not surrogates and would be safe to recurse into.
// This code is equivalent to the IsReparseTagNameSurrogate macro: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-isreparsetagnamesurrogate
2019-08-13 17:45:41 -07:00
if (! handle . IsInvalid && ( data . dwReserved0 & 0x20000000 ) == 0 && ( data . dwReserved0 & IO_REPARSE_TAG_APPEXECLINK ) != IO_REPARSE_TAG_APPEXECLINK )
2019-05-06 17:28:00 -07:00
{
return false ;
}
}
#endif
return true ;
}
2016-04-06 12:01:07 -07:00
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 ;
}
2017-04-21 16:09:53 -07:00
internal static bool IsSameFileSystemItem ( string pathOne , string pathTwo )
{
#if UNIX
return Platform . NonWindowsIsSameFileSystemItem ( pathOne , pathTwo );
#else
return WinIsSameFileSystemItem ( pathOne , pathTwo );
#endif
}
2017-08-28 15:39:42 -07:00
#if ! UNIX
private static bool WinIsSameFileSystemItem ( string pathOne , string pathTwo )
2017-04-21 16:09:53 -07:00
{
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 )
{
2017-09-04 19:45:51 -07:00
BY_HANDLE_FILE_INFORMATION infoOne ;
BY_HANDLE_FILE_INFORMATION infoTwo ;
2019-02-04 21:11:46 +01:00
if ( GetFileInformationByHandle ( sfOne . DangerousGetHandle (), out infoOne )
2017-04-21 16:09:53 -07:00
&& GetFileInformationByHandle ( sfTwo . DangerousGetHandle (), out infoTwo ))
{
2019-02-04 21:11:46 +01:00
return infoOne . VolumeSerialNumber == infoTwo . VolumeSerialNumber
2017-04-21 16:09:53 -07:00
&& infoOne . FileIndexHigh == infoTwo . FileIndexHigh
&& infoOne . FileIndexLow == infoTwo . FileIndexLow ;
}
}
}
return false ;
}
2017-08-28 15:39:42 -07:00
#endif
2017-04-21 16:09:53 -07:00
2017-07-06 12:28:27 -07:00
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 ;
}
2017-08-28 15:39:42 -07:00
#if ! UNIX
private static bool WinGetInodeData ( string path , out System . ValueTuple < UInt64 , UInt64 > inodeData )
2017-07-06 12:28:27 -07:00
{
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 ;
}
2017-08-28 15:39:42 -07:00
#endif
2016-04-06 11:55:18 -07:00
internal static bool IsHardLink ( ref IntPtr handle )
2016-04-06 12:01:07 -07:00
{
2016-10-05 16:34:48 -07:00
#if UNIX
return Platform . NonWindowsIsHardLink ( ref handle );
#else
return WinIsHardLink ( ref handle );
#endif
2016-04-06 12:01:07 -07:00
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
internal static bool WinIsHardLink ( ref IntPtr handle )
2016-07-29 13:02:49 -07:00
{
BY_HANDLE_FILE_INFORMATION handleInfo ;
2016-03-30 14:20:52 -07:00
bool succeeded = InternalSymbolicLinkLinkCodeMethods . GetFileInformationByHandle ( handle , out handleInfo );
2018-07-26 10:08:38 -07:00
return succeeded && ( handleInfo . NumberOfLinks > 1 );
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
2016-04-06 12:01:07 -07:00
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
private static string WinInternalGetTarget ( SafeFileHandle handle )
2016-03-30 14:20:52 -07:00
{
2017-07-29 17:40:19 -07:00
int outBufferSize = Marshal . SizeOf < REPARSE_DATA_BUFFER_SYMBOLICLINK >();
2016-03-30 14:20:52 -07:00
IntPtr outBuffer = Marshal . AllocHGlobal ( outBufferSize );
bool success = false ;
try
{
int bytesReturned ;
2019-01-06 06:51:20 +05:00
// OACR warning 62001 about using DeviceIOControl has been disabled.
2016-03-30 14:20:52 -07:00
// 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 );
}
string targetDir = null ;
2019-08-13 17:45:41 -07:00
REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal . PtrToStructure < REPARSE_DATA_BUFFER_SYMBOLICLINK >( outBuffer );
2016-03-30 14:20:52 -07:00
2019-08-13 17:45:41 -07:00
switch ( reparseDataBuffer . ReparseTag )
2016-03-30 14:20:52 -07:00
{
2019-08-13 17:45:41 -07:00
case IO_REPARSE_TAG_SYMLINK :
targetDir = Encoding . Unicode . GetString ( reparseDataBuffer . PathBuffer , reparseDataBuffer . SubstituteNameOffset , reparseDataBuffer . SubstituteNameLength );
break ;
2016-03-30 14:20:52 -07:00
2019-08-13 17:45:41 -07:00
case IO_REPARSE_TAG_MOUNT_POINT :
REPARSE_DATA_BUFFER_MOUNTPOINT reparseMountPointDataBuffer = Marshal . PtrToStructure < REPARSE_DATA_BUFFER_MOUNTPOINT >( outBuffer );
targetDir = Encoding . Unicode . GetString ( reparseMountPointDataBuffer . PathBuffer , reparseMountPointDataBuffer . SubstituteNameOffset , reparseMountPointDataBuffer . SubstituteNameLength );
break ;
case IO_REPARSE_TAG_APPEXECLINK :
REPARSE_DATA_BUFFER_APPEXECLINK reparseAppExeDataBuffer = Marshal . PtrToStructure < REPARSE_DATA_BUFFER_APPEXECLINK >( outBuffer );
// The target file is at index 2
if ( reparseAppExeDataBuffer . StringCount >= 3 )
{
string temp = Encoding . Unicode . GetString ( reparseAppExeDataBuffer . StringList );
targetDir = temp . Split ( '\0' )[ 2 ];
}
break ;
default :
return null ;
2016-03-30 14:20:52 -07:00
}
2019-02-13 16:51:05 +05:00
if ( targetDir != null && targetDir . StartsWith ( NonInterpretedPathPrefix , StringComparison . OrdinalIgnoreCase ))
{
2016-03-30 14:20:52 -07:00
targetDir = targetDir . Substring ( NonInterpretedPathPrefix . Length );
2019-02-13 16:51:05 +05:00
}
2016-03-30 14:20:52 -07:00
return targetDir ;
}
finally
{
if ( success )
{
handle . DangerousRelease ();
}
Marshal . FreeHGlobal ( outBuffer );
}
}
2016-04-06 11:55:18 -07:00
internal static bool CreateJunction ( string path , string target )
2016-04-06 12:01:07 -07:00
{
// this is a purely Windows specific feature, no feature flag
// used for that reason
2016-04-08 14:54:41 -07:00
if ( Platform . IsWindows )
2016-04-06 12:01:07 -07:00
{
2016-07-29 13:02:49 -07:00
return WinCreateJunction ( path , target );
2016-04-06 12:01:07 -07:00
}
else
{
return false ;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
private static bool WinCreateJunction ( string path , string target )
2016-03-30 14:20:52 -07:00
{
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( path ))
2016-03-30 14:20:52 -07:00
{
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( target ))
2016-03-30 14:20:52 -07:00
{
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 ;
2019-01-06 06:51:20 +05:00
mountPoint . ReparseDataLength = ( ushort )( mountPointBytes . Length + 12 ); // Added space for the header and null endo
2016-03-30 14:20:52 -07:00
mountPoint . SubstituteNameOffset = 0 ;
mountPoint . SubstituteNameLength = ( ushort ) mountPointBytes . Length ;
2017-01-16 13:31:14 -08:00
mountPoint . PrintNameOffset = ( ushort )( mountPointBytes . Length + 2 ); // 2 as unicode null take 2 bytes.
2016-03-30 14:20:52 -07:00
mountPoint . PrintNameLength = 0 ;
2019-01-06 06:51:20 +05:00
mountPoint . PathBuffer = new byte [ 0x3FF0 ]; // Buffer for max size.
2016-03-30 14:20:52 -07:00
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 ;
2019-01-06 06:51:20 +05:00
// OACR warning 62001 about using DeviceIOControl has been disabled.
2016-03-30 14:20:52 -07:00
// 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 )
2016-04-06 12:01:07 -07:00
{
2016-07-22 10:49:45 -07:00
#if UNIX
2016-07-14 15:44:47 -07:00
throw new PlatformNotSupportedException ();
#else
2016-07-29 13:02:49 -07:00
return WinOpenReparsePoint ( reparsePoint , accessMode );
2016-07-14 15:44:47 -07:00
#endif
2016-04-06 12:01:07 -07:00
}
private static SafeFileHandle WinOpenReparsePoint ( string reparsePoint , FileDesiredAccess accessMode )
2016-03-30 14:20:52 -07:00
{
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 ;
}
}
2016-07-29 13:02:49 -07:00
#endregion
2016-03-30 14:20:52 -07:00
}
namespace System.Management.Automation.Internal
{
2017-08-28 15:39:42 -07:00
#if ! UNIX
2016-03-30 14:20:52 -07:00
#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>
2019-01-06 04:40:24 +05:00
/// Provides access to alternate data streams on a file.
2016-03-30 14:20:52 -07:00
/// </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>
2019-01-06 04:40:24 +05:00
/// List all of the streams on a file.
2016-03-30 14:20:52 -07:00
/// </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" ;
2018-12-31 20:44:16 +05:00
if (! string . Equals ( findStreamData . Name , dataStream , StringComparison . OrdinalIgnoreCase ))
2016-03-30 14:20:52 -07:00
{
2018-06-13 00:30:10 +05:00
findStreamData . Name = findStreamData . Name . Replace ( dataStream , string . Empty );
2016-03-30 14:20:52 -07:00
}
AlternateStreamData data = new AlternateStreamData ();
data . Stream = findStreamData . Name ;
data . Length = findStreamData . Length ;
2018-06-13 00:30:10 +05:00
data . FileName = path . Replace ( data . Stream , string . Empty );
2016-03-30 14:20:52 -07:00
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>
2019-01-06 04:40:24 +05:00
/// Creates a file stream on a file.
2016-03-30 14:20:52 -07:00
/// </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 )
{
2018-10-24 06:34:15 +02:00
if (! TryCreateFileStream ( path , streamName , mode , access , share , out var stream ))
{
string errorMessage = StringUtil . Format (
FileSystemProviderStrings . AlternateDataStreamNotFound , streamName , path );
throw new FileNotFoundException ( errorMessage , $"{path}:{streamName}" );
}
2016-03-30 14:20:52 -07:00
2018-10-24 06:34:15 +02:00
return stream ;
}
2016-03-30 14:20:52 -07:00
2018-10-24 06:34:15 +02:00
/// <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>
2018-12-25 21:43:03 +05:00
/// <returns>True if the stream was successfully created, otherwise false.</returns>
2018-10-24 06:34:15 +02:00
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}" ;
2016-03-30 14:20:52 -07:00
SafeFileHandle handle = NativeMethods . CreateFile ( resultPath , access , share , IntPtr . Zero , mode , 0 , IntPtr . Zero );
if ( handle . IsInvalid )
{
2018-10-24 06:34:15 +02:00
stream = null ;
return false ;
2016-03-30 14:20:52 -07:00
}
2018-10-24 06:34:15 +02:00
stream = new FileStream ( handle , access );
return true ;
2016-03-30 14:20:52 -07:00
}
/// <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 ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
string resultPath = path + adjustedStreamName ;
2018-07-01 12:08:29 +05:00
File . Delete ( resultPath );
2016-03-30 14:20:52 -07:00
}
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
}
2017-04-21 16:09:53 -07:00
internal static class NativeMethods
2016-03-30 14:20:52 -07:00
{
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 ,
2016-07-29 13:02:49 -07:00
[In, Out, MarshalAs(UnmanagedType.LPStruct)]
2016-03-30 14:20:52 -07:00
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 ,
2016-07-29 13:02:49 -07:00
[In, Out, MarshalAs(UnmanagedType.LPStruct)]
2016-03-30 14:20:52 -07:00
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
2017-08-28 15:39:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
#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" ;
2016-07-29 13:02:49 -07:00
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 );
2016-03-30 14:20:52 -07:00
private const string PSCopyToSessionHelperDefinitionFormat = @"
param (
[Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")]
[Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"")]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[string] $copyToFilePath,
2016-04-28 13:41:25 -07:00
[Parameter(ParameterSetName=""PSCopyFileToRemoteSession"", Mandatory=$false)]
2016-03-30 14:20:52 -07:00
[Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"")]
[ValidateNotNullOrEmpty()]
[string] $b64Fragment,
[Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")]
[switch] $createFile = $false,
2016-04-28 13:41:25 -07:00
[Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")]
[switch] $emptyFile = $false,
2016-03-30 14:20:52 -07:00
[Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"", Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $streamName,
[Parameter(ParameterSetName=""PSTargetSupportsAlternateStreams"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[string] $supportAltStreamPath,
[Parameter(ParameterSetName=""PSSetFileMetadata"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[string] $metaDataFilePath,
[Parameter(ParameterSetName=""PSSetFileMetadata"", Mandatory=$true)]
[ValidateNotNull()]
[hashtable] $metaDataToSet,
[Parameter(ParameterSetName=""PSRemoteDestinationPathIsFile"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[string] $isFilePath,
2016-04-28 13:41:25 -07:00
[Parameter(ParameterSetName=""PSGetRemotePathInfo"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-04-28 13:41:25 -07:00
[string] $remotePath,
2016-03-30 14:20:52 -07:00
[Parameter(ParameterSetName=""PSCreateDirectoryOnRemoteSession"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[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
)
2017-07-22 05:03:49 +01:00
if (($null -ne $resolvedPath.Drive) -and ($null -ne $resolvedPath.Drive.MaximumSize))
2016-03-30 14:20:52 -07:00
{{
$maxUserSize = $resolvedPath.Drive.MaximumSize
$dirSize = 0
2017-07-22 05:03:49 +01:00
Microsoft.PowerShell.Management\Get-ChildItem -LiteralPath ($resolvedPath.Drive.Name + "":"") -Recurse | ForEach-Object {{
Microsoft.PowerShell.Management\Get-Item -LiteralPath $_.FullName -Stream * | ForEach-Object {{ $dirSize += $_.Length }}
2016-03-30 14:20:52 -07:00
}}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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(
2017-01-16 13:31:14 -08:00
[string] $copyToFilePath,
[string] $b64Fragment,
2016-04-28 13:41:25 -07:00
[switch] $createFile = $false,
[switch] $emptyFile = $false
2016-03-30 14:20:52 -07:00
)
2016-04-28 13:41:25 -07:00
2016-03-30 14:20:52 -07:00
$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
2016-04-28 13:41:25 -07:00
if ($emptyFile)
{{
# Handle the empty file scenario.
$op['BytesWritten'] = 0
return $op
}}
2016-03-30 14:20:52 -07:00
}}
# Resolve path in case it is a PSDrive
$resolvedPath = Microsoft.PowerShell.Management\Resolve-Path -literal $copyToFilePath
2016-04-28 13:41:25 -07:00
# Decode
$fragment = [System.Convert]::FromBase64String($b64Fragment)
2016-03-30 14:20:52 -07:00
# 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
{{
2017-07-22 05:03:49 +01:00
if ($null -ne $wstream)
2016-03-30 14:20:52 -07:00
{{
$wstream.Dispose()
}}
}}
return $op
}}
2016-08-28 05:51:23 +00:00
# Returns a hashtable with the following members:
2016-03-30 14:20:52 -07:00
# BytesWritten - number of bytes written to an alternate file stream
#
function PSCopyFileAlternateStreamToRemoteSession
{{
param (
[string] $copyToFilePath,
[string] $b64Fragment,
[string] $streamName
)
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
$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 (
2017-01-16 13:31:14 -08:00
[string] $metaDataFilePath,
2016-03-30 14:20:52 -07:00
[hashtable] $metaDataToSet
)
$item = Microsoft.PowerShell.Management\get-item $metaDataFilePath -ea SilentlyContinue -Force
if ($item)
{{
# LastWriteTime
if ($metaDataToSet['LastWriteTimeUtc'])
{{
$item.LastWriteTimeUtc = $metaDataToSet['LastWriteTimeUtc']
}}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if ($metaDataToSet['LastWriteTime'])
{{
$item.LastWriteTime = $metaDataToSet['LastWriteTime']
}}
# Attributes
if ($metaDataToSet['Attributes'])
{{
$item.Attributes = $metaDataToSet['Attributes']
}}
}}
}}
2016-04-28 13:41:25 -07:00
# 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
}}
2016-03-30 14:20:52 -07:00
# Returns a hashtable with the following information:
# - IsFileInfotrue bool to keep track if the given destination is a FileInfo type.
function PSRemoteDestinationPathIsFile
{{
2017-01-16 13:31:14 -08:00
param (
2016-03-30 14:20:52 -07:00
[string] $isFilePath
)
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
$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:
2017-01-16 13:31:14 -08:00
# 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.
2016-03-30 14:20:52 -07:00
# 2) If DirectoryPath does not exist, create it. Set DirectoryPath to the created directory path.
function PSCreateDirectoryOnRemoteSession
{{
2017-01-16 13:31:14 -08:00
param (
2016-03-30 14:20:52 -07:00
[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)
{{
2017-01-16 13:31:14 -08:00
""PSCopyFileToRemoteSession""
2016-03-30 14:20:52 -07:00
{{
return PSCopyFileToRemoteSession @params
}}
""PSCopyAlternateStreamToRemoteSession""
{{
return PSCopyFileAlternateStreamToRemoteSession @params
}}
""PSTargetSupportsAlternateStreams""
{{
return PSTargetSupportsAlternateStreams @params
}}
""PSSetFileMetadata""
{{
return PSSetFileMetadata @params
}}
""PSRemoteDestinationPathIsFile""
{{
return PSRemoteDestinationPathIsFile @params
}}
2016-04-28 13:41:25 -07:00
""PSGetRemotePathInfo""
{{
return PSGetRemotePathInfo @params
}}
2016-03-30 14:20:52 -07:00
""PSCreateDirectoryOnRemoteSession""
{{
return PSCreateDirectoryOnRemoteSession @params
}}
}}
" ;
2016-07-29 13:02:49 -07:00
private static string s_PSCopyToSessionHelper = functionToken + PSCopyToSessionHelperName + @"
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
" + s_PSCopyToSessionHelperDefinition + @"
2016-03-30 14:20:52 -07:00
}
" ;
2016-07-29 13:02:49 -07:00
private static Hashtable s_PSCopyToSessionHelperFunction = new Hashtable () {
2016-03-30 14:20:52 -07:00
{ nameToken , PSCopyToSessionHelperName },
2016-07-29 13:02:49 -07:00
{ definitionToken , s_PSCopyToSessionHelperDefinitionRestricted }
2016-03-30 14:20:52 -07:00
};
#endregion
#region PSCopyFromSessionHelper
internal const string PSCopyFromSessionHelperName = @"PSCopyFromSessionHelper" ;
2016-07-29 13:02:49 -07:00
private static string s_PSCopyFromSessionHelperDefinition = StringUtil . Format ( PSCopyFromSessionHelperDefinitionFormat , @"[ValidateNotNullOrEmpty()]" );
private static string s_PSCopyFromSessionHelperDefinitionRestricted = StringUtil . Format ( PSCopyFromSessionHelperDefinitionFormat , @"[ValidateUserDrive()]" );
2016-03-30 14:20:52 -07:00
private const string PSCopyFromSessionHelperDefinitionFormat = @"
param (
[Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[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}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[string] $supportAltStreamPath,
[Parameter(ParameterSetName=""PSGetFileMetadata"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[string] $getMetaFilePath,
[Parameter(ParameterSetName=""PSGetPathItems"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[string] $getPathItems,
[Parameter(ParameterSetName=""PSGetPathDirAndFiles"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[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
}}
}}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
$op
}}
finally
{{
2017-07-22 05:03:49 +01:00
if ($null -ne $rstream)
2016-03-30 14:20:52 -07:00
{{
$rstream.Dispose()
}}
}}
}}
function WriteException
{{
param ($ex)
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
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
2017-01-16 13:31:14 -08:00
$item = Microsoft.PowerShell.Management\Get-Item $resolvedFilePath
2016-03-30 14:20:52 -07:00
if ($item.Attributes.HasFlag([System.IO.FileAttributes]::Hidden))
{{
$isHidden = $true
$item.Attributes = $item.Attributes -band (-bnot ([System.IO.FileAttributes]::Hidden))
}}
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
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
2017-01-16 13:31:14 -08:00
($e.Exception.InnerException -is [System.Security.SecurityException] ) -or
($e.Exception.InnerException -is [System.ArgumentException]) -or
2016-03-30 14:20:52 -07:00
($e.Exception.InnerException -is [System.IO.IOException]))
{{
# Write out the original error since we failed to force the copy
WriteException $unAuthorizedAccessException
}}
else
{{
WriteException $e
}}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
$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.
2017-01-16 13:31:14 -08:00
# Streams - the list of alternate streams
2016-03-30 14:20:52 -07:00
#
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)
}}
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
# 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
}}
2017-01-16 13:31:14 -08:00
## A hashtable is returned in the following format:
2016-03-30 14:20:52 -07:00
## 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
}}
2017-01-16 13:31:14 -08:00
}}
2016-03-30 14:20:52 -07:00
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 + @"
{
2016-07-29 13:02:49 -07:00
" + s_PSCopyFromSessionHelperDefinition + @"
2016-03-30 14:20:52 -07:00
}
" ;
2016-07-29 13:02:49 -07:00
private static Hashtable s_PSCopyFromSessionHelperFunction = new Hashtable () {
2016-03-30 14:20:52 -07:00
{ nameToken , PSCopyFromSessionHelperName },
2016-07-29 13:02:49 -07:00
{ definitionToken , s_PSCopyFromSessionHelperDefinitionRestricted }
2016-03-30 14:20:52 -07:00
};
#endregion
#region PSCopyRemoteUtils
internal const string PSCopyRemoteUtilsName = @"PSCopyRemoteUtils" ;
internal static string PSCopyRemoteUtilsDefinition = StringUtil . Format ( PSCopyRemoteUtilsDefinitionFormat , @"[ValidateNotNullOrEmpty()]" , PSValidatePathFunction );
2016-07-29 13:02:49 -07:00
private static string s_PSCopyRemoteUtilsDefinitionRestricted = StringUtil . Format ( PSCopyRemoteUtilsDefinitionFormat , @"[ValidateUserDrive()]" , PSValidatePathFunction );
2016-03-30 14:20:52 -07:00
private const string PSCopyRemoteUtilsDefinitionFormat = @"
param (
[Parameter(ParameterSetName=""PSRemoteDirectoryExist"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[string] $dirPathExists,
[Parameter(ParameterSetName=""PSValidatePath"", Mandatory=$true)]
{0}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[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) }}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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 = @"
2017-01-16 13:31:14 -08:00
# Return hashtable in the following format:
2016-03-30 14:20:52 -07:00
# 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
}
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
# Get the root path using Get-Item
$item = Microsoft.PowerShell.Management\Get-Item $pathToValidate -ea SilentlyContinue
2017-07-22 05:03:49 +01:00
if (($null -ne $item) -and ($item[0].PSProvider.Name -eq 'FileSystem'))
2016-03-30 14:20:52 -07:00
{
$result['Root'] = SafeGetDriveRoot $item[0].PSDrive
return $result
}
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
# If this fails, try to get them via Get-PSDrive
$fileSystemDrives = @(Microsoft.PowerShell.Management\Get-PSDrive -PSProvider FileSystem -ea SilentlyContinue)
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
# 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 + @"
}
" ;
2016-07-29 13:02:49 -07:00
internal static Hashtable PSCopyRemoteUtilsFunction = new Hashtable () {
2016-03-30 14:20:52 -07:00
{ nameToken , PSCopyRemoteUtilsName },
2016-07-29 13:02:49 -07:00
{ definitionToken , s_PSCopyRemoteUtilsDefinitionRestricted }
2016-03-30 14:20:52 -07:00
};
#endregion
2016-07-29 13:02:49 -07:00
internal static string AllCopyToRemoteScripts = s_PSCopyToSessionHelper + PSCopyRemoteUtils ;
2016-03-30 14:20:52 -07:00
internal static IEnumerable < Hashtable > GetAllCopyToRemoteScriptFunctions ()
{
2016-07-29 13:02:49 -07:00
yield return s_PSCopyToSessionHelperFunction ;
2016-03-30 14:20:52 -07:00
yield return PSCopyRemoteUtilsFunction ;
}
internal static string AllCopyFromRemoteScripts = PSCopyFromSessionHelper + PSCopyRemoteUtils ;
internal static IEnumerable < Hashtable > GetAllCopyFromRemoteScriptFunctions ()
{
2016-07-29 13:02:49 -07:00
yield return s_PSCopyFromSessionHelperFunction ;
2016-03-30 14:20:52 -07:00
yield return PSCopyRemoteUtilsFunction ;
}
}
#endregion
}