TM-DockerUtility.psm1

using namespace System.Collections.Generic

# Write verbose messages on import
if ((Get-PSCallStack)[1].Arguments -imatch 'Verbose=True') { $PSDefaultParameterValues['*:Verbose'] = $true }

# Ensure we're using the primary write commands from the Microsoft.PowerShell.Utility module.
Set-Alias -Name 'Write-Progress'    -Value 'Microsoft.PowerShell.Utility\Write-Progress'    -Scope Script
Set-Alias -Name 'Write-Debug'       -Value 'Microsoft.PowerShell.Utility\Write-Debug'       -Scope Script
Set-Alias -Name 'Write-Verbose'     -Value 'Microsoft.PowerShell.Utility\Write-Verbose'     -Scope Script
Set-Alias -Name 'Write-Host'        -Value 'Microsoft.PowerShell.Utility\Write-Host'        -Scope Script
Set-Alias -Name 'Write-Information' -Value 'Microsoft.PowerShell.Utility\Write-Information' -Scope Script
Set-Alias -Name 'Write-Warning'     -Value 'Microsoft.PowerShell.Utility\Write-Warning'     -Scope Script
Set-Alias -Name 'Write-Error'       -Value 'Microsoft.PowerShell.Utility\Write-Error'       -Scope Script

if ((Test-ApplicationExistsInPath -ApplicationName 'docker') -eq $false) {
    Write-Verbose 'docker does not exist in the Path. Skipping the import of docker ProfileUtility commands.'
    # Do not export any module commands.
    Export-ModuleMember
    exit
}
# https://github.com/PowerShell/PowerShell/issues/17730#issuecomment-1190678484
$ExportedMembers = [List[string]]::new()


class MountPoint {
<#
    .SYNOPSIS
    Holds the HostPath and ContainerPath for use connecting to a given docker image.
#>

    [string]$HostPath
    [string]$ContainerPath

    MountPoint(
        [string]$hostPath = (Read-Host -Prompt 'Enter the local mount path'),
        [string]$containerPath = (Read-Host -Prompt 'Enter the containers mount path')
    ) {
        $this.HostPath = $hostPath
        $this.ContainerPath = $containerPath
    }

    MountPoint() {
        $this.HostPath = Read-Host -Prompt 'Enter the local mount path'
        $this.ContainerPath = Read-Host -Prompt 'Enter the containers mount path'
    }

    [string]ToString() { return "-v ""$($this.HostPath)`:$($this.ContainerPath)"" " }
}


function New-MountPoint {
<#
    .SYNOPSIS
    Create a new MountPoint object.
 
    .DESCRIPTION
    This function allows the user to create a new MountPoint object from the TM-DockerUtility module.
    MountPoint objects should be used with the Enter-DockerImageWithNewEntryPoint function's MountPoints parameter.
 
    .PARAMETER HostPath
    The path to a directory that exists on the local machine.
 
    .PARAMETER ContainerPath
    The path to create and mount the HostPath to inside the container.
 
    .EXAMPLE
    Enter-DockerImageWithNewEntryPoint -EntryPoint '/bin/bash' -DeleteDebugImage -MountPoints @(
        (New-MountPoint -HostPath 'C:\Path\To\Folder1' -ContainerPath '/workspace/Folder1'),
        (New-MountPoint -HostPath 'C:\Path\To\Folder2' -ContainerPath '/workspace/Folder2')
    )
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [Validation.ValidatePathExists('Folder')]
        [string]$HostPath,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ContainerPath
    )

    return [MountPoint]::New($HostPath, $ContainerPath)
}
$ExportedMembers.Add('New-MountPoint')


