DockerHelpers.psm1

Write-Verbose 'Importing from [C:\MyProjects\DockerHelpers\DockerHelpers\private]'
# .\DockerHelpers\private\ConvertFrom-Docker.ps1
<#
.SYNOPSIS

Converts from docker output to objects
.DESCRIPTION

Converts from docker tabular output to objects that can be worked with in a familiar way in PowerShell

.EXAMPLE
Get the running containers and

docker ps -a --no-trunc | ConvertFrom-Docker | ft

.NOTES
Original source: https://github.com/samneirinck/posh-docker/blob/3f8645196209ff7fd7090bcf1176307315b1ab30/posh-docker/posh-docker.psm1#L259-L284
Copywrite/licence: https://github.com/samneirinck/posh-docker/blob/master/LICENSE

#>

function ConvertFrom-Docker{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True,
        ValueFromPipeline=$True)]
        [object[]]$items
    )
    
    begin{
        $positions = $null;
    }
    process {
        foreach ($item in $items)
        {
            if($null -eq $positions) {
                # header row => determine column positions
                $positions  = GetColumnInfo -headerRow $item
            } else {
                # data row => output!
                ParseRow -row $item -columnInfo $positions
            }
        }
    }
    end {
    }
}
# .\DockerHelpers\private\GetColumnInfo.ps1
function GetColumnInfo($headerRow){
    $lastIndex = 0
    $i = 0
    while ($i -lt $headerRow.Length){
        $i = GetHeaderBreak $headerRow $lastIndex
        if ($i -lt 0){
            $name = $headerRow.Substring($lastIndex)
            New-Object PSObject -Property @{ HeaderName = $name; Name = PascalName $name; Start=$lastIndex; End=-1}
            break
        } else {
            $name = $headerRow.Substring($lastIndex, $i-$lastIndex)
            $temp = $lastIndex
            $lastIndex = GetHeaderNonBreak $headerRow $i
            New-Object PSObject -Property @{ HeaderName = $name; Name = PascalName $name; Start=$temp; End=$lastIndex}
       }
    }
}
# .\DockerHelpers\private\GetHeaderBreak.ps1
function GetHeaderBreak($headerRow, $startPoint=0){
    $i = $startPoint
    while( $i + 1  -lt $headerRow.Length)
    {
        if ($headerRow[$i] -eq ' ' -and $headerRow[$i+1] -eq ' '){
            return $i
            break
        }
        $i += 1
    }
    return -1
}
# .\DockerHelpers\private\GetHeaderNonBreak.ps1
function GetHeaderNonBreak($headerRow, $startPoint=0){
    $i = $startPoint
    while( $i + 1  -lt $headerRow.Length)
    {
        if ($headerRow[$i] -ne ' '){
            return $i
            break
        }
        $i += 1
    }
    return -1
}
# .\DockerHelpers\private\ParseRow.ps1
function ParseRow($row, $columnInfo) {
    $values = @{}
    $columnInfo | ForEach-Object {
        if ($_.End -lt 0) {
            $len = $row.Length - $_.Start
        } else {
            $len = $_.End - $_.Start
        }
        $values[$_.Name] = $row.SubString($_.Start, $len).Trim()
    }
    New-Object PSObject -Property $values
}
# .\DockerHelpers\private\PascalName.ps1
function PascalName($name){
    $parts = $name.Split(" ")
    for($i = 0 ; $i -lt $parts.Length ; $i++){
        $parts[$i] = [char]::ToUpper($parts[$i][0]) + $parts[$i].SubString(1).ToLower();
    }
    $parts -join ""
}
Write-Verbose 'Importing from [C:\MyProjects\DockerHelpers\DockerHelpers\public]'
# .\DockerHelpers\public\Get-DockerContainer.ps1
function Get-DockerContainer
{
    <#
    .SYNOPSIS
    Return powershell objects describing container(s) on the docker host
    
    .DESCRIPTION
    Return powershell objects describing container(s) on the docker host
    
    .PARAMETER Name
    Filter container to return based on name
    
    .PARAMETER All
    Include stopped containers?
    
    .PARAMETER Inspect
    Return information about the container using `docker inspect`?
    
    .EXAMPLE
    Get-DockerContainer

    Description
    -----------
    Return running containers; see `docker container ls`

    .EXAMPLE
    Get-DockerContainer -All -Inspect

    Description
    -----------
    Return verbose information for both running and stopped containers;
    see `docker container ls` and `docker container inspect`

    .EXAMPLE
    Get-DockerContainer my-container, my-container2

    Description
    -----------
    Return multiple containers by exact name match

    .EXAMPLE
    Get-DockerContainer 'my-*' -Inspect

    Description
    -----------
    Return verbose information for containers whose name matches a wildcard search

    .EXAMPLE
    Get-DockerContainer 'web*' -Inspect | select -PV container |
        ForEach-Object { $_.Mounts.Source } |
        ForEach-Object { [PsCustomObject]@{ Name = $container.Name; MountPath = $_ } }

    Name MountPath
    ---- ---------
    web-spa C:\ProgramData\Docker\volumes\web-spa-iis-logs\_data
    web-spa C:\ProgramData\Docker\volumes\web-spa-app-logs\_data
    web-tokensvr C:\ProgramData\Docker\volumes\web-tokensvr-app-logs\_data

    Description
    -----------
    Return the local path(s) for volumes mounted into the containers whose name matches
    the wildcard search 'web*'
    
    .NOTES
    Alias 'gdc'

    #>

    [CmdletBinding(DefaultParameterSetName = 'List')]
    [OutputType('System.Management.Automation.PSCustomObject')]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Name', Position = 0)]
        [SupportsWildcards()]
        [string[]]$Name,

        [Parameter(ParameterSetName = 'List')]
        [switch] $All,

        [switch] $Inspect
    )
    
    process
    {
        $candidateNames = if ($All -or $PSCmdlet.ParameterSetName -eq 'Name')
        {
            docker container ls -a --format '{{.Names}}'
        }
        else
        {
            docker container ls --format '{{.Names}}'
        }

        $matchedNames = switch ($PSCmdlet.ParameterSetName)
        {
            'List' 
            { 
                $candidateNames
            }
            'Name' 
            { 
                $Name | ForEach-Object {
                    $currentName = $_
                    $criteria = if ($currentName -match '\*')
                    {
                        { $_ -like $currentName }
                    }
                    else
                    {
                        { $_ -eq $currentName }
                    }
                    $candidateNames | Where-Object $criteria
                } | Select-Object -Unique
            }
            Default 
            {
                throw "ParameterSet '$PSCmdlet.ParameterSetName' not implemented"
            }
        }
        $containers = if ($Inspect)
        {
            $matchedNames |
                ForEach-Object { [PsCustomObject](docker container inspect $_ | ConvertFrom-Json) } |
                Select-Object -ExcludeProperty Name -Property @{n = 'Name'; e = { $_.Name.TrimStart('/')}}, *
        }
        else
        {
            docker container ls -a | ConvertFrom-Docker |
                Where-Object { $_.Names -in $matchedNames } |
                Select-Object -ExcludeProperty Names, Command -Property @{n = 'Name'; e = { $_.Names}}, *
        }
        $containers
    }
}

