7
7
using System . Diagnostics . CodeAnalysis ;
8
8
using System . IO ;
9
9
using System . IO . Compression ;
10
- using System . Linq ;
11
10
using System . Management . Automation ;
11
+ using System . Runtime . InteropServices ;
12
12
13
13
using Microsoft . PowerShell . Archive . Localized ;
14
14
@@ -68,85 +68,105 @@ private enum ParameterSet
68
68
[ Parameter ( ) ]
69
69
public ArchiveFormat ? Format { get ; set ; } = null ;
70
70
71
- // Stores paths from -Path parameter
72
- private List < string > ? _literalPaths ;
71
+ private readonly PathHelper _pathHelper ;
73
72
74
- // Stores paths from -LiteralPath parameter
75
- private List < string > ? _nonliteralPaths ;
73
+ private bool _didCreateNewArchive ;
76
74
77
- private readonly PathHelper _pathHelper ;
75
+ // Stores paths
76
+ private HashSet < string > ? _paths ;
78
77
79
- private FileSystemInfo ? _destinationPathInfo ;
78
+ // This is used so the cmdlet can show all nonexistent paths at once to the user
79
+ private HashSet < string > _nonexistentPaths ;
80
80
81
- private bool _didCreateNewArchive ;
81
+ // Keeps track of duplicate paths so the cmdlet can show them all at once to the user
82
+ private HashSet < string > _duplicatePaths ;
83
+
84
+ // Keeps track of whether any source path is equal to the destination path
85
+ // Since we are already checking for duplicates, only a bool is necessary and not a List or a HashSet
86
+ // Only 1 path could be equal to the destination path after filtering for duplicates
87
+ private bool _isSourcePathEqualToDestinationPath ;
82
88
83
89
public CompressArchiveCommand ( )
84
90
{
85
- _literalPaths = new List < string > ( ) ;
86
- _nonliteralPaths = new List < string > ( ) ;
87
91
_pathHelper = new PathHelper ( this ) ;
88
92
Messages . Culture = new System . Globalization . CultureInfo ( "en-US" ) ;
89
93
_didCreateNewArchive = false ;
90
- _destinationPathInfo = null ;
94
+ _paths = new HashSet < string > ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) ? StringComparer . Ordinal : StringComparer . OrdinalIgnoreCase ) ;
95
+ _nonexistentPaths = new HashSet < string > ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) ? StringComparer . Ordinal : StringComparer . OrdinalIgnoreCase ) ;
96
+ _duplicatePaths = new HashSet < string > ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) ? StringComparer . Ordinal : StringComparer . OrdinalIgnoreCase ) ;
91
97
}
92
98
93
99
protected override void BeginProcessing ( )
94
100
{
95
- _destinationPathInfo = _pathHelper . ResolveToSingleFullyQualifiedPath ( DestinationPath ) ;
96
- DestinationPath = _destinationPathInfo . FullName ;
101
+ // This resolves the path to a fully qualified path and handles provider exceptions
102
+ DestinationPath = _pathHelper . GetUnresolvedPathFromPSProviderPath ( DestinationPath ) ;
97
103
ValidateDestinationPath ( ) ;
98
104
}
99
105
100
106
protected override void ProcessRecord ( )
101
107
{
102
- // Add each path from -Path or -LiteralPath to _nonliteralPaths or _literalPaths because they can get lost when the next item in the pipeline is sent
103
108
if ( ParameterSetName == nameof ( ParameterSet . Path ) )
104
109
{
105
110
Debug . Assert ( Path is not null ) ;
106
- _nonliteralPaths ? . AddRange ( Path ) ;
111
+ foreach ( var path in Path ) {
112
+ var resolvedPaths = _pathHelper . GetResolvedPathFromPSProviderPath ( path , _nonexistentPaths ) ;
113
+ if ( resolvedPaths is not null ) {
114
+ foreach ( var resolvedPath in resolvedPaths ) {
115
+ // Add resolvedPath to _path
116
+ AddPathToPaths ( pathToAdd : resolvedPath ) ;
117
+ }
118
+ }
119
+ }
120
+
107
121
}
108
122
else
109
123
{
110
124
Debug . Assert ( LiteralPath is not null ) ;
111
- _literalPaths ? . AddRange ( LiteralPath ) ;
125
+ foreach ( var path in LiteralPath ) {
126
+ var unresolvedPath = _pathHelper . GetUnresolvedPathFromPSProviderPath ( path , _nonexistentPaths ) ;
127
+ if ( unresolvedPath is not null ) {
128
+ // Add unresolvedPath to _path
129
+ AddPathToPaths ( pathToAdd : unresolvedPath ) ;
130
+ }
131
+ }
112
132
}
113
133
}
114
134
115
135
protected override void EndProcessing ( )
116
136
{
117
- Debug . Assert ( _destinationPathInfo is not null ) ;
118
- Debug . Assert ( _literalPaths is not null ) ;
119
- Debug . Assert ( _nonliteralPaths is not null ) ;
137
+ // If there are non-existent paths, throw a terminating error
138
+ if ( _nonexistentPaths . Count > 0 ) {
139
+ // Get a comma-seperated string containg the non-existent paths
140
+ string commaSeperatedNonExistentPaths = string . Join ( ',' , _nonexistentPaths ) ;
141
+ var errorRecord = ErrorMessages . GetErrorRecord ( ErrorCode . InvalidPath , commaSeperatedNonExistentPaths ) ;
142
+ ThrowTerminatingError ( errorRecord ) ;
143
+ }
120
144
121
- // Get archive entries, validation is performed by PathHelper
122
- // _literalPaths should not be null at this stage, but if it is, prevent a NullReferenceException by doing the following
123
- List < ArchiveAddition > archiveAdditions = _pathHelper . GetArchiveAdditionsForPath ( paths : _literalPaths . ToArray ( ) , literalPath : true ) ;
145
+ // If there are duplicate paths, throw a terminating error
146
+ if ( _duplicatePaths . Count > 0 ) {
147
+ // Get a comma-seperated string containg the non-existent paths
148
+ string commaSeperatedDuplicatePaths = string . Join ( ',' , _nonexistentPaths ) ;
149
+ var errorRecord = ErrorMessages . GetErrorRecord ( ErrorCode . DuplicatePaths , commaSeperatedDuplicatePaths ) ;
150
+ ThrowTerminatingError ( errorRecord ) ;
151
+ }
124
152
125
- // Do the same as above for _nonliteralPaths
126
- List < ArchiveAddition > ? nonliteralArchiveAdditions = _pathHelper . GetArchiveAdditionsForPath ( paths : _nonliteralPaths . ToArray ( ) , literalPath : false ) ;
153
+ // If a source path is the same as the destination path, throw a terminating error
154
+ // We don't want to overwrite the file or directory that we want to add to the archive.
155
+ if ( _isSourcePathEqualToDestinationPath ) {
156
+ var errorCode = ParameterSetName == nameof ( ParameterSet . Path ) ? ErrorCode . SamePathAndDestinationPath : ErrorCode . SameLiteralPathAndDestinationPath ;
157
+ var errorRecord = ErrorMessages . GetErrorRecord ( errorCode ) ;
158
+ ThrowTerminatingError ( errorRecord ) ;
159
+ }
127
160
128
- // Add nonliteralArchiveAdditions to archive additions, so we can keep track of one list only
129
- archiveAdditions . AddRange ( nonliteralArchiveAdditions ) ;
161
+ // Get archive entries
162
+ // If a path causes an exception (e.g., SecurityException), _pathHelper should handle it
163
+ List < ArchiveAddition > archiveAdditions = _pathHelper . GetArchiveAdditions ( _paths ) ;
130
164
131
- // Remove references to _sourcePaths , Path, and LiteralPath to free up memory
165
+ // Remove references to _paths , Path, and LiteralPath to free up memory
132
166
// The user could have supplied a lot of paths, so we should do this
133
167
Path = null ;
134
168
LiteralPath = null ;
135
- _literalPaths = null ;
136
- _nonliteralPaths = null ;
137
- // Remove reference to nonliteralArchiveAdditions since we do not use it any more
138
- nonliteralArchiveAdditions = null ;
139
-
140
- // Throw a terminating error if there is a source path as same as DestinationPath.
141
- // We don't want to overwrite the file or directory that we want to add to the archive.
142
- var additionsWithSamePathAsDestination = archiveAdditions . Where ( addition => PathHelper . ArePathsSame ( addition . FileSystemInfo , _destinationPathInfo ) ) . ToList ( ) ;
143
- if ( additionsWithSamePathAsDestination . Count > 0 )
144
- {
145
- // Since duplicate checking is performed earlier, there must a single ArchiveAddition such that ArchiveAddition.FullPath == DestinationPath
146
- var errorCode = ParameterSetName == nameof ( ParameterSet . Path ) ? ErrorCode . SamePathAndDestinationPath : ErrorCode . SameLiteralPathAndDestinationPath ;
147
- var errorRecord = ErrorMessages . GetErrorRecord ( errorCode , errorItem : additionsWithSamePathAsDestination [ 0 ] . FileSystemInfo . FullName ) ;
148
- ThrowTerminatingError ( errorRecord ) ;
149
- }
169
+ _paths = null ;
150
170
151
171
// Warn the user if there are no items to add for some reason (e.g., no items matched the filter)
152
172
if ( archiveAdditions . Count == 0 )
@@ -161,20 +181,18 @@ protected override void EndProcessing()
161
181
IArchive ? archive = null ;
162
182
try
163
183
{
164
- if ( ShouldProcess ( target : _destinationPathInfo . FullName , action : Messages . Create ) )
184
+ if ( ShouldProcess ( target : DestinationPath , action : Messages . Create ) )
165
185
{
166
186
// If the WriteMode is overwrite, delete the existing archive
167
187
if ( WriteMode == WriteMode . Overwrite )
168
188
{
169
189
DeleteDestinationPathIfExists ( ) ;
170
- _destinationPathInfo = new FileInfo ( _destinationPathInfo . FullName ) ;
171
190
}
172
191
173
192
// Create an archive -- this is where we will switch between different types of archives
174
193
archive = ArchiveFactory . GetArchive ( format : Format ?? ArchiveFormat . Zip , archivePath : DestinationPath , archiveMode : archiveMode , compressionLevel : CompressionLevel ) ;
175
- _didCreateNewArchive = archiveMode = = ArchiveMode . Update ;
194
+ _didCreateNewArchive = archiveMode ! = ArchiveMode . Update ;
176
195
}
177
-
178
196
179
197
long numberOfAdditions = archiveAdditions . Count ;
180
198
long numberOfAddedItems = 0 ;
@@ -212,7 +230,7 @@ protected override void EndProcessing()
212
230
// If -PassThru is specified, write a System.IO.FileInfo object
213
231
if ( PassThru )
214
232
{
215
- WriteObject ( _destinationPathInfo ) ;
233
+ WriteObject ( new FileInfo ( DestinationPath ) ) ;
216
234
}
217
235
}
218
236
@@ -221,7 +239,7 @@ protected override void StopProcessing()
221
239
// If a new output archive was created, delete it (this does not delete an archive if -WriteMode Update is specified)
222
240
if ( _didCreateNewArchive )
223
241
{
224
- _destinationPathInfo ? . Delete ( ) ;
242
+ DeleteDestinationPathIfExists ( ) ;
225
243
}
226
244
}
227
245
@@ -230,13 +248,12 @@ protected override void StopProcessing()
230
248
/// </summary>
231
249
private void ValidateDestinationPath ( )
232
250
{
233
- Debug . Assert ( _destinationPathInfo is not null ) ;
234
251
ErrorCode ? errorCode = null ;
235
252
236
- if ( _destinationPathInfo . Exists )
253
+ if ( System . IO . Path . Exists ( DestinationPath ) )
237
254
{
238
255
// Check if DestinationPath is an existing directory
239
- if ( _destinationPathInfo . Attributes . HasFlag ( FileAttributes . Directory ) )
256
+ if ( Directory . Exists ( DestinationPath ) )
240
257
{
241
258
// Throw an error if DestinationPath exists and the cmdlet is not in Update mode or Overwrite is not specified
242
259
if ( WriteMode == WriteMode . Create )
@@ -249,12 +266,12 @@ private void ValidateDestinationPath()
249
266
errorCode = ErrorCode . ArchiveExistsAsDirectory ;
250
267
}
251
268
// Throw an error if the DestinationPath is the current working directory and the cmdlet is in Overwrite mode
252
- else if ( WriteMode == WriteMode . Overwrite && _destinationPathInfo . FullName == SessionState . Path . CurrentFileSystemLocation . ProviderPath )
269
+ else if ( WriteMode == WriteMode . Overwrite && DestinationPath == SessionState . Path . CurrentFileSystemLocation . ProviderPath )
253
270
{
254
271
errorCode = ErrorCode . CannotOverwriteWorkingDirectory ;
255
272
}
256
273
// Throw an error if the DestinationPath is a directory with at 1 least item and the cmdlet is in Overwrite mode
257
- else if ( WriteMode == WriteMode . Overwrite && _destinationPathInfo is DirectoryInfo directory && directory . GetFileSystemInfos ( ) . Length > 0 )
274
+ else if ( WriteMode == WriteMode . Overwrite && Directory . GetFileSystemEntries ( DestinationPath ) . Length > 0 )
258
275
{
259
276
errorCode = ErrorCode . ArchiveIsNonEmptyDirectory ;
260
277
}
@@ -268,7 +285,7 @@ private void ValidateDestinationPath()
268
285
errorCode = ErrorCode . ArchiveExists ;
269
286
}
270
287
// Throw an error if the cmdlet is in Update mode but the archive is read only
271
- else if ( WriteMode == WriteMode . Update && _destinationPathInfo . Attributes . HasFlag ( FileAttributes . ReadOnly ) )
288
+ else if ( WriteMode == WriteMode . Update && File . GetAttributes ( DestinationPath ) . HasFlag ( FileAttributes . ReadOnly ) )
272
289
{
273
290
errorCode = ErrorCode . ArchiveReadOnly ;
274
291
}
@@ -283,7 +300,7 @@ private void ValidateDestinationPath()
283
300
if ( errorCode is not null )
284
301
{
285
302
// Throw an error -- since we are validating DestinationPath, the problem is with DestinationPath
286
- var errorRecord = ErrorMessages . GetErrorRecord ( errorCode : errorCode . Value , errorItem : _destinationPathInfo . FullName ) ;
303
+ var errorRecord = ErrorMessages . GetErrorRecord ( errorCode : errorCode . Value , errorItem : DestinationPath ) ;
287
304
ThrowTerminatingError ( errorRecord ) ;
288
305
}
289
306
@@ -293,37 +310,39 @@ private void ValidateDestinationPath()
293
310
294
311
private void DeleteDestinationPathIfExists ( )
295
312
{
296
- Debug . Assert ( _destinationPathInfo is not null ) ;
297
313
try
298
314
{
299
315
// No need to ensure DestinationPath has no children when deleting it
300
316
// because ValidateDestinationPath should have already done this
301
- if ( _destinationPathInfo . Exists )
317
+ if ( File . Exists ( DestinationPath ) )
302
318
{
303
- _destinationPathInfo . Delete ( ) ;
319
+ File . Delete ( DestinationPath ) ;
320
+ }
321
+ else if ( Directory . Exists ( DestinationPath ) )
322
+ {
323
+ Directory . Delete ( DestinationPath ) ;
304
324
}
305
325
}
306
326
// Throw a terminating error if an IOException occurs
307
327
catch ( IOException ioException )
308
328
{
309
329
var errorRecord = new ErrorRecord ( ioException , errorId : nameof ( ErrorCode . OverwriteDestinationPathFailed ) ,
310
- errorCategory : ErrorCategory . InvalidOperation , targetObject : _destinationPathInfo . FullName ) ;
330
+ errorCategory : ErrorCategory . InvalidOperation , targetObject : DestinationPath ) ;
311
331
ThrowTerminatingError ( errorRecord ) ;
312
332
}
313
333
// Throw a terminating error if an UnauthorizedAccessException occurs
314
334
catch ( System . UnauthorizedAccessException unauthorizedAccessException )
315
335
{
316
336
var errorRecord = new ErrorRecord ( unauthorizedAccessException , errorId : nameof ( ErrorCode . InsufficientPermissionsToAccessPath ) ,
317
- errorCategory : ErrorCategory . PermissionDenied , targetObject : _destinationPathInfo . FullName ) ;
337
+ errorCategory : ErrorCategory . PermissionDenied , targetObject : DestinationPath ) ;
318
338
ThrowTerminatingError ( errorRecord ) ;
319
339
}
320
340
}
321
341
322
342
private void DetermineArchiveFormat ( )
323
343
{
324
- Debug . Assert ( _destinationPathInfo is not null ) ;
325
344
// Check if cmdlet is able to determine the format of the archive based on the extension of DestinationPath
326
- bool ableToDetermineArchiveFormat = ArchiveFactory . TryGetArchiveFormatFromExtension ( path : _destinationPathInfo . FullName , archiveFormat : out var archiveFormat ) ;
345
+ bool ableToDetermineArchiveFormat = ArchiveFactory . TryGetArchiveFormatFromExtension ( path : DestinationPath , archiveFormat : out var archiveFormat ) ;
327
346
// If the user did not specify which archive format to use, try to determine it automatically
328
347
if ( Format is null )
329
348
{
@@ -334,7 +353,7 @@ private void DetermineArchiveFormat()
334
353
else
335
354
{
336
355
// If the archive format could not be determined, use zip by default and emit a warning
337
- var warningMsg = string . Format ( Messages . ArchiveFormatCouldNotBeDeterminedWarning , _destinationPathInfo . FullName ) ;
356
+ var warningMsg = string . Format ( Messages . ArchiveFormatCouldNotBeDeterminedWarning , DestinationPath ) ;
338
357
WriteWarning ( warningMsg ) ;
339
358
Format = ArchiveFormat . Zip ;
340
359
}
@@ -347,10 +366,21 @@ private void DetermineArchiveFormat()
347
366
{
348
367
if ( archiveFormat is null || archiveFormat . Value != Format . Value )
349
368
{
350
- var warningMsg = string . Format ( Messages . ArchiveExtensionDoesNotMatchArchiveFormatWarning , _destinationPathInfo . FullName ) ;
369
+ var warningMsg = string . Format ( Messages . ArchiveExtensionDoesNotMatchArchiveFormatWarning , DestinationPath ) ;
351
370
WriteWarning ( warningMsg ) ;
352
371
}
353
372
}
354
373
}
374
+
375
+ // Adds a path to _paths variable
376
+ // If the path being added is a duplicate, it adds it _duplicatePaths (if it is not already there)
377
+ // If the path is the same as the destination path, it sets _isSourcePathEqualToDestinationPath to true
378
+ private void AddPathToPaths ( string pathToAdd ) {
379
+ if ( ! _paths . Add ( pathToAdd ) ) {
380
+ _duplicatePaths . Add ( pathToAdd ) ;
381
+ } else if ( ! _isSourcePathEqualToDestinationPath && pathToAdd == DestinationPath ) {
382
+ _isSourcePathEqualToDestinationPath = true ;
383
+ }
384
+ }
355
385
}
356
386
}
0 commit comments