function Enter-DockerImageWithNewEntryPoint {
<#
    .SYNOPSIS
    Enters a Docker container image with a new entry point.
 
    .DESCRIPTION
    This function allows you to enter a selected Docker container with a specified entry point.
    This can be useful if your containers default entrypoint is causing errors and the container
    will not stay running long enough for you to exec into it and debug the issue.
 
    .PARAMETER EntryPoint
    The entry point command to use with the debug image.
    If no EntryPoint is provided then the user will be prompt to enter an EntryPoint instead.
 
    .PARAMETER DeleteDebugImage
    A boolean value that indicates whether to delete the debug image after exiting the container.
    The default value is $true.
 
    .PARAMETER MountPoints
    An array of mount points to add to the Docker container.
    Use the New-MountPoint function to create new mountpoint objects.
 
    .EXAMPLE
    Enter-DockerImageWithNewEntryPoint -EntryPoint '/bin/bash' -DeleteDebugImage -MountPoints @(
        (New-MountPoint -HostPath 'C:\Path\To\Folder1' -ContainerPath '/workspace/Folder1'),
        (New-MountPoint -HostPath 'C:\Path\To\Folder2' -ContainerPath '/workspace/Folder2')
    )
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$EntryPoint = (
            Read-Host -Prompt "Provide the entrypoint command to use with '$DebugName'.`nexample: /bin/bash "),

        [Parameter(Mandatory = $false)]
        [bool]$DeleteDebugImage = $true,

        [Parameter(Mandatory = $false)]
        [MountPoint[]]$MountPoints
    )

    if (($env:OS -eq 'Windows_NT') -and ($null -eq (Get-Process -Name 'Docker Desktop' -ErrorAction Ignore))) {
        Write-Warning -Message 'Start Docker Desktop before trying to enter a docker image'
        return
    }

    $DockerPSOut = docker ps -a --format '{{json .}}' | ConvertFrom-Json -ErrorAction Ignore

    # Exit early if there are no containers to copy and enter.
    if ($null -eq $DockerPSOut) {
        Write-Warning 'Docker did not return any output from the ''docker ps -a'' command.'
        return
    }

    Write-Host 'Select a container to enter:'
    $Count = 0
    $Container = $DockerPSOut | ForEach-Object {
        Select-Object -InputObject $_ -Property @{n = '#'; e = { $Count } }, ID, Names, Status, Image
        $Count++
    } | Out-GridView -Title 'Available Containers' -OutputMode Single

    # Make a copy of the selected container image
    $ContainerImage = $Container.Image.Split('/')[-1]
    $DebugName = "debug/$ContainerImage"
    docker commit $Container.ID $DebugName | Write-Host

    # Create start process arguments:
    $dockerMount = ''
    foreach ($MountPoint in $MountPoints) {
        $dockerMount += $MountPoint.ToString()
    }
    $StartDockerRun = @{
        FilePath     = 'docker'
        PassThru     = $true
        Wait         = $true
        NoNewWindow  = $true
        ArgumentList = @('run', '-it', $dockerMount, '--rm', '--entrypoint', $EntryPoint, $DebugName)
    }

    # Run the process in a try block in case something in the container sends a terminating error.
    try {
        $Process = Start-Process @StartDockerRun
        if ($Process.ExitCode -ne 0) {
            Write-Warning "Docker ExitCode is not 0. ExitCode: $($Process.ExitCode)"
        }
    } finally {
        # Cleanup, Don't forget to delete the debug image if requested.
        if ($DeleteDebugImage) {
            $DockerImagesOut = docker images -a --format '{{json .}}' | ConvertFrom-Json -ErrorAction Ignore
            $ImageName = "$DebugName".Split(':')[0]
            $ImageId = $DockerImagesOut | Where-Object { $_.Repository -eq $ImageName } | Select-Object -ExpandProperty ID
            docker.exe image rm $ImageId
        }
    }
}
$ExportedMembers.Add('Enter-DockerImageWithNewEntryPoint')


function Remove-DockerResources {
<#
    .SYNOPSIS
    Removes Docker resources, including unused containers, networks, images, and optionally volumes.
 
    .DESCRIPTION
    This function removes unused Docker resources such as containers, networks, images, and optionally volumes.
    It serves as a cleanup tool to reset the docker environment state back to the default install state.
 
    .PARAMETER IncludeVolumes
    A boolean value that indicates whether to include volumes in the Docker resource removal.
    The default value is $true.
#>

    [Alias('Prune-DockerResources')]
    [CmdletBinding()]
    [OutputType([Void])]
    param (
        [Parameter(Mandatory = $false)]
        [bool]$IncludeVolumes = $true
    )

    [List[string]]$DockerArgs = [List[string]]::new()
    $DockerArgs.AddRange([string[]]@('system', 'prune', '--all', '--force'))
    if ($IncludeVolumes) { $DockerArgs.Add('--volumes') }
    docker @DockerArgs
}
$ExportedMembers.Add('Remove-DockerResources')


Export-ModuleMember -Function $ExportedMembers.ToArray()