WinDirOps.psm1

#Region './Classes/FolderDeletionResult.ps1' -1

class FolderDeletionResult {
    [string]$FolderPath
    [bool]$DeletionSuccess
    [string]$DeletionMessage
    [string]$ComputerName

    # Constructor to initialize the properties
    FolderDeletionResult([string]$folderPath, [bool]$deletionSuccess, [string]$deletionMessage, [string]$computerName) {
        $this.FolderPath = $folderPath
        $this.DeletionSuccess = $deletionSuccess
        $this.DeletionMessage = $deletionMessage
        $this.ComputerName = $computerName
    }
}
#EndRegion './Classes/FolderDeletionResult.ps1' 15
#Region './Private/Invoke-RoboCopyCommand.ps1' -1

<#
.SYNOPSIS
    Executes the Robocopy command to copy or mirror directories.

.DESCRIPTION
    This function runs the Robocopy utility to mirror or copy files from a source directory to a target directory.
    You can pass additional Robocopy parameters to customize the operation, with /mir (mirror) being the default.

.PARAMETER SourceDirectory
    The path to the source directory that will be copied or mirrored to the target directory.

.PARAMETER TargetDirectory
    The path to the target directory where the source directory's contents will be copied or mirrored.

.PARAMETER AdditionalParams
    Optional parameters to customize the Robocopy operation.
    Defaults to /mir (mirror), but you can specify other parameters such as /e, /r:5, /w:10, etc.

.EXAMPLE
    Invoke-RobocopyCommand -SourceDirectory "C:\Source" -TargetDirectory "C:\Target"

    Mirrors the contents of C:\Source to C:\Target using the default /mir parameter.

.EXAMPLE
    Invoke-RobocopyCommand -SourceDirectory "C:\Source" -TargetDirectory "C:\Target" -AdditionalParams @("/e", "/r:2", "/w:5")

    Copies all files from C:\Source to C:\Target, including empty directories (/e), with a retry count of 2 (/r:2) and a wait time of 5 seconds between retries (/w:5).

.NOTES
    RoboCopy exit codes:
    - 0: No files were copied.
    - 1: One or more files were copied successfully.
    - 8: Some files or directories could not be copied (failure).
    - 16: Fatal error (failure).

    The function returns the exit code from Robocopy.

.LINK
    https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy

#>

function Invoke-RobocopyCommand
{
    param (
        [Parameter(Mandatory = $true)]
        [string]$SourceDirectory,

        [Parameter(Mandatory = $true)]
        [string]$TargetDirectory,

        [string[]]$AdditionalParams = @()  # Default to /mir
    )

    # Build the robocopy command with parameters
    $robocopyArgs = @($SourceDirectory, $TargetDirectory) + $AdditionalParams

    # Call robocopy directly
    $result = robocopy @robocopyArgs

    return $result
}
#EndRegion './Private/Invoke-RoboCopyCommand.ps1' 62
#Region './Private/New-FolderDeletionResult.ps1' -1

<#
.SYNOPSIS
    Creates a new FolderDeletionResult object.

.DESCRIPTION
    The `New-FolderDeletionResult` function creates and returns a new `FolderDeletionResult` object,
    which contains information about the deletion of a directory, including the directory path,
    whether the deletion was successful, a message about the result, and the computer name
    on which the deletion took place.

.PARAMETER Directory
    The path of the directory that was being deleted.

.PARAMETER DeletionSuccess
    A boolean value indicating whether the deletion was successful.
    `$true` if the deletion was successful, `$false` otherwise.

.PARAMETER DeletionMessage
    A message describing the result of the deletion process.

.PARAMETER ComputerName
    The name of the computer where the directory deletion occurred.

.EXAMPLE
    $result = New-FolderDeletionResult -Directory "C:\Temp\OldData" -DeletionSuccess $true -DeletionMessage "Successfully deleted." -ComputerName "LocalHost"

    This example creates a `FolderDeletionResult` object with information about the successful deletion
    of the "C:\Temp\OldData" directory on the local computer "LocalHost".

.EXAMPLE
    $result = New-FolderDeletionResult -Directory "D:\RemoteData" -DeletionSuccess $false -DeletionMessage "Failed to delete." -ComputerName "RemotePC"

    This example creates a `FolderDeletionResult` object with information about the failed deletion
    of the "D:\RemoteData" directory on the remote computer "RemotePC".

.NOTES
    This function is used to encapsulate the result of a directory deletion operation,
    providing a structured way to handle and return the outcome of such operations.
#>