Set-Alias -Name gdc -Value Get-DockerContainer
Export-ModuleMember -Alias gdc
# .\DockerHelpers\public\Get-DockerContainerIP.ps1
function Get-DockerContainerIP
{
    <#
    .SYNOPSIS
    Return the IP address assigned to a container
    
    .DESCRIPTION
    Return the IP address assigned to a container
    
    .PARAMETER Name
    Filter container based on name
    
    .PARAMETER All
    Include stopped containers?
    
    .PARAMETER InputObject
    The container object whose IP address to return
    
    .EXAMPLE
    Get-DockerContainerIP

    Name IPAddress
    ---- ---------
    web-spa 172.24.221.65
    web-tokensvr 172.24.221.66

    Description
    -----------
    Return IP address of all running containers; see `docker container ls`
    and `docker container inspect -f`

    .EXAMPLE
    Show-DockerContainerGridView -PassThru | Get-DockerContainerIP
    # or
    sdc -PassThru | gdip

    Description
    -----------
    Return IP address for container(s) selected interactively from a grid
    
    .NOTES
    Alias 'gdip'

    #>

    [CmdletBinding(DefaultParameterSetName = 'List')]
    [OutputType('System.Management.Automation.PSCustomObject')]
    param (
        [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0)]
        [SupportsWildcards()]
        [string[]]$Name,

        [Parameter(ParameterSetName = 'List')]
        [switch] $All,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Container')]
        [PSCustomObject]$InputObject
    )
    
    process
    {
        $containers = switch ($PSCmdlet.ParameterSetName)
        {
            'Name' { Get-DockerContainer -Name $Name -Inspect }
            'List' { Get-DockerContainer -Inspect }
            'Container'
            {
                if ($InputObject.PsObject.Properties.Name -notcontains 'NetworkSettings')
                {
                    Get-DockerContainer -Name ($InputObject.Name) -Inspect
                }
                else
                {
                    $InputObject
                }
            }
            Default { throw "ParameterSet '$PSCmdlet.ParameterSetName' not implemented"}
        }

        $containers | Select-Object -PV container |
            Select-Object -Exp NetworkSettings | 
            Select-Object -Exp Networks | 
            Select-Object -Exp * |
            Select-Object @{n = 'Name'; e = {$container.Name}}, IPAddress
    }
}

