STDockerPS.psm1

#requires -version 2
function GetDockerContainerCommands {
    # List all "docker container --help commands" (or at least attempt to).
    # I made the leading whitespace mandatory to avoid the exception with the last
    # line with "Run 'docker container COMMAND --help' for more information on a command.
    # Not sure which is more rubust, I'm not a shrink and can't read minds either. :p
    
    # Cache results, obvious optimization... Should replace these docker client things
    # and parsing with API calls when possible.
    if (-not $Script:DockerContainerCommands) {
        $Script:DockerContainerCommands = @{}
    }
    else {
        $Script:DockerContainerCommands.Keys
        return
    }
    [Regex]::Matches(@( (@(docker container --help) | 
    Select-String -Pattern '^\s*Commands:\s*$' -Context 10000).Context.DisplayPostContext -join "`n"), 
    '(?m)^\s+(\S+)\s+.+') | ForEach-Object -Begin {
        $HashTable = @{}
        } -Process {
        $HashTable.($_.Groups[1].Value) = $True
        } -End {
        $HashTable.Keys
    }
    if ($Script:DockerContainerCommands.Keys.Count -lt 1) {
        $Script:DockerContainerCommands = $HashTable
    }
}

function dockerps {
<#
.SYNOPSIS
    Turn "docker ps" output into custom PowerShell objects. Add docker container methods
    to the objects by specifying the "-Full" parameter to dockerps, at the expense of
    speed and some CPU usage.
 
.DESCRIPTION
    The parsing to "objectify" relies on the column widths in this case, rather than my usual
    approach of using regular expressions, since it seems more robust for this
    case/output at hand. Will use API calls later, when possible.
 
    Use the parameter "-OmitNames" (will not be tab completed) to omit the name column for the
    containers. This is to get more condensed output. Just "-Omit" will work as a minimum.
     
    Use the parameter "-Full" (will not be tab completed) to add docker methods to the custom
    PowerShell objects dockerps returns. These are dynamically generated for each container and
    attached to each object. Therefore it uses some CPU and is a bit slower than without this
    parameter, but without you do not get the methods, only data/properties (no "docker interaction"
    through the objects themselves). The methods are tacked on and not special-cased. They assume
    a container ID belongs at the end.
 
    Otherwise, "docker ps --help" is what you should read for further information about the
    available parameters.
 
    -q / --quiet is special-cased to work identically as in the native "docker ps -q" (lists
    only container IDs).
 
.PARAMETER OmitNames
    Omit the container name column. Not tab completed. "-Omit" is the shortest allowed form.
.PARAMETER Full
    Add docker container methods to the custom PowerShell objects dockerps returns, at the
    expense of speed and some CPU usage. Not tab completed.
 
.EXAMPLE
dockerps | select -first 3
 
 
CONTAINER_ID : 26b08e320d17
IMAGE : microsoft/nanoserver
COMMAND : "cmd"
CREATED : 17 hours ago
STATUS : Up 17 hours
PORTS :
NAMES : temp1234temp1234
 
CONTAINER_ID : d92cb8edaf53
IMAGE : microsoft/nanoserver
COMMAND : "powershell"
CREATED : 39 hours ago
STATUS : Up 39 hours
PORTS :
NAMES : temp3
 
CONTAINER_ID : 9c45b67925db
IMAGE : microsoft/nanoserver
COMMAND : "powershell"
CREATED : 40 hours ago
STATUS : Up 40 hours
PORTS :
NAMES : temp2
 
.EXAMPLE
$Var = dockerps
 
$Var.CONTAINER_ID # list all container IDs (but use -q for (only) that)
$Var.COMMAND # list all commands
$Var.STATUS # list all statuses
$Var.IMAGE # list all image titles
$Var.NAMES # list all container names
 
.EXAMPLE
PS C:\temp\STDockerPs> @(dockerps | Where { $_.IMAGE -like "microsoft/*" }).Count
3
 
PS C:\temp\STDockerPs> @(dockerps | Where { $_.IMAGE -like "microsoft/*" }).Command
"powershell"
"powershell"
"cmd"
 
.EXAMPLE
dockerps -omit | select -first 1
 
 
CONTAINER_ID : 26b08e320d17
IMAGE : microsoft/nanoserver
COMMAND : "cmd"
CREATED : 17 hours ago
STATUS : Up 17 hours
PORTS :
 
.EXAMPLE
dockerps | select -first 1
 
 
CONTAINER_ID : 26b08e320d17
IMAGE : microsoft/nanoserver
COMMAND : "cmd"
CREATED : 17 hours ago
STATUS : Up 17 hours
PORTS :
NAMES : temp1234temp1234
 
 
 
.EXAMPLE
PS C:\temp\STDockerPs> (dockerps).CONTAINER_ID
26b08e320d17
d92cb8edaf53
9c45b67925db
520649cfb9d7
 
.EXAMPLE
(dockerps -a).CONTAINER_ID
26b08e320d17
d92cb8edaf53
9c45b67925db
520649cfb9d7
a0fc2bb9ad1b
e82a27f84cb6
 
.EXAMPLE
PS C:\> $x = dockerps -a -full
PS C:\> $x[4].RestartContainer( @{ PSForce = 1 } )
a4b49a9c0692
 
PS C:\temp\STDockerPs> dockerps -a | ft
 
CONTAINER_ID IMAGE COMMAND CREATED STATUS PORTS NAMES
------------ ----- ------- ------- ------ ----- -----
9d6cc0aac547 microsoft/nanoserver "cmd" 42 hours ago Exited (0) 6 minutes ago temp39
67ca71717171 microsoft/nanoserver "cmd" 42 hours ago Up About an hour temp38
eafcabb612da microsoft/nanoserver "cmd" 42 hours ago Up 42 hours temp37
796438a6e850 microsoft/nanoserver "cmd" 42 hours ago Up 42 hours temp36
a4b49a9c0692 microsoft/nanoserver "cmd" 42 hours ago Up 8 seconds temp35
f4294be07862 microsoft/nanoserver "cmd" 42 hours ago Up About an hour temp34
8ee7021bec8f microsoft/nanoserver "cmd" 42 hours ago Up About an hour temp33
48754acd5513 microsoft/nanoserver "cmd" 42 hours ago Up About an hour temp32
c66589488683 microsoft/nanoserver "cmd" 42 hours ago Up 42 hours temp31
4e4d98ee2785 microsoft/nanoserver "cmd" 42 hours ago Up 42 hours temp30
 
.EXAMPLE
PS C:\temp\STDockerPs> docker ps # the native command that returns text..
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0ee74ffb1c94 microsoft/nanoserver "powershell" 10 seconds ago Up 8 seconds temp14
6c692d226125 microsoft/nanoserver "powershell" 19 seconds ago Up 16 seconds temp13
168a95901a92 microsoft/nanoserver "powershell" 28 seconds ago Up 25 seconds temp12
e016f93d662d microsoft/nanoserver "powershell" 30 seconds ago Up 27 seconds temp11
542cf524b3e2 microsoft/nanoserver "powershell" 32 seconds ago Up 29 seconds temp10
c08e1e9ff5d7 microsoft/nanoserver "powershell" 34 seconds ago Up 31 seconds temp9
0e91d4d8a945 microsoft/nanoserver "powershell" 36 seconds ago Up 33 seconds temp8
393413eae3d1 microsoft/nanoserver "powershell" 38 seconds ago Up 35 seconds temp7
dbb26f999892 microsoft/nanoserver "cmd" 48 seconds ago Up 45 seconds temp6
d449d533d010 microsoft/nanoserver "cmd" About a minute ago Up About a minute temp5
 
# dockerps and objects with methods, restarting all containers running powershell
 
PS C:\temp\STDockerPs> @(dockerps -a -full).Where({
    $_.COMMAND -like '"powershell*'
}).RestartContainer( @{ PSForce = 1 } )
0ee74ffb1c94
6c692d226125
168a95901a92
e016f93d662d
542cf524b3e2
c08e1e9ff5d7
0e91d4d8a945
393413eae3d1
 
PS C:\temp\STDockerPs> dockerps -a | ft
 
CONTAINER_ID IMAGE COMMAND CREATED STATUS PORTS NAMES
------------ ----- ------- ------- ------ ----- -----
0ee74ffb1c94 microsoft/nanoserver "powershell" 3 minutes ago Up 45 seconds temp14
6c692d226125 microsoft/nanoserver "powershell" 3 minutes ago Up 43 seconds temp13
168a95901a92 microsoft/nanoserver "powershell" 3 minutes ago Up 41 seconds temp12
e016f93d662d microsoft/nanoserver "powershell" 3 minutes ago Up 39 seconds temp11
542cf524b3e2 microsoft/nanoserver "powershell" 3 minutes ago Up 37 seconds temp10
c08e1e9ff5d7 microsoft/nanoserver "powershell" 3 minutes ago Up 34 seconds temp9
0e91d4d8a945 microsoft/nanoserver "powershell" 3 minutes ago Up 32 seconds temp8
393413eae3d1 microsoft/nanoserver "powershell" 3 minutes ago Up 30 seconds temp7
dbb26f999892 microsoft/nanoserver "cmd" 4 minutes ago Up 4 minutes temp6
d449d533d010 microsoft/nanoserver "cmd" 4 minutes ago Up 4 minutes temp5
 
#>
  
    if ($Args -match '-q|--quiet') {
        docker ps ($Args -replace '-Omit.*|-Full')
        return
    }
    else {
        $PreservedArgs = $Args | ForEach-Object { $_ } # deep copy
        $DockerPSOutput = @(@(docker ps ($Args -replace '-Omit.*|-Full')) | Where-Object {
            $_ -match '\S'
        })
    }
    
    $DockerPSTitles = $DockerPSOutput | Select-Object -First 1
    
    # Since we ((for) now) have a predictable and logical starting point for each
    # column (same char as the header), I will play with SubString to PSobjectify the stuff...
    
    $Indexes = @()
    $Headers = @("CONTAINER ID", "IMAGE", "COMMAND", "CREATED", "STATUS",
        "PORTS", "NAMES")
    
    foreach ($Header in $Headers) {
        $Indexes += $DockerPSTitles.IndexOf($Header)
    }
    
    $Indexes += 0 # dummy value, replaced later for each container line, small "trick"..
    
    $TextInfo = [CultureInfo]::CurrentCulture.TextInfo
    
    $DockerPSOutput | Select-Object -Skip 1 | ForEach-Object {
    
        Write-Verbose -Message "Current line: $_ (length: $($_.Length))." #-Verbose
        $Indexes[-1] = ([String]$_).Length
    
        # Avoid spaces in the titles, for easier access later.
        $Object = "" | Select-Object -Property ($MyPSHeaders = @($Headers -replace ' ', '_'))
        
        foreach ($i in 0..($Indexes.Count - 2)) {
            $Object.($MyPSHeaders[$i]) = $_.SubString(
                $Indexes[$i], ($Indexes[$i + 1] - $Indexes[$i])
            ).TrimEnd()
        }
        
        if ($PreservedArgs -match '-Full') {
            foreach ($Command in @(GetDockerContainerCommands)) {
                $TitleCommand = $TextInfo.ToTitleCase($Command)
                $Object | Add-Member -MemberType ScriptMethod -Name "${TitleCommand}Container" -Value ([ScriptBlock]::Create("
                    [CmdletBinding()]
                    Param([HashTable] `$InternalArgs = @{})
                    function $TitleCommand-DockerContainer {
                        [CmdletBinding(
                            SupportsShouldProcess = `$True,
                            ConfirmImpact = 'High')]
                        Param(
                            [System.Object] `$This2,
                            [HashTable] `$InternalArgs)
                        if (`$InternalArgs.Keys -match '-?PSForce' -or `
                            `$PSCmdlet.ShouldProcess(`"[OPERATION: $Command] `$(`$This2.CONTAINER_ID) (`$(`$This2.NAMES))`")) {
                            `$OldEAP = `$ErrorActionPreference
                            `$ErrorActionPreference = 'Stop'
                            try {
                                if (`$InternalArgs.ContainsKey('-Verbose')) {
                                    Write-Verbose -Verbose `"Running: docker container $Command `$(`$InternalArgs.Keys -replace '-?PSForce|-?Verbose') `$(`$This2.CONTAINER_ID)`"
                                }
                                # Wrap in a powershell.exe call to allow for commenting out the ID (this is weird...)
                                powershell -command `"docker container $Command `$(`$InternalArgs.Keys -replace '-?PSForce|-?Verbose') `$(`$This2.CONTAINER_ID)`"
                            }
                            catch {
                                Write-Error -Message `$_.ToString()
                            }
                            `$ErrorActionPreference = `$OldEAP
                        }
                    }
                    $TitleCommand-DockerContainer -This2 `$this -InternalArgs `$InternalArgs
                "
))
            }
        }
        if ($PreservedArgs -match '-Omit') {
            $Object | Select-Object -Property ($MyPSHeaders[0..($Headers.Count - 2)])
        }
        else {
            $Object
        }
    }
}

function dockerpsq {
    <#
    .SYNOPSIS
        Query for a container ID or name and get an object back.
         
        Use the "-Full" parameter to also get docker container methods
        attached to the object or objects returned. This is a bit slower.
 
    .PARAMETER Identity
        Container ID to query for.
    .PARAMETER Name
        Container name to query for.
    .PARAMETER Full
        Add docker script methods to the returned PowerShell objects.
    #>

    [CmdletBinding(
        DefaultParameterSetName = "ID"
    )]
    Param(
        [Parameter(ParameterSetName="ID", Mandatory = $True)][String[]] $Identity,
        [Parameter(ParameterSetName="Name", Mandatory = $True)][String[]] $Name,
        [Switch] $Full)
    @(dockerps -a $( if ($Full) { "-Full" } )) | Where-Object {
        if ($PSCmdlet.ParameterSetName -eq "ID") {
            $Identity -eq $_.CONTAINER_ID
        }
        else {
            $Name -eq $_.NAMES
        }
    }
}