function New-FolderDeletionResult
{
    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseShouldProcessForStateChangingFunctions", "")]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Directory,

        [Parameter(Mandatory = $true)]
        [bool]$DeletionSuccess,

        [Parameter(Mandatory = $true)]
        [string]$DeletionMessage,

        [Parameter(Mandatory = $true)]
        [string]$ComputerName
    )

    return [FolderDeletionResult]::new(
        $Directory,
        $DeletionSuccess,
        $DeletionMessage,
        $ComputerName
    )
}
#EndRegion './Private/New-FolderDeletionResult.ps1' 65
#Region './Public/Clear-Directory.ps1' -1

<#
.SYNOPSIS
    Deletes a specified directory on the local or remote computer.

.DESCRIPTION
    This function attempts to delete an entire directory and its contents either on the local or a remote computer.
    It determines whether the operation is local or remote using the `ComputerName` parameter.
    Internally, it delegates to `Remove-DirectoryByType` to handle the deletion, making this a high-level function
    for directory deletion on any machine.

.PARAMETER Directory
    The path of the directory to be deleted.

.PARAMETER ComputerName
    The name of the computer where the directory resides. Defaults to the local computer.

.EXAMPLE
    Clear-Directory -Directory "C:\Temp\Logs"

    This example deletes the "C:\Temp\Logs" directory on the local machine.

.EXAMPLE
    Clear-Directory -Directory "D:\OldFiles" -ComputerName "RemotePC"

    This example deletes the "D:\OldFiles" directory on a remote computer named "RemotePC".

.NOTES
    This function determines whether the operation is local or remote and uses the appropriate
    function (either `Remove-LocalDirectory` or `Remove-RemoteDirectory`) to perform the deletion.
    It supports `-WhatIf` and `-Confirm` for previewing or confirming the operation.
#>


function Clear-Directory
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [string]$Directory,

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$ComputerName = $env:COMPUTERNAME  # Default to local computer
    )

    process
    {
        if ($PSCmdlet.ShouldProcess($Directory, "Delete directory on $ComputerName"))
        {
            $result = Remove-DirectoryByType -Directory $Directory -ComputerName $ComputerName -Confirm:$false
            Write-Information -MessageData $result.DeletionMessage -Tags "DeleteOperation"
            return $result
        }
    }
}
#EndRegion './Public/Clear-Directory.ps1' 54
#Region './Public/Clear-DirectoryContents.ps1' -1

<#
.SYNOPSIS
    Clears only the contents of a directory without deleting the directory itself.

.DESCRIPTION
    This function clears the contents of a directory by mirroring it with an empty temporary directory.
    It does not delete the directory itself but removes all files and subfolders within the specified directory.
    It uses `Invoke-MirRoboCopy` to sync an empty directory with the target directory, effectively clearing it.

.PARAMETER Directory
    The path of the directory whose contents will be cleared.

.EXAMPLE
    Clear-DirectoryContents -Directory "C:\Temp\OldData"

    Clears the contents in the "C:\Temp\OldData" directory but does not remove the directory itself.

.NOTES
    This function is for clearing the contents of a directory while preserving the directory itself.
    To delete both the directory and its contents, use `Clear-Directory` or `Remove-DirectoryByType`.
#>

function Clear-DirectoryContents
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [OutputType([System.Boolean])]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Directory
    )

    try
    {
        # Create a temporary empty directory
        $emptyDir = [System.IO.Path]::GetTempPath() + [System.Guid]::NewGuid().ToString()
        mkdir $emptyDir | Out-Null

        if ($PSCmdlet.ShouldProcess($Directory, "Clear directory contents"))
        {
            # Use Invoke-MirRoboCopy to fast-delete the directory contents by syncing an empty directory
            $syncSuccess = Invoke-MirRoboCopy -SourceDirectory $emptyDir -TargetDirectory $Directory -confirm:$false

            if ($syncSuccess)
            {
                # Remove the target directory after clearing its contents
                Remove-Item -Path $Directory -Recurse -Force -ErrorAction SilentlyContinue
                Remove-Item -Path $emptyDir -Recurse -Force -ErrorAction SilentlyContinue

                return $true
            }
            else
            {
                return $false
            }
        }
    }
    catch
    {
        Write-Error "Failed to clear directory contents for $Directory. Error: $_"
        return $false
    }
}
#EndRegion './Public/Clear-DirectoryContents.ps1' 63
#Region './Public/Invoke-MirRoboCopy.ps1' -1

<#
.SYNOPSIS
    Mirrors the contents of a source directory to a target directory using RoboCopy.

.DESCRIPTION
    This function mirrors the contents of a source directory to a target directory using the RoboCopy utility.
    It ensures that the contents of the target directory exactly match the source directory by syncing them.

