File: RemoveDir.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
using Microsoft.Build.Utilities;
 
#nullable disable
 
namespace Microsoft.Build.Tasks
{
    /// <summary>
    /// Remove the specified directories.
    /// </summary>
    public class RemoveDir : TaskExtension, IIncrementalTask
    {
        //-----------------------------------------------------------------------------------
        // Property:  directory to remove
        //-----------------------------------------------------------------------------------
        private ITaskItem[] _directories;
 
        [Required]
        public ITaskItem[] Directories
        {
            get
            {
                ErrorUtilities.VerifyThrowArgumentNull(_directories, nameof(Directories));
                return _directories;
            }
            set => _directories = value;
        }
 
        //-----------------------------------------------------------------------------------
        // Property:  list of directories that were removed from disk
        //-----------------------------------------------------------------------------------
 
        [Output]
        public ITaskItem[] RemovedDirectories { get; set; }
 
        public bool FailIfNotIncremental { get; set; }
 
        //-----------------------------------------------------------------------------------
        // Execute -- this runs the task
        //-----------------------------------------------------------------------------------
        public override bool Execute()
        {
            // Our record of the directories that were removed
            var removedDirectoriesList = new List<ITaskItem>();
 
            foreach (ITaskItem directory in Directories)
            {
                if (string.IsNullOrEmpty(directory.ItemSpec))
                {
                    // Skip any empty ItemSpecs, otherwise RemoveDir will wipe the root of the current drive (!).
                    // https://github.com/dotnet/msbuild/issues/7563
                    Log.LogWarningWithCodeFromResources("RemoveDir.EmptyPath");
                    continue;
                }
 
                if (FileSystems.Default.DirectoryExists(directory.ItemSpec))
                {
                    if (FailIfNotIncremental)
                    {
                        Log.LogErrorFromResources("RemoveDir.Removing", directory.ItemSpec);
                        continue;
                    }
 
                    // Do not log a fake command line as well, as it's superfluous, and also potentially expensive
                    Log.LogMessageFromResources(MessageImportance.Normal, "RemoveDir.Removing", directory.ItemSpec);
 
                    // Try to remove the directory, this will not log unauthorized access errors since
                    // we will attempt to remove read only attributes and try again.
                    bool currentSuccess = RemoveDirectory(directory, false, out bool unauthorizedAccess);
 
                    // The first attempt failed, to we will remove readonly attributes and try again..
                    if (!currentSuccess && unauthorizedAccess)
                    {
                        // If the directory delete operation returns an unauthorized access exception
                        // we need to attempt to remove the readonly attributes and try again.
                        currentSuccess = RemoveReadOnlyAttributeRecursively(new DirectoryInfo(directory.ItemSpec));
                        if (currentSuccess)
                        {
                            // Retry the remove directory operation, this time we want to log any errors
                            currentSuccess = RemoveDirectory(directory, true, out unauthorizedAccess);
                        }
                    }
 
                    // We successfully removed the directory, so add the removed directory to our record
                    if (currentSuccess)
                    {
                        // keep a running list of the directories that were actually removed
                        // note that we include in this list directories that did not exist
                        removedDirectoriesList.Add(new TaskItem(directory));
                    }
                }
                else
                {
                    Log.LogMessageFromResources(MessageImportance.Normal, "RemoveDir.SkippingNonexistentDirectory", directory.ItemSpec);
                    // keep a running list of the directories that were actually removed
                    // note that we include in this list directories that did not exist
                    removedDirectoriesList.Add(new TaskItem(directory));
                }
            }
            // convert the list of deleted files into an array of ITaskItems
            RemovedDirectories = removedDirectoriesList.ToArray();
            return !Log.HasLoggedErrors;
        }
 
        // Core implementation of directory removal
        private bool RemoveDirectory(ITaskItem directory, bool logUnauthorizedError, out bool unauthorizedAccess)
        {
            bool success = true;
 
            unauthorizedAccess = false;
 
            try
            {
                // Try to delete the directory
                Directory.Delete(directory.ItemSpec, true);
            }
            catch (UnauthorizedAccessException e)
            {
                success = false;
                // Log the fact that there was a problem only if we have been asked to.
                if (logUnauthorizedError)
                {
                    Log.LogErrorWithCodeFromResources("RemoveDir.Error", directory, e.Message);
                }
                unauthorizedAccess = true;
            }
            catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
            {
                Log.LogErrorWithCodeFromResources("RemoveDir.Error", directory.ItemSpec, e.Message);
                success = false;
            }
 
            return success;
        }
 
        // recursively remove RO attribs from all files
        private bool RemoveReadOnlyAttributeRecursively(DirectoryInfo directory)
        {
            bool success = true;
            try
            {
                // Remove the ReadOnly attribute from the directory if it is present
                if ((directory.Attributes & FileAttributes.ReadOnly) != 0)
                {
                    FileAttributes faNew = (directory.Attributes & ~FileAttributes.ReadOnly);
                    directory.Attributes = faNew;
                }
 
                // For each file in the directory remove the readonly attribute if it is present
                foreach (FileSystemInfo file in directory.GetFileSystemInfos())
                {
                    if ((file.Attributes & FileAttributes.ReadOnly) != 0)
                    {
                        FileAttributes faNew = (file.Attributes & ~FileAttributes.ReadOnly);
                        file.Attributes = faNew;
                    }
                }
 
                // Recursively call ourselves for sub-directories
                foreach (DirectoryInfo folder in directory.GetDirectories())
                {
                    success = RemoveReadOnlyAttributeRecursively(folder);
                }
            }
            catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
            {
                Log.LogErrorWithCodeFromResources("RemoveDir.Error", directory, e.Message);
                success = false;
            }
 
            return success;
        }
    }
}