Set-Alias -Name gdip -Value Get-DockerContainerIP
Export-ModuleMember -Alias gdip
# .\DockerHelpers\public\Get-DockerVolume.ps1
function Get-DockerVolume
{
    <#
    .SYNOPSIS
    Return powershell object describing docker volume(s)
    
    .DESCRIPTION
    Return powershell object describing docker volume(s)
    
    .PARAMETER Name
    Filter volume to return based on name
    
    .PARAMETER ContainerName
    Filter volume to those associated with the supplied container
    
    .PARAMETER Container
    Filter volume to those associated with the supplied container
    
    .EXAMPLE
    Get-DockerVolume

    Description
    -----------
    Return all volumes; see `docker volume ls` and `docker volume inspect`
    
    .EXAMPLE
    Get-DockerVolume my-vol1, my-vol2

    Description
    -----------
    Return multiple volumes by exact name match

    .EXAMPLE
    Get-DockerVolume 'my-*'

    Description
    -----------
    Return volumes whose name matches a wildcard search

    .EXAMPLE
    Show-DockerContainerGridView -PassThru | Get-DockerVolume

    Description
    -----------
    Return volumes associated by container(s) selected interactively from a grid
    
    .NOTES
    Alias 'gdv'

    #>

    [CmdletBinding(DefaultParameterSetName = 'List')]
    [OutputType('System.Management.Automation.PSCustomObject')]
    param (
        [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0)]
        [SupportsWildcards()]
        [string[]]$Name,
        
        [Parameter(Mandatory, ParameterSetName = 'ContainerName')]
        [SupportsWildcards()]
        [string]$ContainerName,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Container')]
        [PSCustomObject]$Container
    )
    
    process
    {
        $selectVolumeName = { $_ | Select-Object -Exp Mounts | Select-Object -Exp Name }
        $getAllVolumeNames = { docker volume ls --format '{{.Name}}' }
        $volumes = switch ($PSCmdlet.ParameterSetName)
        {
            'List'
            { 
                & $getAllVolumeNames
            }
            'Name'
            {
                $allNames = & $getAllVolumeNames
                $Name | ForEach-Object {
                    $currentName = $_
                    $criteria = if ($currentName -match '\*')
                    {
                        { $_ -like $currentName }
                    }
                    else
                    {
                        { $_ -eq $currentName }
                    }
                    $allNames | Where-Object $criteria
                } | Select-Object -Unique
            }
            'ContainerName'
            {
                Get-DockerContainer -Name $ContainerName -Inspect | ForEach-Object $selectVolumeName
            }
            'Container'
            {
                $Container = if ($Container.PsObject.Properties.Name -notcontains 'Mounts')
                {
                    Get-DockerContainer -Name ($Container.Name) -Inspect
                }
                else
                {
                    $Container
                }
                $Container | ForEach-Object $selectVolumeName
            }
            Default
            {
                throw "ParameterSet '$PSCmdlet.ParameterSetName' not implemented"
            }
        }
        $volumes | ForEach-Object { [PsCustomObject](docker volume inspect $_ | ConvertFrom-Json) }
    }
}