.PARAMETER SourceDirectory
    The path of the source directory whose contents will be mirrored to the target directory.

.PARAMETER TargetDirectory
    The path of the target directory where the source directory contents will be mirrored.

.EXAMPLE
    Invoke-MirRoboCopy -SourceDirectory "C:\SourceFolder" -TargetDirectory "C:\TargetFolder"

    Mirrors the contents of "C:\SourceFolder" to "C:\TargetFolder".

.NOTES
    This function is a low-level utility used by other functions like `Clear-DirectoryContents`
    to handle the mirroring operation.

    RoboCopy exit codes:
    0 = No files were copied
    1 = One or more files were copied successfully (even if some files were skipped)
    8 = Some files or directories could not be copied (failure)
    16 = Fatal error (failure)

    Exit codes 8 and higher are considered failures in this function.
#>

function Invoke-MirRoboCopy
{
    [OutputType([System.Boolean])]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true)]
        [string]$SourceDirectory,

        [Parameter(Mandatory = $true)]
        [string]$TargetDirectory
    )

    try
    {

        # Validate that the source directory exists
        if (-not (Test-Path -Path $SourceDirectory -PathType Container))
        {
            throw "Source directory '$SourceDirectory' does not exist."
        }

        # Validate that the target directory exists
        if (-not (Test-Path -Path $TargetDirectory -PathType Container))
        {
            throw "Target directory '$TargetDirectory' does not exist."
        }

        if ($PSCmdlet.ShouldProcess($TargetDirectory, "Mirror directory from '$SourceDirectory'"))
        {
            # Use robocopy to mirror the source directory to the target directory
            $result = Invoke-RoboCopyCommand -SourceDirectory $SourceDirectory -TargetDirectory $TargetDirectory  -AdditionalParams '/mir'

            # Check robocopy result
            if ($result -ge 8)
            {
                throw "RoboCopy encountered a failure with code $result."
            }

            return $true
        }
    }
    catch
    {
        Write-Error "RoboCopy failed from '$SourceDirectory' to '$TargetDirectory'. Error: $_"
        return $false
    }
}
#EndRegion './Public/Invoke-MirRoboCopy.ps1' 79
#Region './Public/Remove-DirectoryByType.ps1' -1

<#
.SYNOPSIS
    Deletes a directory either locally or remotely based on the computer name provided.

.DESCRIPTION
    This function deletes a directory either on the local machine or a remote machine depending on
    the computer name provided. It determines whether the operation is local or remote and calls the
    appropriate function (either `Remove-LocalDirectory` or `Remove-RemoteDirectory`).
    It supports `-WhatIf` and `-Confirm` to allow users to preview or confirm the deletion before it is performed.

.PARAMETER Directory
    The path of the directory to be deleted.

.PARAMETER ComputerName
    The name of the computer where the directory resides. Defaults to the local computer.

.EXAMPLE
    Remove-DirectoryByType -Directory "C:\Temp\Logs"

    Deletes the directory "C:\Temp\Logs" from the local machine.

.NOTES
    This function determines whether the directory is on the local or remote computer and uses
    the appropriate method to delete it.
#>


function Remove-DirectoryByType
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Directory,

        [Parameter(Mandatory = $false)]
        [string]$ComputerName = $env:COMPUTERNAME
    )

    $isLocal = $ComputerName -eq $env:COMPUTERNAME

    if ($PSCmdlet.ShouldProcess("$ComputerName': $Directory", "Delete directory by type"))
    {
        if ($isLocal)
        {
            return Remove-LocalDirectory -Directory $Directory -confirm:$false
        }
        else
        {
            return Remove-RemoteDirectory -Directory $Directory -ComputerName $ComputerName -confirm:$false
        }
    }
}
#EndRegion './Public/Remove-DirectoryByType.ps1' 52
#Region './Public/Remove-LocalDirectory.ps1' -1

<#
.SYNOPSIS
    Deletes a local directory and its contents.

.DESCRIPTION
    This function deletes a specified directory on the local computer.
    It first clears the contents of the directory and then removes the directory itself.

.PARAMETER Directory
    The path of the local directory to be deleted.

.EXAMPLE
    Remove-LocalDirectory -Directory "C:\Temp\OldData"

    Deletes the "C:\Temp\OldData" directory and its contents on the local machine.

.NOTES
    This function works only on local directories. For remote directories, use `Remove-RemoteDirectory`.
#>