Set-Alias -Name gdv -Value Get-DockerVolume
Export-ModuleMember -Alias gdv
# .\DockerHelpers\public\Show-DockerContainerGridView.ps1
function Show-DockerContainerGridView
{
    <#
    .SYNOPSIS
    Show docker containers in a grid; optionally allow items in grid to be selected
    for input to other commands
    
    .DESCRIPTION
    Show docker containers in a grid; optionally allow items in grid to be selected
    for input to other commands
    
    .PARAMETER InputObject
    The docker containers to show in grid

    .PARAMETER All
    Include stopped containers?
    
    .PARAMETER Inspect
    Return information about the container using `docker inspect`?
    Only relevant when -PassThru supplied
    
    .PARAMETER Force
    Show grid even when there is only one container? Only relevant when -PassThru
    supplied
    
    .PARAMETER PassThru
    Pass items selected in grid down the pipeline as input to other commands?
    
    .EXAMPLE
    Show-DockerContainerGridView -All

    Description
    -----------
    Include both running and stopped containers; see `docker container ls`

    .EXAMPLE
    Show-DockerContainerGridView -PassThru -Force | ForEach-Object { docker container rm $_.Name }
    # or
    sdc -PassThrus -Force | % { docker rm $_.Name }

    Description
    -----------
    Show running containers for selection, even if there is only one; run the native docker
    remove command on any container selected from grid

    .EXAMPLE
    Get-DockerContainer 'mycompose_*' | Show-DockerContainerGridView -PassThru | % { docker rm $_.Name }

    Description
    -----------
    Show containers whose name matches the wildcard search to allow user to select which ones
    to remove
    
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = 'List')]
    [OutputType('System.Management.Automation.PSCustomObject')]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Input')]
        [PSCustomObject[]] $InputObject,

        [Parameter(ParameterSetName = 'List')]
        [switch] $All,

        [switch] $Inspect,

        [switch] $Force,

        [switch] $PassThru
    )
    
    begin
    {
        $items = @()
    }
    
    process
    {
        $items += if ($InputObject) 
        {
            $InputObject
        }
        else
        {
            Get-DockerContainer -All:$All
        }
    }

    end
    {
        $title = 'docker container(s)'
        if (!$All)
        {
            $title = "RUNNING $title"
        }
        if ($PassThru)
        {
            $title = "Select $title; tip: hold down CTRL button for multi-select"
        }

        $outputMode = if ($PassThru)
        {
            'Multiple'
        }
        else
        {
            'None'
        }

        $selected = if ($PassThru -and !$Force -and $items.Count -eq 1)
        {
            $items
        }
        else
        {
            $items |
                Sort-Object Name -Unique | Sort-Object Image, Name |
                Out-GridView -Title $title -OutputMode $outputMode
        }

        if ($PassThru)
        {
            $selected | Select-Object -Exp Name | Get-DockerContainer -Inspect:$Inspect
        }   
    }
}

Set-Alias -Name sdc -Value Show-DockerContainerGridView
Export-ModuleMember -Alias sdc
# .\DockerHelpers\public\Show-DockerContainerVolumeGridView.ps1
function Show-DockerContainerVolumeGridView
{
    <#
    .SYNOPSIS
    Select docker containers whose associated volumn are shown in a grid;
    optionally allow volumes in grid to be selected for input to other commands
    
    .DESCRIPTION
    Select docker containers whose associated volumn are shown in a grid;
    optionally allow volumes in grid to be selected for input to other commands
    
    .PARAMETER InputObject
    The docker containers to show in grid
    
    .PARAMETER All
    Include stopped containers?
    
    .PARAMETER Force
    Show grid even when there is only one volume? Only relevant when -PassThru
    supplied
    
    .PARAMETER PassThru
    Pass volumes selected in grid down the pipeline as input to other commands?
    
    .EXAMPLE
    Show-DockerContainerVolumeGridView -All

    Description
    -----------
    Select from both running and stopped containers whose volumes are to be shown;
    see `docker container ls` and `docker volume ls`

    .EXAMPLE
    Get-DockerContainer 'mycompose_*' | Show-DockerContainerVolumeGridView

    Description
    -----------
    Select from containers whose name matches the wildcard search; show volumes
    associated with only these containers

    .EXAMPLE
    Show-DockerContainerVolumeGridView -PassThru | Show-DockerVolumeDirectory

    Description
    -----------
    Select from both running containers, then select from volumes associated with
    these containers; show the filesystem contents of the host directory for the
    selected volumes
    
    .NOTES
    Alias sdcv
    
    #>

    [CmdletBinding(DefaultParameterSetName = 'List')]
    [OutputType('System.Management.Automation.PSCustomObject')]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Input')]
        [PSCustomObject[]] $InputObject,

        [Parameter(ParameterSetName = 'List')]
        [switch] $All,

        [switch] $Force,

        [switch] $PassThru
    )
    
    begin
    {
        $items = @()
    }
    
    process
    {
        $items += if ($InputObject)
        {
            $InputObject
        }
        else
        {
            Get-DockerContainer -All:$All
        }
    }
    end
    {
        $items | Show-DockerContainerGridView -Force -PassThru |
            Get-DockerVolume | 
            Show-DockerVolumeGridView -Force:$Force -PassThru:$PassThru
    }
}