function Remove-LocalDirectory
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Directory
    )

    try
    {
        if ($PSCmdlet.ShouldProcess($Directory, "Delete local directory"))
        {
            $deletionInfo = @{
                Directory    = $Directory
                ComputerName = $env:COMPUTERNAME
            }

            if (Clear-DirectoryContents -Directory $Directory -Confirm:$false)
            {
                $deletionInfo.DeletionSuccess = $true
                $deletionInfo.DeletionMessage = "Successfully deleted local directory '$Directory'."
            }
            else
            {
                $deletionInfo.DeletionSuccess = $false
                $deletionInfo.DeletionMessage = "Failed to delete local directory '$Directory'."
            }

            return New-FolderDeletionResult @deletionInfo
        }
    }
    catch
    {
        $deletionInfo = @{
            Directory       = $Directory
            DeletionSuccess = $false
            DeletionMessage = "Error occurred while deleting local directory '$Directory'. $_"
            ComputerName    = $env:COMPUTERNAME
        }
        return New-FolderDeletionResult @deletionInfo
    }
}
#EndRegion './Public/Remove-LocalDirectory.ps1' 63
#Region './Public/Remove-RemoteDirectory.ps1' -1

<#
.SYNOPSIS
    Deletes a remote directory and its contents.

.DESCRIPTION
    This function deletes a directory and its contents on a remote computer.
    It sends a script block to the remote computer to clear the directory contents using `Clear-DirectoryContents`,
    then deletes the directory itself.

.PARAMETER Directory
    The path of the remote directory to be deleted.

.PARAMETER ComputerName
    The name of the remote computer where the directory is located.

.EXAMPLE
    Remove-RemoteDirectory -Directory "D:\OldFiles" -ComputerName "RemotePC"

    Deletes the "D:\OldFiles" directory and its contents on the remote computer "RemotePC".

.NOTES
    This function is specifically for remote directories.
    Use `Remove-LocalDirectory` for local directory deletions.
#>


function Remove-RemoteDirectory
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Directory,

        [Parameter(Mandatory = $true)]
        [string]$ComputerName
    )

    # Check if the remote computer is online
    if (-not (Test-ComputerPing -ComputerName $ComputerName))
    {
        $deletionInfo = @{
            Directory       = $Directory
            DeletionSuccess = $false
            DeletionMessage = "The remote computer '$ComputerName' is offline or unreachable."
            ComputerName    = $ComputerName
        }
        return New-FolderDeletionResult @deletionInfo
    }

    # Get the script blocks for Clear-DirectoryContents and Invoke-MirRoboCopy functions
    $clearDirectoryFunction = Get-FunctionScriptBlock -FunctionName 'Clear-DirectoryContents'
    $invokeMirRoboCopyFunction = Get-FunctionScriptBlock -FunctionName 'Invoke-MirRoboCopy'

    if (-not $clearDirectoryFunction -or -not $invokeMirRoboCopyFunction)
    {
        Write-Error "Unable to retrieve required functions."
        return
    }

    $scriptBlock = {
        param ($remoteDirectory, $clearFunction, $roboCopyFunction)

        # Define the functions from the passed strings
        & $clearFunction
        & $roboCopyFunction

        # Call the Clear-DirectoryContents function
        if (Clear-DirectoryContents -Directory $remoteDirectory -Confirm:$false)
        {
            return @{
                Directory    = $remoteDirectory
                Success      = $true
                Message      = "Successfully deleted remote directory '$remoteDirectory'."
                ComputerName = $env:COMPUTERNAME
            }
        }
        else
        {
            return @{
                Directory    = $remoteDirectory
                Success      = $false
                Message      = "Failed to delete remote directory '$remoteDirectory'."
                ComputerName = $env:COMPUTERNAME
            }
        }
    }

    try
    {
        if ($PSCmdlet.ShouldProcess("$ComputerName`: $Directory", "Delete remote directory"))
        {
            # Execute the script block on the remote machine
            $rawResult = Invoke-Command -ComputerName $ComputerName -ScriptBlock $scriptBlock -ArgumentList $Directory, $clearDirectoryFunction, $invokeMirRoboCopyFunction

            $deletionInfo = @{
                Directory       = $rawResult.Directory
                DeletionSuccess = $rawResult.Success
                DeletionMessage = $rawResult.Message
                ComputerName    = $rawResult.ComputerName
            }
            return New-FolderDeletionResult @deletionInfo
        }
    }
    catch
    {
        $deletionInfo = @{
            Directory       = $Directory
            DeletionSuccess = $false
            DeletionMessage = "Error occurred while deleting remote directory '$Directory'. $_"
            ComputerName    = $ComputerName
        }
        return New-FolderDeletionResult @deletionInfo
    }
}
#EndRegion './Public/Remove-RemoteDirectory.ps1' 114