Set-Alias -Name sdcv -Value Show-DockerContainerVolumeGridView
Export-ModuleMember -Alias sdcv
# .\DockerHelpers\public\Show-DockerVolumeDirectory.ps1
function Show-DockerVolumeDirectory
{
    <#
    .SYNOPSIS
    Show the filesystem content of docker volumes
    
    .DESCRIPTION
    Show the filesystem content of docker volumess
    
    .PARAMETER Name
    Filter volume to return based on name
    
    .PARAMETER InputObject
    The docker volumes to show in grid
    
    .PARAMETER Interactive
    Show the directory contents in windows explorer? Defaults to true
    Where Windows explorer is not available, falls back to listing the content
    in the console
    
    .EXAMPLE
    Show-DockerVolumeDirectory

    Description
    -----------
    Show directory content of all volume

    .EXAMPLE
    Show-DockerVolumeDirectory 'my-vol*' -Interactive:$false

    Description
    -----------
    Show directory content of those volumes whose name matches the wildcard search;
    use the console to display this content

    .EXAMPLE
    Show-DockerVolumeGridView -PassThru | Show-DockerVolumeDirectory

    Description
    -----------
    Show directory content for the selected volumes
    
    .NOTES
    Alias 'sdvd'

    #>

    [CmdletBinding(DefaultParameterSetName = 'List')]
    param (
        [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0)]
        [SupportsWildcards()]
        [string[]]$Name,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Volume', Position = 0)]
        [PSCustomObject[]]$InputObject,

        [switch] $Interactive
    )
    
    begin
    {
        if (!$PSBoundParameters.ContainsKey('Interactive'))
        {
            $Interactive = $true
        }
        if (-not(Test-Path 'C:\Windows\explorer.exe'))
        {
            $Interactive = $false
        }

        $volumes = @()
        $showDirectory = if ($Interactive)
        {
            { Start-Process explorer $_.Mountpoint }
        }
        else
        {
            { 
                "Volume '$($_.Name)'" | Out-Host
                $directoryContent = Get-ChildItem $_.Mountpoint
                if ($directoryContent)
                {
                    $directoryContent | Out-Host
                } 
                else
                {
                    "$([Environment]::NewLine)" | Out-Host
                    " Directory: None" | Out-Host
                }
                "$([Environment]::NewLine)" | Out-Host
            }
        }
    }
    
    process
    {
        $volumes += switch ($PSCmdlet.ParameterSetName)
        {
            'Name'
            { 
                Get-DockerVolume -Name $Name
            }
            'Volume'
            {
                $InputObject
            }
            'List'
            {
                Get-DockerVolume
            }
            Default
            {
                throw "ParameterSet '$PSCmdlet.ParameterSetName' not implemented"
            }
        }
    }

    end
    {
        $volumes | Sort-Object Mountpoint -Unique | ForEach-Object $showDirectory
    }
}

Set-Alias -Name sdvd -Value Show-DockerVolumeDirectory
Export-ModuleMember -Alias sdvd
# .\DockerHelpers\public\Show-DockerVolumeGridView.ps1
function Show-DockerVolumeGridView
{
    <#
    .SYNOPSIS
    Show docker volumes in a grid; optionally allow items in grid to be
    selected for input to other commands
    
    .DESCRIPTION
    Show docker volumes in a grid; optionally allow items in grid to be
    selected for input to other commands
    
    .PARAMETER InputObject
    The docker volumes to show in grid
    
    .PARAMETER Force
    Show grid even when there is only one volume? Only relevant when -PassThru
    supplied
    
    .PARAMETER PassThru
    Pass items selected in grid down the pipeline as input to other commands?
    
    .EXAMPLE
    Show-DockerVolumeGridView
    
    Description
    -----------
    Show all volumes; see `docker volume ls` and `docker volume inspect`
    
    .EXAMPLE
    Show-DockerVolumeGridView -PassThru | Show-DockerVolumeDirectory

    Description
    -----------
    Show directories for the selected volumes

    .NOTES
    Alias 'sdv'

    #>

    [CmdletBinding()]
    [OutputType('System.Management.Automation.PSCustomObject')]
    param (
        [Parameter(ValueFromPipeline)]
        [PSCustomObject[]] $InputObject,

        [switch]$Force,

        [switch] $PassThru
    )
    
    begin
    {
        $items = @()
    }
    
    process
    {
        $items += if ($InputObject) 
        {
            $InputObject
        }
        else
        {
            Get-DockerVolume
        }
    }
    end
    {
        $title = 'docker volume(s)'
        if ($PassThru)
        {
            $title = "Select $title; tip: hold down CTRL button for multi-select"
        }

        $outputMode = if ($PassThru)
        {
            'Multiple'
        }
        else
        {
            'None'
        }

        $selected = if ($PassThru -and !$Force -and $items.Count -eq 1)
        {
            $items
        }
        else
        {
            $items |
                Sort-Object Name -Unique |
                Select-Object Name, Driver, MountPoint |
                Out-GridView -Title $title -OutputMode $outputMode
        }

        if ($PassThru)
        {
            $selected | ForEach-Object { Get-DockerVolume ($_.Name) }
        }
    }
}

Set-Alias -Name sdv -Value Show-DockerVolumeGridView
Export-ModuleMember -Alias sdv
# .\DockerHelpers\public\Wait-DockerContainerStatus.ps1
function Wait-DockerContainerStatus
{
    <#
    .SYNOPSIS
    Waits for the container status supplied
    
    .DESCRIPTION
    Waits for the container status supplied
    
    .PARAMETER Name
    The container to inspect
    
    .PARAMETER Status
    The status to wait for
    
    .PARAMETER Timeout
    The time (in seconds) to wait until giving up and throwing
    
    .PARAMETER Interval
    The polling interval to check for container status
    
    .EXAMPLE
    Wait-DockerContainerStatus 'some-container' running

    Description
    -----------
    Wait for the container named 'some-container' to have a status of running
    
    .NOTES
    Alias 'wdcs'

    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string[]]$Name,

        [Parameter(Mandatory)]
        [ValidateSet('created', 'running', 'paused', 'exited', 'dead', 'healthy', 'unhealthy')]
        [string[]] $Status,

        [int] $Timeout = 90,

        [int] $Interval = 3

    )
    
    begin
    {
        # todo: support wildcards; will need careful thought:
        # * wait for any container with the required status or all containers whose
        # name matches at the start of the wait?
        # * should created status be treated differently from other status?
        if ($Name -match '\*') {
            throw "Name parameter does not support wildcards"
        }

        $timeToWait = if ($Timeout -eq 0) {
            # ie never timeout!
            (Get-Date).AddYears(99)
        } else {
            (Get-Date).AddSeconds($Timeout)
        }

        $healthStates = 'healthy', 'unhealthy'
        $healthStatus = $Status | Where-Object { $healthStates -contains $_ }
    }
    
    process
    {
        $names = @()
        $names += $Name | Select-Object -Unique

        $statusPredicate = {
            $_.State.Status -in $Status -or ($_.State.Health -and $_.State.Health.Status -in $healthStatus)
        }

        while ($true) {
            $container = @(Get-DockerContainer $Name -Inspect)
            $container | ForEach-Object {
                $debugInfo = "{0}:{1}" -f $_.Name, $_.State.Status
                if ($_.State.Health) {
                    $debugInfo = '{0} ({1})' -f $debugInfo, $_.State.Health.Status
                }
                Write-Verbose $debugInfo
            }
            $matching = @($container | Where-Object $statusPredicate)
            if ($names.Count -eq $matching.Count) {
                break
            } elseif ((Get-Date) -ge $timeToWait) {
                throw "Timeout exceeded waiting on container (status required: $Status)"
            }
            Start-Sleep -Seconds $Interval
        }
    }
}

Set-Alias -Name wdcs -Value Wait-DockerContainerStatus
Export-ModuleMember -Alias wdcs
Write-Verbose 'Importing from [C:\MyProjects\DockerHelpers\DockerHelpers\classes]'