Docker.PowerShell.CLI.psm1

using namespace System.Management.Automation
using namespace System.Management.Automation.Language
using namespace System.Collections.Generic
using namespace System.Collections
using namespace System.Diagnostics
using namespace System.Collections.ObjectModel
class BooleanArgumentCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        if ($wordToComplete) {
            return [CompletionResult[]]::new(0)
        }

        $CompletionResults = [List[CompletionResult]]::new(3)
        $CompletionResults.Add([CompletionResult]::new('$true', 'true', 'ParameterValue', 'true'))
        $CompletionResults.Add([CompletionResult]::new('$false', 'false', 'ParameterValue', 'false'))

        $Command = Get-Command -Name $commandAst.GetCommandName()
        $Parameter = $Command.Parameters[$parameterName]
        if ($Parameter.ParameterType -eq [Nullable[bool]]) {
            $CompletionResults.Add([CompletionResult]::new('$null', 'null', 'ParameterValue', 'null'))
        }
        
        return $CompletionResults
    }
}
class DateTimeArgumentCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        if ($wordToComplete) {
            return [CompletionResult[]]::new(0)
        }

        $CompletionResults = [CompletionResult[]]::new(10)

        for ($i = 0; $i -lt $CompletionResults.Count; $i++) {
            $Date = [DateTime]::Today.AddDays(-$i)
            $DateString = $Date.ToString('yyyy-MM-dd')
            $CompletionResults[$i] = [CompletionResult]::new($DateString, $DateString, 'ParameterValue', $DateString)
        }

        return $CompletionResults
    }
}
class DockerBinArgumentCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string] $commandName,
        [string] $parameterName,
        [string] $wordToComplete,
        [CommandAst] $commandAst,
        [IDictionary] $fakeBoundParameters) {
        $wc = ConvertTo-WordToCompleteWildcard -WordToComplete $wordToComplete

        $DockerContainerParameters = @{}
        if ($FakeBoundParameters.ContainsKey('Context')) {
            $DockerContainerParameters['Context'] = $FakeBoundParameters['Context']
        }
        if ($FakeBoundParameters.ContainsKey('Id')) {
            $DockerContainerParameters['Id'] = $FakeBoundParameters['Id']
        }
        if ($FakeBoundParameters.ContainsKey('Name')) {
            $DockerContainerParameters['Name'] = $FakeBoundParameters['Name']
        }

        $Container = Get-DockerContainer @DockerContainerParameters
        if (!$?) { return [CompletionResult[]]::new(0) }
        if (!$Container) { return [CompletionResult[]]::new(0) }

        $DockerArguments = @(
            if ($FakeBoundParameters['Context']) { '--context'; $FakeBoundParameters['Context'] }
            'exec'
            '-w'
            '/bin'
            '-t'
            $Container.Id
            'ls'
            '--format=single-column'
        )

        Write-Debug "docker $DockerArguments"
        $BinContents = docker $DockerArguments
        if (!$?) { return [CompletionResult[]]::new(0) }

        $Completions = [List[CompletionResult]]::new($BinContents.Count)
        foreach ($item in $BinContents) {
            if ($Item -like $wc) {
                $CompletionText = ConvertTo-CompletionText -InputObject $Item -WordToComplete $wordToComplete
                $Completions.Add([CompletionResult]::new($CompletionText, $Item, 'ParameterValue', $Item))
            }
        }

        return $Completions
    }

}
class DockerBuildAddHostTransformation : ArgumentTransformationAttribute {
    [object] Transform([EngineIntrinsics]$EngineIntrinsics, [object]$InputData) {
        if ($InputData -as [Dictionary[string, ipaddress]]) {
            return $InputData
        }

        $Dictionary = [Dictionary[string, IPAddress]]::new()
        # InputData may be a hasthable of valid dns name to IP address
        if ($InputData -is [hashtable]) {
            foreach ($Key in $InputData.Keys) {
                $Dictionary[$Key] = [ipaddress]$InputData[$Key]
            }
        }
        else {
            foreach ($Item in $InputData) {
                if ($Item -is [string]) {
                    $Parts = $Item.Split(':')
                    if ($Parts.Length -ne 2) {
                        throw "Invalid host specification '$Item'. Input must be in the form 'hostname:ipaddress'."
                    }
                    $Dictionary[$Parts[0]] = [ipaddress]$Parts[1]
                }
                else {
                    try {
                        $Dictionary.Add($Item)
                    }
                    catch {
                        throw [ArgumentException]::new("Invalid host specification '$Item'. Input must be in the form 'hostname:ipaddress'.", $_.Exception)
                    }
                }
            }
        }
        return $Dictionary
    }
}
class DockerContainer {

    hidden [PSObject]$PSSourceValue

    [string]$Id

    [string[]]$Names

    [string[]]$Labels

    [string]$Image

    [string]$Command

    [string]$Status

    [string[]]$Ports

    [string[]]$Networks

    [DateTimeOffset]$CreatedAt

    [string] ToString() {
        return $this.Id
    }

    DockerContainer([psobject]$deserializedJson) {
        $this.PSSourceValue = $deserializedJson
        $this.PSObject.TypeNames.Insert(0, 'Docker.Container')
        $this.PSObject.TypeNames.Insert(1, 'Docker.PowerShell.CLI.Container')

        foreach ($Property in $deserializedJson.PSObject.Properties) {
            if ($Property.Name -eq 'Names') {
                $this.Names = $Property.Value -split ','
            }
            elseif ($Property.Name -eq 'Labels') {
                $this.Labels = $Property.Value -split ','
            }
            elseif ($Property.Name -eq 'Ports') {
                $this.Ports = $Property.Value -split ','
            }
            elseif ($Property.Name -eq 'CreatedAt') {
                $this.CreatedAt = [DateTimeOffset][string]$Property.Value.Split(' ')[0..2]
            }
            elseif ($Property.Name -eq 'Networks') {
                $this.Networks = $Property.Value -split ','
            }
            elseif ($this.PSObject.Properties[$Property.Name]) {
                $this.$($Property.Name) = $Property.Value
            }
            else {
                $this.PSObject.Properties.Add($Property.Copy())
            }
        }
    }
}
class DockerContainerCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        $wc = ConvertTo-WordToCompleteWildcard $wordToComplete
        $ProxyParameters = @{}
        if ($FakeBoundParameters['Context']) {
            $ProxyParameters['Context'] = $FakeBoundParameters['Context']
        }

        $Containers = Get-DockerContainer @ProxyParameters

        $CompletionResults = [List[CompletionResult]]::new();
        foreach ($Container in $Containers) {
            $IsMatch = $Container.Names -like $wc -or $Container.id -like $wc
            if (-not $IsMatch) {
                continue
            }

            if ($parameterName -in 'ContainerId', 'Id') {
                $CompletionText = @($Container.Id)
            }
            else {
                $CompletionText = @($Container.Names)
            }

            foreach ($Completion in $CompletionText) {
                $SafeCompletionText = ConvertTo-CompletionText -InputObject $Completion -WordToComplete $wordToComplete
                $ListItemText = if ($Completion -eq $Container.Id) { "$Completion (name: $($Container.Names -join ', '))" } else { "$Completion (id: $($Container.Id))" }

                $CompletionResults.Add(
                    [CompletionResult]::new(
                        $SafeCompletionText,
                        $ListItemText,
                        'ParameterValue',
                        $Completion
                    )
                )
            }
        }

        return $CompletionResults
    }
}
class DockerContainerNetworkConnection {
    hidden [PSObject]$PSSourceValue

    [string]$ContainerName

    [string]$ContainerId

    [string]$ImageName

    [string]$ImageId

    [string]$NetworkName

    [string]$NetworkId

    [string]$EndpointId

    [string]$MacAddress

    [ipaddress]$IPAddress

    [string[]]$Aliases

    [string] ToString() {
        return $this.EndpointId
    }

    DockerContainerNetworkConnection(
        [PSObject]$deserializedJson,
        [string]$ContainerId,
        [string]$ContainerName,
        [string]$ImageId,
        [string]$ImageName,
        [string]$NetworkName) {
        $this.PSSourceValue = $deserializedJson
        $this.ContainerId = $ContainerId
        $this.ContainerName = $ContainerName
        $this.ImageId = $ImageId
        $this.ImageName = $ImageName
        $this.NetworkName = $NetworkName
        $this.PSObject.TypeNames.Insert(0, 'Docker.ContainerNetworkConnection')
        $this.PSObject.TypeNames.Insert(1, 'Docker.PowerShell.CLI.ContainerNetworkConnection')

        foreach ($Property in $deserializedJson.PSObject.Properties) {
            if ($this.PSObject.Properties[$Property.Name]) {
                $this.$($Property.Name) = $Property.Value
            }
            else {
                $this.PSObject.Properties.Add($Property.Copy())
            }
        }
    }
}
class DockerContext {

    hidden [PSObject] $PSSourceValue

    [bool]$Current

    [string]$Name

    [string]$Description

    [string]$DockerEndpoint

    [string]$KubernetesEndpoint

    [string]$ContextType

    [string]$StackOrchestrator

    [string] ToString() {
        return $this.Name
    }

    DockerContext([PSObject]$deserializedJson) {
        $this.PSSourceValue = $deserializedJson
        $this.PSTypeNames.Insert(0, 'Docker.Context')
        $this.PSTypeNames.Insert(1, 'Docker.PowerShell.CLI.Context')

        foreach ($Property in $deserializedJson.PSObject.Properties) {
            if ($this.PSObject.Properties[$Property.Name]) {
                $this.$($Property.Name) = $Property.Value
            }
            else {
                $this.PSObject.Properties.Add($Property.Copy())
            }
        }
    }
}
class DockerContextCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        $wc = ConvertTo-WordToCompleteWildcard -WordToComplete $wordToComplete

        Write-Debug 'docker context list --quiet'
        $Contexts = docker context list --quiet
        Write-Debug 'docker context show'
        $CurrentContext = docker context show

        $Results = [List[CompletionResult]]::new()
        foreach ($Context in $Contexts) {
            if ($Context -notlike $wc) {
                continue
            }
            if ($Context -eq $CurrentContext) {
                $DisplayText = "$($global:PSStyle.Foreground.BrightCyan)$Context$($global:PSStyle.Reset)"
            }
            else {
                $DisplayText = $Context
            }
            $CompletionText = ConvertTo-CompletionText -InputObject $Context -WordToComplete $wordToComplete
            $Results.Add(
                [CompletionResult]::new(
                    $CompletionText,
                    $DisplayText,
                    'ParameterValue',
                    $DisplayText
                )
            )
        }
        return $Results
    }
}
class DockerImage {

    hidden [PSObject]$PSSourceObject

    [string]$Id

    [string]$Repository

    [string[]]$Containers

    [DateTimeOffset]$CreatedAt

    [string[]]$Labels

    [string]$Digest

    [string]$Tag

    [string[]]$Mounts

    DockerImage([PSObject]$deserializedJson) {
        $this.PSSourceObject = $deserializedJson
        $this.PSTypeNames.Insert(0, 'Docker.Image')
        $this.PSTypeNames.Insert(1, 'Docker.PowerShell.CLI.Image')

        foreach ($Property in $deserializedJson.PSObject.Properties) {
            if ($Property.Name -eq 'Containers') {
                if ($Property.Value -eq 'N/A') {
                    $this.Containers = @()
                }
                else {
                    $this.Containers = $Property.Value -split ','
                }
            }
            elseif ($Property.Name -eq 'CreatedAt') {
                $this.CreatedAt = [DateTimeOffset][string]$Property.Value.Split(' ')[0..2]
            }
            elseif ($Property.Name -eq 'Labels') {
                $this.Labels = $Property.Value -split ','
            }
            elseif ($Property.Name -eq 'Mounts') {
                $this.Mounts = $Property.Value -split ','
            }
            elseif ($this.PSObject.Properties[$Property.Name]) {
                $this.($Property.Name) = $Property.Value
            }
            else {
                $this.PSObject.Properties.Add($Property.Copy())
            }
        }
    }
}
class DockerImageCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        $wc = ConvertTo-WordToCompleteWildcard $wordToComplete

        $ProxyParameters = @{}
        if ($FakeBoundParameters['Context']) {
            $ProxyParameters['Context'] = $FakeBoundParameters['Context']
        }

        $Images = Get-DockerImage @ProxyParameters

        $CompletionResults = [List[CompletionResult]]::new();
        $CompletedTags = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
        foreach ($Image in $Images) {
            $IsMatch = $Image.Name -like $wc -or $Image.id -like $wc
            if (-not $IsMatch) {
                continue
            }

            if ($parameterName -in 'ImageId', 'Id') {
                $CompletionText = $Image.Id
                $ListItemText = "$($Image.Id) ($($Image.Repository))"
            }
            # DockerImageCompleter is designed to complete from all images, so we don't
            # need to worry about filtering the tag down to tags for the specified image.
            elseif ($parameterName -eq 'Tag') {
                $CompletionText = $Image.Tag
                if (!$CompletedTags.Add($CompletionText)) {
                    continue
                }
                $ListItemText = $Image.Tag
            }
            elseif ($parameterName -in 'Repository', 'Name', 'ImageName', 'RepositoryName') {
                $CompletionText = $Image.Repository
                $ListItemText = "$($Image.Repository) ($($Image.Id))"
            }
            else {
                $CompletionText = $Image.FullName
                $ListItemText = "$($Image.FullName) ($($Image.Id))"
            }

            $SafeCompletionText = ConvertTo-CompletionText -InputObject $CompletionText -WordToComplete $wordToComplete

            $CompletionResults.Add(
                [CompletionResult]::new(
                    $SafeCompletionText,
                    $ListItemText,
                    'ParameterValue',
                    $CompletionText
                )
            )
        }

        return $CompletionResults
    }
}
class DockerNetwork {

    hidden [psobject]$PSSourceValue

    [string]$Id

    [string]$Name

    [string]$Driver

    [string]$Scope

    [string[]]$Labels

    [DateTimeOffset]$CreatedAt

    [string] ToString() {
        return $this.Id
    }

    DockerNetwork([psobject]$deserializedJson) {
        $this.PSSourceValue = $deserializedJson
        $this.PSObject.TypeNames.Insert(0, 'Docker.Network')
        $this.PSObject.TypeNames.Insert(1, 'Docker.PowerShell.CLI.Network')

        foreach ($Property in $deserializedJson.PSObject.Properties) {
            if ($Property -is [psnoteproperty]) {
                # Handle special property values
                if ($Property.Name -eq 'Driver' -and $Property.Value -eq 'null') {
                    $this.Driver = $null
                    continue
                }
                if ($Property.Name -eq 'Labels') {
                    $this.Labels = $Property.Value -split ','
                    continue
                }
                if ($Property.Name -eq 'CreatedAt') {
                    $this.CreatedAt = [DateTimeOffset][string]$Property.Value.Split(' ')[0..2]
                    continue
                }

                # Handle normal property values
                if ($this.PSObject.Properties[$Property.Name]) {
                    $this.($Property.Name) = $Property.Value
                }
                else {
                    $Duplicate = $Property.Copy()
                    $asBool = $false
                    if ([bool]::TryParse($Duplicate.Value, [ref]$asBool)) {
                        $Duplicate.Value = $asBool
                    }
                    $this.PSObject.Properties.Add($Duplicate)
                }
            }
            else {
                Write-Warning "Unsupported property type '$($Property.GetType().Name)' for property '$($Property.Name)'. Contact the module author."
            }
        }
    }
}
class DockerNetworkAuxAddressTransformation : ArgumentTransformationAttribute {
    [object] Transform([EngineIntrinsics]$engineIntrinsics, [object]$inputData) {
        $Result = [Dictionary[string, HashSet[IPAddress]]]::new([StringComparer]::OrdinalIgnoreCase)
        if ($inputData -is [object[]]) {
            foreach ($item in $inputData) {
                if ($_ -match '^(?<key>.+)=(?<value>.+)$') {
                    if (!$Result[$Matches['key']]) {
                        $Result[$Matches['key']] = @($Matches['value'])
                    }
                    else {
                        $Result[$Matches['key']].Add($Matches['value'])
                    }
                }
                else {
                    # Cannot handle the string, return the original input
                    return $inputData
                }
            }
        }
        elseif ($inputData -is [hashtable]) {
            foreach ($keyvalue in $inputData) {
                if ($result[$keyvalue.Key]) {
                    $Result[$keyvalue.Key].Add($keyvalue.Value)
                }
                else {
                    $Result[$keyvalue.Key] = @($keyvalue.Value)
                }
            }
        }
        else {
            return $inputData
        }
        return $Result
    }
}
class DockerNetworkCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string] $CommandName,
        [string] $ParameterName,
        [string] $WordToComplete,
        [CommandAst] $CommandAst,
        [IDictionary] $FakeBoundParameters
    ) {
        $wc = ConvertTo-WordToCompleteWildcard -WordToComplete $WordToComplete
        $CompletionResults = [List[CompletionResult]]::new()

        $DockerNetworkParameters = @{}
        if ($FakeBoundParameters['Context']) {
            $DockerNetworkParameters['Context'] = $FakeBoundParameters['Context']
        }
        
        $networks = Get-DockerNetwork @DockerNetworkParameters

        foreach ($network in $networks) {
            $IsMatch = $false
            $RawCompletionText = ''
            switch ($ParameterName) {
                { $_ -in @('Id', 'NetworkId') } {
                    $IsMatch = $network.Id -like $wc
                    $RawCompletionText = $network.Id
                }
                'Driver' {
                    $IsMatch = $network.Driver -ne 'null' -and $network.Driver -like $wc
                    $RawCompletionText = $network.Driver
                }
                'Scope' {
                    $IsMatch = $network.Scope -like $wc
                    $RawCompletionText = $network.Scope
                }
                default {
                    $IsMatch = $network.Name -like $wc
                    $RawCompletionText = $network.Name
                }
            }
            if (!$IsMatch) {
                continue
            }

            $CompletionText = ConvertTo-CompletionText -InputObject $RawCompletionText -WordToComplete $WordToComplete

            $CompletionResults.Add(
                [CompletionResult]::new(
                    $CompletionText,
                    $RawCompletionText,
                    'ParameterValue',
                    $RawCompletionText
                )
            )
        }
        
        return $CompletionResults
    }
}
class EmptyHashtableArgumentCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        if ($wordToComplete) {
            return [CompletionResult[]]::new(0)
        }

        return [CompletionResult[]]@(
            [CompletionResult]::new('@{}', '@{} # empty hashtable', 'ParameterValue', '@{} # empty hashtable')
        )
    }
}
class EmptyIpAddressArgumentCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        if ($wordToComplete) {
            return [CompletionResult[]]::new(0)
        }

        return [CompletionResult[]]@(
            [CompletionResult]::new('''0.0.0.0''', '''0.0.0.0''', 'ParameterValue', '''0.0.0.0''')
        )
    }
}
class EmptyScriptBlockArgumentCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        if ($wordToComplete) {
            return [CompletionResult[]]::new(0)
        }

        return [CompletionResult[]]@(
            [CompletionResult]::new('{}', '{} # empty ScriptBlock', 'ParameterValue', '{} # empty ScriptBlock')
        )
    }
}
class EmptyStringArgumentCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        if ($wordToComplete) {
            return [CompletionResult[]]::new(0)
        }

        return [CompletionResult[]]@(
            [CompletionResult]::new('''''', ''''' # empty string', 'ParameterValue', ''''' # empty string')
        )
    }
}
class LowerCaseTransformation : ArgumentTransformationAttribute {
    [Object] Transform([EngineIntrinsics]$engineIntrinsics, [System.Object]$inputData) {

        $result = foreach ($item in $inputData) {
            ([string]$item).ToLower()
        }

        return $result
    }
}
class NumericArgumentCompleter : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string]$commandName,
        [string]$parameterName,
        [string]$wordToComplete,
        [CommandAst]$commandAst,
        [IDictionary]$fakeBoundParameters
    ) {
        if ($wordToComplete) {
            return [CompletionResult[]]::new(0)
        }

        return [CompletionResult[]]@(
            [CompletionResult]::new(1, 1, 'ParameterValue', 1)
            [CompletionResult]::new(2, 2, 'ParameterValue', 2)
            [CompletionResult]::new(3, 3, 'ParameterValue', 3)
            [CompletionResult]::new(4, 4, 'ParameterValue', 4)
            [CompletionResult]::new(5, 5, 'ParameterValue', 5)
            [CompletionResult]::new(6, 6, 'ParameterValue', 6)
            [CompletionResult]::new(7, 7, 'ParameterValue', 7)
            [CompletionResult]::new(8, 8, 'ParameterValue', 8)
            [CompletionResult]::new(9, 9, 'ParameterValue', 9)
            [CompletionResult]::new(10, 10, 'ParameterValue', 10)
        )
    }
}
class ValidateDockerContext : ValidateArgumentsAttribute {
    [void ]Validate([object]$Context, [EngineIntrinsics]$EngineIntrinsics) {
        if ($Context -as [string]) {
            Write-Debug 'docker context list --quiet'
            $Contexts = docker context list --quiet
            if ($Contexts -notcontains $Context) {
                throw "Context '$Context' does not exist"
            }
        }
    }
}
function Assert-DockerPullJob {
    [CmdletBinding()]
    param()
    process {
        if ('Docker.PowerShell.CLI.DockerPullJob' -as [type]) {
            return
        }

        Add-Type -TypeDefinition @'
using System;
using System.Management.Automation;
using System.Diagnostics;
 
namespace Docker.PowerShell.CLI {
    public sealed class DockerPullJob : Job
    {
        // Docker process
        private readonly Process _process;
        // Previous line of output. If successfully exiting, this line will
        // contain the image name.
        private string _lastOutput;
 
        // Abstract member, does not apply
        public override string Location
        {
            get { return _process.MachineName; }
        }
 
        public override string StatusMessage
        {
            get
            {
                if (_process.HasExited)
                {
                    return string.Format("Exited ({0})", _process.ExitCode);
                }
                else
                {
                    return "Running";
                }
            }
        }
 
        public override bool HasMoreData
        {
            get
            {
                return Output.Count > 0
                    || Error.Count > 0
                    || Debug.Count > 0;
            }
        }
 
        public override void StopJob()
        {
            _process.Kill();
            SetJobState(JobState.Stopped);
        }
 
        public int GetProcessId()
        {
            return _process.Id;
        }
 
        private static string GetName(string arguments)
        {
            return string.Format("docker {0}", arguments);
        }
 
        public DockerPullJob(string command, string arguments)
            : base(command, GetName(arguments))
        {
            PSJobTypeName = "DockerJob";
            var startInfo = new ProcessStartInfo("docker", arguments);
            startInfo.RedirectStandardOutput = true;
            startInfo.RedirectStandardError = true;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true;
 
            _process = new Process();
            _process.StartInfo = startInfo;
            _process.EnableRaisingEvents = true;
            _process.Exited += OnProcessExited;
            _process.OutputDataReceived += OnOutputDataReceived;
            _process.ErrorDataReceived += OnErrorDataReceieved;
 
            SetJobState(JobState.Running);
 
            _process.Start();
            _process.BeginOutputReadLine();
            _process.BeginErrorReadLine();
        }
 
        private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (e == null || e.Data == null)
            {
                return;
            }
            _lastOutput = e.Data;
            var record = new DebugRecord(e.Data);;
            Debug.Add(record);
        }
 
        private void OnErrorDataReceieved(object sender, DataReceivedEventArgs e)
        {
            if (e == null || e.Data == null)
            {
                return;
            }
            var exn = new Exception(e.Data);
            var err = new ErrorRecord(
                exn,
                "DockerPullError",
                ErrorCategory.FromStdErr,
                null
            );
            Error.Add(err);
        }
 
        private void OnProcessExited(object sender, EventArgs e)
        {
            if (_process.ExitCode == 0)
            {
                var image = _lastOutput;
                var pso = new PSObject(image);
                Output.Add(pso);
 
                SetJobState(JobState.Completed);
            }
            else
            {
                SetJobState(JobState.Failed);
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _process.Exited -= OnProcessExited;
                _process.OutputDataReceived -= OnOutputDataReceived;
                _process.ErrorDataReceived -= OnErrorDataReceieved;
 
                _process.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}
'@

    }
}
function Assert-DockerPushJob {
    [CmdletBinding()]
    param()
    process {
        if ('Docker.PowerShell.CLI.DockerPushJob' -as [type]) {
            return
        }

        Add-Type -TypeDefinition @'
using System;
using System.Management.Automation;
using System.Diagnostics;
 
namespace Docker.PowerShell.CLI {
    public sealed class DockerPushJob : Job
    {
        // Docker process
        private readonly Process _process;
 
        // Abstract member, does not apply
        public override string Location
        {
            get { return _process.MachineName; }
        }
 
        public override string StatusMessage
        {
            get
            {
                if (_process.HasExited)
                {
                    return string.Format("Exited ({0})", _process.ExitCode);
                }
                else
                {
                    return "Running";
                }
            }
        }
 
        public override bool HasMoreData
        {
            get
            {
                return Error.Count > 0
                    || Debug.Count > 0;
            }
        }
 
        public override void StopJob()
        {
            _process.Kill();
            SetJobState(JobState.Stopped);
        }
 
        public int GetProcessId()
        {
            return _process.Id;
        }
 
        private static string GetName(string arguments)
        {
            return string.Format("docker {0}", arguments);
        }
 
        public DockerPushJob(string command, string arguments)
            : base(command, GetName(arguments))
        {
            PSJobTypeName = "DockerJob";
            var startInfo = new ProcessStartInfo("docker", arguments);
            startInfo.RedirectStandardOutput = true;
            startInfo.RedirectStandardError = true;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true;
 
            _process = new Process();
            _process.StartInfo = startInfo;
            _process.EnableRaisingEvents = true;
            _process.Exited += OnProcessExited;
            _process.OutputDataReceived += OnOutputDataReceived;
            _process.ErrorDataReceived += OnErrorDataReceieved;
 
            SetJobState(JobState.Running);
 
            _process.Start();
            _process.BeginOutputReadLine();
            _process.BeginErrorReadLine();
        }
 
        private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (e == null || e.Data == null)
            {
                return;
            }
            var record = new DebugRecord(e.Data);;
            Debug.Add(record);
        }
 
        private void OnErrorDataReceieved(object sender, DataReceivedEventArgs e)
        {
            if (e == null || e.Data == null)
            {
                return;
            }
            var exn = new Exception(e.Data);
            var err = new ErrorRecord(
                exn,
                "DockerPullError",
                ErrorCategory.FromStdErr,
                null
            );
            Error.Add(err);
        }
 
        private void OnProcessExited(object sender, EventArgs e)
        {
            if (_process.ExitCode == 0)
            {
                SetJobState(JobState.Completed);
            }
            else
            {
                SetJobState(JobState.Failed);
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _process.Exited -= OnProcessExited;
                _process.OutputDataReceived -= OnOutputDataReceived;
                _process.ErrorDataReceived -= OnErrorDataReceieved;
 
                _process.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}
'@

    }
}
function ConvertTo-CompletionText {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline, Position = 0)]
        [AllowNull()]
        [AllowEmptyString()]
        [string]
        $InputObject,

        [Parameter(Mandatory)]
        [AllowNull()]
        [AllowEmptyString()]
        [string]
        $WordToComplete
    )
    process {
        $QuoteType = $null
        foreach ($QuoteChar in @('''', '"')) {
            if ($WordToComplete.StartsWith($QuoteChar)) {
                $QuoteType = $QuoteChar
                break
            }
        }

        switch ($QuoteType) {
            '''' {
                '''{0}''' -f [CodeGeneration]::EscapeSingleQuotedStringContent($InputObject)
            }
            '"' {
                '"{0}"' -f (($InputObject -replace '"', '""') -replace '`', '``')
            }
            default {
                if ($InputObject -match '[\s''"]') {
                    '''{0}''' -f [CodeGeneration]::EscapeSingleQuotedStringContent($InputObject)
                }
                else {
                    $InputObject
                }
            }
        }
    }
}
function ConvertTo-DockerWildcard {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)]
        [string]
        $Expression
    )
    process {
        $Expression -split '(?<!`)\*' | ForEach-Object {
            if ($_) {
                $_ -replace '`\*', '*'
            }
        }
    }
}
function ConvertTo-WordToCompleteWildcard {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline, Position = 0)]
        [AllowNull()]
        [AllowEmptyString()]
        [string]
        $WordToComplete
    )
    process {
        if ($WordToComplete -eq '') {
            return '*'
        }

        $StartIndex = 0
        $Length = $WordToComplete.Length - 1
        foreach ($QuoteChar in @('''', '"')) {
            if ($WordToComplete.StartsWith($QuoteChar)) {
                $StartIndex = 1
                if ($WordToComplete.EndsWith($QuoteChar)) {
                    $Length = $WordToComplete.Length - 2
                }
                else {
                    $Length = $WordToComplete.Length - 1
                }
            }
        }

        $TextToComplete = $WordToComplete.Substring($StartIndex, $Length)

        "$TextToComplete*"
    }
}
function Get-DockerContainerInternal {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Name,

        [Parameter()]
        [string[]]
        $Id,

        [Parameter()]
        [string]
        $Context,

        [Parameter()]
        [switch]
        $EscapeId
    )

    process {
        $Parameters = @{}
        if ($Id) {
            $Parameters['Id'] = $Id | ForEach-Object {
                if ($EscapeId) {
                    [WildcardPattern]::Escape($_)
                }
                else {
                    $_
                }
            }
        }
        if ($Name) {
            $Parameters['Name'] = $Name
        }
        if ($Context) {
            $Parameters['Context'] = $Context
        }

        Get-DockerContainer @Parameters
    }
}
function Get-DockerContainerSingle {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Id,

        [Parameter()]
        [string]
        $Context,

        [Parameter()]
        [switch]
        $AllowNone
    )
    process {
        $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId

        $Message = if ($Name) { "name '$Name'" } else { "id '$Id'" }
        $TargetObject = if ($Name) { $Name } else { $Id }

        if ($Containers.Count -gt 1) {
            Write-Error "More than one container found for $Message." -Category InvalidArgument -ErrorId 'AmbiguousContainer' -TargetObject $TargetObject
        }
        if ($Containers.Count -eq 0 -and !$AllowNone) {
            Write-Error "No container found for $Message." -Category ObjectNotFound -ErrorId 'ContainerNotFound' -TargetObject $TargetObject
        }

        $Containers
    }
}
function Get-DockerImageInternal {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Name,

        [Parameter()]
        [string]
        $Tag,

        [Parameter()]
        [string[]]
        $Id,

        [Parameter()]
        [string[]]
        $FullName,

        [Parameter()]
        [string]
        $Context,

        [Parameter()]
        [switch]
        $EscapeId
    )

    process {
        $Parameters = @{}
        if ($Id) {
            $Parameters['Id'] = $Id | ForEach-Object {
                if ($EscapeId) {
                    [WildcardPattern]::Escape($_)
                }
                else {
                    $_
                }
            }
        }
        if ($Name) {
            $Parameters['Name'] = $Name
            $Parameters['Tag'] = $Tag
        }
        if ($FullName) {
            $Parameters['FullName'] = $FullName
        }
        if ($Context) {
            $Parameters['Context'] = $Context
        }

        Get-DockerImage @Parameters
    }
}
function Get-DockerNetworkInternal {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::None,
        PositionalBinding = $false
    )]
    [OutputType([DockerNetwork])]
    param(
        [Parameter()]
        [string[]]
        $Name,

        [Parameter()]
        [string[]]
        $Id,

        [Parameter()]
        [switch]
        $EscapeId,

        [Parameter()]
        [string]
        $Context
    )
    process {
        $Parameters = @{}

        if ($Name) {
            $Parameters['Name'] = $Name
        }
        if ($Id) {
            $Parameters['Id'] = $Id | ForEach-Object {
                if ($EscapeId) {
                    [WildcardPattern]::Escape($_)
                }
                else {
                    $_
                }
            }
        }
        if ($Context) {
            $Parameters['Context'] = $Context
        }

        Get-DockerNetwork @Parameters
    }
}
$Docker = (Get-Command 'docker' -ErrorAction Stop).Path
function Invoke-Docker {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromRemainingArguments)]
        [string[]]
        $ArgumentList,

        [Parameter()]
        [ValidateDockerContext()]
        [string]
        $Context
    )

    process {
        $List = [List[string]]$ArgumentList
        if ($Context) {
            $List.Insert(0, '--context')
            $List.Insert(1, $Context)
        }
        Write-Debug "$Docker $List"
        & $Docker $List
    }
}
function Test-MultipleWildcard {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $WildcardPattern,

        [Parameter()]
        [string[]]
        $ActualValue
    )
    process {
        if (!$WildcardPattern) {
            return $true
        }
        if (!$ActualValue) {
            return $false
        }

        foreach ($w in $WildcardPattern) {
            foreach ($a in $ActualValue) {
                if ($a -like $w) {
                    return $true
                }
            }
        }
        return $false
    }
}
function Connect-DockerContainer {
    [CmdletBinding()]
    [OutputType([System.Management.Automation.Internal.AutomationNull])]
    [Alias('ccdc')]
    param(
        [Parameter(Mandatory)]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string]
        $ContainerName,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        Invoke-Docker attach $ContainerName -Context $Context
    }
}
function Enter-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false
    )]
    [OutputType([System.Management.Automation.Internal.AutomationNull])]
    [Alias('etdc')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [Alias('ContainerName')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string]
        $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [Alias('Container', 'ContainerId')]
        [string]
        $Id,

        [Parameter(Mandatory, Position = 1)]
        [ArgumentCompleter([DockerBinArgumentCompleter])]
        [string[]]
        $Command,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $Container = Get-DockerContainerSingle -Name $Name -Id $Id -Context $Context
        if (!$?) { return }

        if ($Container.State -ne 'running') {
            Write-Error "Cannot enter container $($Container.Id) ($($Container.Names)) because it is not running."
            return
        }

        $ArgumentList = @(
            'exec'
            '-it'
            $Container.Id
            $Command
        )

        Invoke-Docker -ArgumentList $ArgumentList -Context $Context
    }
}
function Get-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false
    )]
    [Alias('gdc')]
    [OutputType([DockerContainer])]
    param(
        [Parameter(Position = 0, ParameterSetName = 'Name')]
        [ValidateNotNullOrEmpty()]
        [Alias('ContainerName')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerContainerCompleter])]
        [LowerCaseTransformation()]
        [string[]]
        $Name,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Id')]
        [ValidateNotNullOrEmpty()]
        [Alias('Container', 'ContainerId')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerContainerCompleter])]
        [LowerCaseTransformation()]
        [string[]]
        $Id,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Name')]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string[]]
        $Label,

        [Parameter()]
        [ValidateSet('running', 'created', 'restarting', 'removing', 'paused', 'exited', 'dead')]
        [Alias('Status')]
        [LowerCaseTransformation()]
        [string[]]
        $State,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        [List[string]]$cl = @(
            'container'
            'list'
            '--no-trunc'
            '--format'
            '{{ json . }}'
            '--all'
        )

        $ReportNotMatched = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)

        foreach ($s in $State) {
            $cl.Add('--filter')
            $cl.Add("status=$s")
        }

        foreach ($n in $Name) {
            if (![WildcardPattern]::ContainsWildcardCharacters($n)) {
                [void]$ReportNotMatched.Add($n)
                
                $cl.Add('--filter')
                $cl.Add("name=$n")

                continue
            }

            # since --filter x=y matches ANY filter x, we can just add the longest part
            # here (least likely to match other filters) and provide specific PowerShell
            # filtering later
            $filter = $n -split '[*?[\]]' | Sort-Object -Property Length -Descending | Select-Object -First 1
            $cl.Add('--filter')
            $cl.Add("name=$filter")
        }

        foreach ($l in $Label) {
            # Label filter does not support partial match
            if (![WildcardPattern]::ContainsWildcardCharacters($l)) {
                $cl.Add('--filter')
                $cl.Add("label=$w")
            }
        }

        $IdFilter = [List[string]]::new()
        $UseIdFilter = $true
        foreach ($i in $Id) {
            if (![WildcardPattern]::ContainsWildcardCharacters($i)) {
                [void]$ReportNotMatched.Add($i)
                $IdFilter.Add($i)
                continue
            }
            if (!$UseIdFilter) {
                continue
            }
            # filter id=x only matches 'x...', not '...x'. We can therefore only
            # filter from the beginning of the string to the first wildcard character
            $FilterParts = $i -split '[*?[\]]'
            if ($FilterParts[0].Length -eq 0) {
                $UseIdFilter = $false
                continue
            }
            else {
                $IdFilter.Add($FilterParts[0])
            }
        }

        if ($UseIdFilter) {
            foreach ($i in $IdFilter) {
                $cl.Add('--filter')
                $cl.Add("id=$i")
            }
        }


        Invoke-Docker -ArgumentList $cl -Context $Context | ForEach-Object {
            [DockerContainer]$pso = $_ | ConvertFrom-Json
            $pso.PSObject.Members.Add([PSNoteProperty]::new('PSDockerContext', $Context))

            if (-not (Test-MultipleWildcard -WildcardPattern $Name -ActualValue $pso.Names)) {
                return
            }
            if (-not (Test-MultipleWildcard -WildcardPattern $Id -ActualValue $pso.Id)) {
                return
            }
            if (-not (Test-MultipleWildcard -WildcardPattern $Label -ActualValue $pso.Labels)) {
                return
            }

            $ToRemove = if ($PSCmdlet.ParameterSetName -eq 'Id') { $pso.Id } else { $pso.Names }
            foreach ($removable in $ToRemove) {
                [void]$ReportNotMatched.Remove($removable)
            }

            $pso
        }

        foreach ($r in $ReportNotMatched) {
            $ErrorId = if ($Id) { 'Id' } else { 'Name' }
            $WriteError = @{
                Message      = "The docker container '$r' was not found."
                Exception    = [ItemNotFoundException]'The docker container was not found.'
                Category     = 'ObjectNotFound'
                ErrorId      = "Container${ErrorId}NotFound"
                TargetObject = $r
            }
            Write-Error @WriteError
        }
    }
}
function Get-DockerContainerLog {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        PositionalBinding = $false,
        RemotingCapability = [RemotingCapability]::OwnedByCommand
    )]
    [OutputType([string])]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [Alias('ContainerName')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string]
        $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')]
        [Alias('Container', 'ContainerId')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string]
        $Id,

        [Parameter()]
        [ArgumentCompleter([DateTimeArgumentCompleter])]
        [DateTime]
        $Since,

        [Parameter()]
        [ArgumentCompleter([DateTimeArgumentCompleter])]
        [DateTime]
        $Until,

        [Parameter()]
        [Alias('Tail')]
        [ArgumentCompleter([NumericArgumentCompleter])]
        [int]
        $Last,

        [Parameter()]
        [switch]
        $Follow,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $Container = Get-DockerContainerSingle -Name $Name -Id $Id -Context $Context
        if (!$?) { return }

        $ArgumentList = @(
            'container'
            'logs'
            $Container.Id
            '--timestamps'
            '--details'
        )

        if ($Follow) {
            $ArgumentList += '--follow'
        }

        if ($Last) {
            $ArgumentList += '--tail'
            $ArgumentList += $Last
        }

        if ($Since) {
            $ArgumentList += '--since'
            $ArgumentList += $Since.ToString('yyyy-MM-ddTHH:mm:ss')
        }

        if ($Until) {
            $ArgumentList += '--until'
            $ArgumentList += $Until.ToString('yyyy-MM-ddTHH:mm:ss')
        }

        Write-Debug "$ArgumentList"
        Invoke-Docker -ArgumentList $ArgumentList -Context $Context
    }
}
function New-DockerContainer {
    [CmdletBinding(
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerContainer])]
    [Alias('ndc')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $ImageName,

        [Parameter(Position = 1)]
        [ValidateNotNullOrEmpty()]
        [Alias('ContainerName')]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $Name,

        [Parameter()]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $HostName,

        [Parameter()]
        [ArgumentCompleter([EmptyIpAddressArgumentCompleter])]
        [IPAddress]
        $IPAddress,

        [Parameter()]
        [switch]
        $Interactive,

        [Parameter()]
        [ArgumentCompleter([EmptyHashtableArgumentCompleter])]
        [Hashtable]
        $Labels,

        [Parameter()]
        [ArgumentCompleter([EmptyHashtableArgumentCompleter])]
        [Hashtable]
        $Environment,

        [Parameter()]
        [ValidateSet('always', 'missing', 'never')]
        [string]
        $PullBehavior,

        [Parameter()]
        [switch]
        $ReadOnly,

        [Parameter()]
        [switch]
        $AutoRemove,

        [Parameter()]
        [ArgumentCompleter([NumericArgumentCompleter])]
        [int]
        $TimeoutSeconds,

        [Parameter()]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $WorkingDirectory,

        # Any additional parameters to pass to the docker cli
        [Parameter()]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string[]]
        $Parameters,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        [List[string]]$ArgumentList = @(
            'container'
            'create'
        )

        if ($Name) {
            $ArgumentList.Add('--name')
            $ArgumentList.Add($Name)
        }

        if ($HostName) {
            $ArgumentList.Add('--hostname')
            $ArgumentList.Add($HostName)
        }

        if ($IPAddress) {
            $ArgumentList.Add('--ip')
            $ArgumentList.Add($IPAddress)
        }

        if ($Interactive) {
            $ArgumentList.Add('--interactive')
        }

        if ($Labels) {
            foreach ($k in $Labels.Keys) {
                $ArgumentList.Add('--label')
                $ArgumentList.Add("$k=$($Labels[$k])")
            }
        }

        if ($Environment) {
            foreach ($k in $Environment.Keys) {
                $ArgumentList.Add('--env')
                $ArgumentList.Add("$k=$($Environment[$k])")
            }
        }

        if ($PullBehavior) {
            $ArgumentList.Add('--pull')
            $ArgumentList.Add($PullBehavior)
        }

        if ($ReadOnly) {
            $ArgumentList.Add('--read-only')
        }

        if ($AutoRemove) {
            $ArgumentList.Add('--rm')
        }

        if ($TimeoutSeconds) {
            $ArgumentList.Add('--timeout')
            $ArgumentList.Add($TimeoutSeconds)
        }

        if ($WorkingDirectory) {
            $ArgumentList.Add('--workdir')
            $ArgumentList.Add($WorkingDirectory)
        }

        if ($Parameters) {
            foreach ($p in $Parameters) {
                $ArgumentList.Add($p)
            }
        }

        $ArgumentList.Add($ImageName)

        if (!$PSCmdlet.ShouldProcess(
                "Creating container '$Name' from image '$ImageName'.",
                "Create container '$Name' from image '$ImageName'?",
                "docker $ArgumentList")) {
            return
        }

        $Id = Invoke-Docker -ArgumentList $ArgumentList -Context $Context 
        if ($?) {
            Get-DockerContainerInternal -Id $Id -Context $Context
        }
    }
}
function Remove-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')]
        [Alias('ContainerName')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')]
        [Alias('Container', 'ContainerId')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Id,

        [Parameter(Mandatory, ParameterSetName = 'Prune')]
        [Alias('Unused', 'Prune')]
        [switch]
        $StoppedContainers,

        [Parameter()]
        [switch]
        $Force,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        if ($StoppedContainers) {
            $ArgumentList = @(
                'container'
                'prune'
                if ($Force) { '--force' }
            )
            if ($PSCmdlet.ShouldProcess(
                    'Removing stopped containers.',
                    'Remove stopped containers?',
                    "docker $ArgumentList")) {
                Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Out-Null
            }
            return
        }
        $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId

        if ($Containers.Count -eq 0) {
            # If no containers, the user input wildcard(s) or an error was reported by internal Get
            Write-Verbose 'No containers to process.'
            return
        }

        $ArgumentList = @(
            'container'
            'rm'
            $Containers.Id
            if ($Force) { '--force' }
        )

        $ShouldProcessTarget = if ($Containers.Count -eq 1) {
            "container '$($Containers.Id)' ($($Containers.Names))"
        }
        else {
            "$($Containers.Count) containers"
        }

        if (!$PSCmdlet.ShouldProcess(
                "Removing $ShouldProcessTarget.",
                "Remove $ShouldProcessTarget?",
                "docker $ArgumentList")) {
            return;
        }
        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug
    }
}
function Rename-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerContainer])]
    [Alias('rndc')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [Alias('ContainerName')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string]
        $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [Alias('Container', 'ContainerId')]
        [string]
        $Id,

        [Parameter(Mandatory, Position = 1)]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $NewName,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context,

        [Parameter()]
        [switch]
        $PassThru
    )
    process {
        $Container = Get-DockerContainerSingle -Name $Name -Id $Id -Context $Context
        if (!$?) { return }

        if ($PSCmdlet.ShouldProcess(
                "Renaming docker container '$($Container.Id)' from '$($Container.Names)' to $($NewName).",
                "Rename docker container '$($Container.Id)' from '$($Container.Names)' to $($NewName)?",
                "docker $ArgumentList")) {
            Invoke-Docker rename $Container.Id $NewName -Context $Context
            if ($PassThru) {
                Get-DockerContainerSingle -Name $NewName -Context $Context
            }
        }
    }
}
function Restart-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerContainer])]
    [Alias('rtdc')]
    param(
        [Parameter(Position = 0, Mandatory, ParameterSetName = 'Name')]
        [Alias('ContainerName')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Name,

        [Parameter(ValueFromPipelineByPropertyName, Mandatory, ParameterSetName = 'Id')]
        [Alias('Container', 'ContainerId')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Id,

        [Parameter()]
        [ArgumentCompleter([NumericArgumentCompleter])]
        [int]
        $TimeoutSeconds,

        [Parameter()]
        [switch]
        $PassThru,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId

        if ($Containers.Count -eq 0) {
            # If no containers, the user input wildcard(s) or an error was reported by internal Get
            Write-Verbose 'No containers to process.'
            return
        }

        $ArgumentList = @(
            'container',
            'restart'
        )
        if ($TimeoutSeconds) {
            $ArgumentList += '--time'
            $ArgumentList += $TimeoutSeconds
        }
        $ArgumentList += $Containers.Id

        $ShouldProcessTarget = if ($Containers.Count -eq 1) { "container '$($Containers.Id)' ($($Containers.Names))" } else { "$($Containers.Count) containers" }
        if (!$PSCmdlet.ShouldProcess(
                "Restarting $ShouldProcessTarget.",
                "Restart $ShouldProcessTarget?",
                "docker $ArgumentList")) {
            return
        }

        # Stream results as they become available
        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object {
            if ($PassThru) {
                Get-DockerContainerInternal -Id $_ -Context $Context
            }
        }
    }
}
function Resume-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerContainer])]
    [Alias('rudc')]
    param(
        [Parameter(Position = 0, Mandatory, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [Alias('ContainerName')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Name,

        [Parameter(ValueFromPipelineByPropertyName, Mandatory, ParameterSetName = 'Id')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [Alias('Container', 'ContainerId')]
        [string[]]
        $Id,

        [Parameter()]
        [switch]
        $PassThru,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $Containers = Get-DockerContainerInternal -Id $Id -Name $Name -Context $Context -EscapeId

        # Ensure we have containers to process
        if ($Containers.Count -eq 0) {
            # If no containers, the user input wildcard(s) or an error was reported by internal Get
            Write-Verbose 'No containers to process.'
            return
        }

        if ($Containers.Count -gt 1) {
            $ContainerIdentification = "$($Containers.Count) containers"
        }
        else {
            $ContainerIdentification = "container $($Containers.Id) ($($Containers.Names))"
        }

        if (!$PSCmdlet.ShouldProcess(
                "Unpausing all processes in $ContainerIdentification.",
                "Unpause all processes in $ContainerIdentification?",
                "docker $ArgumentList"
            )) {
            return
        }

        Invoke-Docker unpause $Containers.Id -Context $Context | ForEach-Object {
            if ($PassThru) {
                Get-DockerContainerInternal -Id $_ -Context $Context
            }
        }
    }
}
function Start-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerContainer])]
    [Alias('sadc')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')]
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name+Interactive')]
        [Alias('ContainerName')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id+Interactive')]
        [Alias('Container', 'ContainerId')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Id,

        # Maps to the --attach and --interactive parameters. (In the context of PowerShell, it does not make
        # sense to stream output to the console without attaching to the container.)
        [Parameter(Mandatory, ParameterSetName = 'Id+Interactive')]
        [Parameter(Mandatory, ParameterSetName = 'Name+Interactive')]
        [switch]
        $Interactive,

        [Parameter(ParameterSetName = 'Id')]
        [Parameter(ParameterSetName = 'Name')]
        [switch]
        $PassThru,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId

        if ($Containers.Count -eq 0) {
            # If no containers, the user input wildcard(s) or an error was reported by internal Get
            Write-Verbose 'No containers to process.'
            return
        }

        $ArgumentList = @(
            'container'
            'start'
            if ($Interactive) { '--attach'; '--interactive' }
            $Containers.Id
        )

        $ShouldProcessTarget = if ($Containers.Count -eq 1) { "container '$($Containers.Id)' ($($Containers.Names))" } else { "$($Containers.Count) containers" }
        if (!$PSCmdlet.ShouldProcess(
                "Starting $ShouldProcessTarget.",
                "Start $ShouldProcessTarget?",
                "docker $ArgumentList"
            )) {
            return
        }

        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object {
            if ($PassThru) {
                Get-DockerContainerInternal -Id $_ -Context $Context
            }
        }
    }
}
function Stop-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerContainer])]
    [Alias('spdc')]
    param(
        [Parameter(Position = 0, Mandatory, ParameterSetName = 'Name')]
        [Alias('ContainerName')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Name,

        [Parameter(ValueFromPipelineByPropertyName, Mandatory, ParameterSetName = 'Id')]
        [Alias('Container', 'ContainerId')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Id,

        [Parameter()]
        [switch]
        $Force,

        [Parameter()]
        [ArgumentCompleter([NumericArgumentCompleter])]
        [int]
        $TimeoutSeconds,

        [Parameter()]
        [switch]
        $PassThru,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId

        if ($Containers.Count -eq 0) {
            # If no containers, the user input wildcard(s) or an error was reported by internal Get
            Write-Verbose 'No containers to process.'
            return
        }

        $StopOrKill = if ($Force) { 'kill' } else { 'stop' }
        $Time = if (!$Force -and $TimeoutSeconds -gt 0) { '--time'; $TimeoutSeconds }
        $ArgumentList = @(
            'container'
            $StopOrKill
            $Time
        )
        $ArgumentList += $Containers.Id

        $ShouldProcessTarget = if ($Containers.Count -eq 1) { "container '$($Containers.Id)' ($($Containers.Names))" } else { "$($Containers.Count) containers" }
        if (!$PSCmdlet.ShouldProcess(
                "$StopOrKill $ShouldProcessTarget.",
                "$StopOrKill $ShouldProcessTarget?",
                "docker $ArgumentList"
            )) {
            return
        }

        # Stream results as they become available
        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object {
            if ($PassThru) {
                Get-DockerContainerInternal -Id $_ -Context $Context
            }
        }
    }
}
function Suspend-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerContainer])]
    [Alias('ssdc')]
    param(
        [Parameter(Position = 0, Mandatory, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [Alias('ContainerName')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Name,

        [Parameter(ValueFromPipelineByPropertyName, Mandatory, ParameterSetName = 'Id')]
        [Alias('Container', 'ContainerId')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Id,

        [Parameter()]
        [switch]
        $PassThru,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $Containers = Get-DockerContainerInternal -Id $Id -Name $Name -Context $Context -EscapeId

        if ($Containers.Count -eq 0) {
            # If no containers, the user input wildcard(s) or an error was reported by internal Get
            Write-Verbose 'No containers to process.'
            return
        }

        if ($Containers.Count -gt 1) {
            $ContainerIdentification = "$($Containers.Count) containers"
        }
        else {
            $ContainerIdentification = "container $($Containers.Id) ($($Containers.Names))"
        }

        if (!$PSCmdlet.ShouldProcess(
                "Pausing all processes in $ContainerIdentification.",
                "Pause all processes in $ContainerIdentification?",
                "docker $ArgumentList"
            )) {
            return
        }

        Invoke-Docker pause $Containers.Id -Context $Context | ForEach-Object {
            if ($PassThru) {
                Get-DockerContainerInternal -Id $_ -Context $Context
            }
        }
    }
}
function Wait-DockerContainer {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false
    )]
    [OutputType([System.Management.Automation.Internal.AutomationNull])]
    [Alias('wdc')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [Alias('ContainerName')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')]
        [Alias('Container', 'ContainerId')]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $Id,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    begin {
        $ContainerIds = [HashSet[string]]::new()
    }
    process {
        $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -EscapeId -Context $Context

        foreach ($Container in $Containers) {
            $ContainerIds.Add($Container.Id)
        }
    }
    end {
        if ($Containers.Count -eq 0) {
            Write-Verbose 'No containers to process.'
            return
        }
        $ArgumentList = @('wait'; $Containers.Id)
        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug
    }
}
function Get-DockerContext {
    [CmdletBinding(
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false
    )]
    [OutputType([DockerContext])]
    [Alias('gdcx')]
    param(
        [Parameter(Position = 0)]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [Alias('ContextName')]
        [ArgumentCompleter([DockerContextCompleter])]
        [string[]]
        $Name
    )
    process {
        $ReportNotMatched = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
        foreach ($i in $Name) {
            if (![WildcardPattern]::ContainsWildcardCharacters($i)) {
                [void]$ReportNotMatched.Add($i)
            }
        }

        Invoke-Docker context list --format '{{ json . }}' | ForEach-Object {
            [DockerContext]$pso = ConvertFrom-Json $_

            if (-not (Test-MultipleWildcard -WildcardPattern $Name -ActualValue $pso.Name)) {
                return
            }

            [void]$ReportNotMatched.Remove($pso.Name)
            $pso
        }

        foreach ($Unmatched in $ReportNotMatched) {
            Write-Error "No context found with name '$Unmatched'." -Category ObjectNotFound -ErrorId 'ContextNotFound' -TargetObject $Unmatched
        }
    }
}
function New-DockerContext {
    [CmdletBinding(
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess = $true,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerContext])]
    [Alias('ndcx')]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $Name,

        [Parameter(Mandatory)]
        [Alias('DockerEndpoint', 'Host')]
        [ValidatePattern('^[^,]+$')]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $DockerHost,

        [Parameter()]
        [Alias('swarm', 'kubernetes', 'all')]
        [string]
        $DefaultStackOrchestrator,

        [Parameter()]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $Description
    )
    process {
        $ArgumentList = @(
            'context'
            'create'
            $Name
            if ($DefaultStackOrchestrator) { '--default-stack-orchestrator'; $DefaultStackOrchestrator }
            if ($Description) { '--description'; $Description }
            if ($DockerHost) { '--docker'; "host=$DockerHost" }
            if ($Kubernetes) { '--kubernetes'; $Kubernetes }
        )

        if ($PSCmdlet.ShouldProcess(
                "Creating docker context '$Name' with host '$($DockerHost)'.",
                "Create docker context '$Name' with host '$($DockerHost)'?",
                "docker $ArgumentList"
            )) {
            Invoke-Docker -ArgumentList $ArgumentList 2>&1 | Write-Debug
            if ($?) {
                Get-DockerContext -Name $Name
            }
        }
    }
}
function Remove-DockerContext {
    [CmdletBinding(
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([System.Management.Automation.Internal.AutomationNull])]
    [Alias('rdcx')]
    param(
        [Parameter(Position = 0, Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('ContextName')]
        [ArgumentCompleter([DockerContextCompleter])]
        [string[]]
        $Name
    )
    begin {
        $RemoveContexts = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
    }
    process {
        $Contexts = Get-DockerContext -Name $Name

        foreach ($Context in $Contexts) {
            if ($Context.Current) {
                $WriteError = @{
                    Message           = "The docker context '$($Context.Name)' cannot be removed because it is currently in use."
                    Exception         = [InvalidOperationException]'The docker context cannot be removed because it is currently in use.'
                    RecommendedAction = 'Change the current docker context with ''Use-DockerContext'' and try again.'
                    TargetObject      = $Context
                    ErrorId           = 'ContextInUse'
                    Category          = 'ResourceBusy'
                }
                Write-Error @WriteError
                continue
            }

            if ($RemoveContexts.Contains($Context.Name)) {
                Write-Warning "Context '$($Context.Name)' is already being removed."
                continue
            }

            if ($PSCmdlet.ShouldProcess(
                    "Removing docker context '$($Context.Name)'.",
                    "Remove docker context '$($Context.Name)'?",
                    "docker context remove $($Context.Name)"
                )) {
                [void]$RemoveContexts.Add($Context.Name)
            }
        }

    }
    end {
        if ($RemoveContexts.Count -eq 0) {
            return
        }
        $ArgumentList = @(
            'context'
            'remove'
            $RemoveContexts
        )
        Invoke-Docker -ArgumentList $ArgumentList | Write-Debug
    }
}
function Use-DockerContext {
    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false
    )]
    [OutputType([DockerContext], ParameterSetName = 'Default')]
    [OutputType([System.Management.Automation.Internal.AutomationNull], ParameterSetName = 'ScriptBlock')]
    [Alias('udcx')]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [Alias('ContextName')]
        [ValidateDockerContext()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Name,

        [Parameter(Mandatory, Position = 1, ParameterSetName = 'ScriptBlock')]
        [ValidateNotNull()]
        [ArgumentCompleter([EmptyScriptBlockArgumentCompleter])]
        [ScriptBlock]
        $ScriptBlock,

        [Parameter(ParameterSetName = 'Default')]
        [switch]
        $PassThru
    )
    process {
        if ($ScriptBlock) {
            $Context = Invoke-Docker context show
            Invoke-Docker context use $Name | Write-Debug
            try {
                & $ScriptBlock
            }
            finally {
                Invoke-Docker context use $Context
            }
        }
        else {
            Invoke-Docker context use $Name | Write-Debug
            if ($? -and $PassThru) {
                Get-DockerContext -Name $Name
            }
        }
    }
}
function Build-DockerImage {
    [CmdletBinding(
        SupportsShouldProcess,
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerImage])]
    [Alias('bddi')]
    param(
        [Parameter()]
        [string]
        $Path = '.',

        [Parameter()]
        [string]
        $DockerFile = './Dockerfile',

        # Tags are not mandatory by the docker engine so this argument may be $null,
        # but personally I always want a tag and if I forget one I'm annoyed at myself.
        [Parameter(Mandatory)]
        [AllowNull()]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string[]]
        $Tag,

        [Parameter()]
        [switch]
        $NoCache,

        [Parameter()]
        [switch]
        $Pull,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context,

        [Parameter()]
        [Alias('Dns', 'CustomDns')]
        [ArgumentCompleter([EmptyHashtableArgumentCompleter])]
        [DockerBuildAddHostTransformation()]
        [Dictionary[string, ipaddress]]
        $AddHosts,

        [Parameter()]
        [Alias('BuildArgs')]
        [ArgumentCompleter([EmptyHashtableArgumentCompleter])]
        [hashtable]
        $Parameters,

        [Parameter()]
        [switch]
        $PassThru
    )
    process {
        $ArgumentList = @(
            'image'
            'build'
            $Path
            if ($DockerFile) { '--file'; $Dockerfile }
            if ($NoCache) { '--no-cache' }
            if ($Pull) { '--pull' }
            if ($Tag) { $Tag | ForEach-Object { '--tag'; $_ } }
            if ($AddHosts) { $AddHosts.Keys | ForEach-Object { '--add-host'; "${_}:$($AddHosts[$_])" } }
            if ($Parameters) { $Parameters.Keys | ForEach-Object { '--build-arg'; "${_}=$($Parameters[$_])" } }
            '--quiet'
        )

        # Oh how I wish there were a way to write progress AND get the id of the final image
        if ($PassThru) {
            $Id = Invoke-Docker -ArgumentList $ArgumentList -Context $Context
            if ($?) {
                Get-DockerImage -Id $Id
            }
        }
        else {
            Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug
        }

    }
}
function Copy-DockerImage {
    [CmdletBinding(
        SupportsShouldProcess,
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        ConfirmImpact = [ConfirmImpact]::Low,
        PositionalBinding = $false
    )]
    [OutputType([DockerImage])]
    [Alias('cpdi')]
    param(
        # Full name of the source image
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'FullName')]
        [SupportsWildcards()]
        [Alias('Reference', 'SourceReference', 'SourceFullName', 'SourceImage', 'Source')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $FullName,

        # FullName for the destination image (the copy to be created)
        [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
        [Alias('TargetImage', 'TargetName', 'DestinationFullName', 'DestinationImage', 'DestinationReference', 'TargetReference')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $DestinationName,

        # Id of the source image
        [Parameter(Mandatory, ParameterSetName = 'Id')]
        [Alias('ImageId')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $Id,

        [Parameter()]
        [switch]
        $PassThru,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    dynamicparam {
        [string]$DestinationName = $PSBoundParameters['DestinationName'];

        if ($DestinationName.Contains(':')) {
            return $null
        }

        $DynamicParameters = [RuntimeDefinedParameterDictionary]::new()
        $DynamicParameters.Add('DestinationTag', [RuntimeDefinedParameter]::new(
                'DestinationTag',
                [string],
                [Collection[Attribute]]@(
                    [ParameterAttribute]@{
                        Mandatory                       = $true
                        Position                        = 2
                        ValueFromPipelineByPropertyName = $true
                    },
                    [AliasAttribute]::new('Tag')
                )
            ))
        return $DynamicParameters
    }
    process {
        $Images = Get-DockerImageInternal -Id $Id -FullName $FullName -EscapeId -Context $Context | Sort-Object -Property Id -Unique

        # Handle no images
        if ($Images.Count -eq 0) {
            Write-Verbose 'No images to process.'
            return
        }

        # Handle ambiguous images
        if ($Images.Count -gt 1) {
            $Message = if ($Id) { "Id '$Id'" } else { "Name '$FullName'" }
            $TargetObject = if ($Id) { $Id } else { $FullName }
            Write-Error "More than one image found for $Message." -Category InvalidArgument -ErrorId 'AmbiguousImage' -TargetObject $TargetObject
            return
        }

        # Handle dynamic parameter in case someone wants to provide destination name and tag as separate arguments
        if ($PSBoundParameters['DestinationTag']) {
            $DestinationName = "${DestinationName}:$($PSBoundParameters['DestinationTag'])"
        }

        # ShouldProcess?
        $ResolvedImage = $Images[0]
        if (!$PSCmdlet.ShouldProcess(
                "Creating new tag '$DestinationName' for image '$($ResolvedImage.FullName)' ($($ResolvedImage.Id)).",
                "Create new tag '$DestinationName' for image '$($ResolvedImage.FullName)' ($($ResolvedImage.Id))?",
                "docker image tag '$($ResolvedImage.Id)' '$DestinationName'"
            )) {
            return
        }

        # execute
        $ArgumentList = @(
            'image'
            'tag'
            $ResolvedImage.Id
            $DestinationName
        )

        Invoke-Docker -ArgumentList $ArgumentList -Context $Context

        # PassThru
        if ($? -and $PassThru) {
            Get-DockerImage -FullName $DestinationName -Context $Context
        }
    }
}
function Export-DockerImage {
    [CmdletBinding(
        DefaultParameterSetName = 'FullName',
        SupportsShouldProcess,
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        ConfirmImpact = [ConfirmImpact]::Low,
        PositionalBinding = $false
    )]
    [OutputType([System.IO.FileInfo])]
    [Alias('epdi')]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName, ParameterSetName = 'FullName')]
        [SupportsWildcards()]
        [Alias('Reference')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $FullName,

        [Parameter(Mandatory, ParameterSetName = 'Id')]
        [Alias('ImageId')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $Id,

        [Parameter(Mandatory, Position = 1)]
        [Alias('Path')]
        [string]
        $Destination,

        [Parameter()]
        [switch]
        $Force,

        [Parameter()]
        [switch]
        $PassThru,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $Images = Get-DockerImageInternal -Id $Id -FullName $FullName -EscapeId | Sort-Object -Property Id -Unique

        # Handle no images
        if ($Images.Count -eq 0) {
            Write-Verbose 'No images to process.'
            return
        }

        # Handle ambiguous images
        if ($Images.Count -gt 1) {
            $Message = if ($Id) { "Id '$Id'" } else { "Name '$FullName'" }
            $TargetObject = if ($Id) { $Id } else { $FullName }
            Write-Error "More than one image found for $Message." -Category InvalidArgument -ErrorId 'AmbiguousImage' -TargetObject $TargetObject
            return
        }

        # Resolve destination path
        $OutputPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Destination)
        if (!$?) {
            return
        }
        if (Test-Path $OutputPath -PathType Container) {
            Write-Error "Destination path '$Destination' is a directory." -Category InvalidArgument -ErrorId 'DestinationIsDirectory' -TargetObject $OutputPath
            return
        }
        if (!$Force -and (Test-Path $OutputPath -PathType Leaf)) {
            Write-Error "Destination path '$Destination' already exists." -Category InvalidArgument -ErrorId 'DestinationExists' -TargetObject $OutputPath
            return
        }

        # ShouldProcess?
        $ResolvedImage = $Images[0]
        if (!$PSCmdlet.ShouldProcess(
                "Exporting image '$($ResolvedImage.FullName)' ($($ResolvedImage.Id)) to '$Destination'.",
                "Export image '$($ResolvedImage.FullName)' ($($ResolvedImage.Id)) to '$Destination'?",
                "docker image save --output '$Outputpath' '$($ResolvedImage.Id)'"
            )) {
            return
        }

        Invoke-Docker image save --output $OutputPath $ResolvedImage.Id -Context $Context

        if ($PassThru) {
            Get-Item $OutputPath
        }
    }
}
function Find-DockerImage {
    [CmdletBinding(
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false
    )]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $Keyword,

        [Parameter()]
        [Alias('Automated')]
        [ArgumentCompleter([BooleanArgumentCompleter])]
        [Nullable[bool]]
        $IsAutomated,

        [Parameter()]
        [Alias('Official')]
        [ArgumentCompleter([BooleanArgumentCompleter])]
        [Nullable[bool]]
        $IsOfficial,

        [Parameter()]
        [Alias('Stars')]
        [ArgumentCompleter([NumericArgumentCompleter])]
        [int]
        $MinimumStars,

        [Parameter()]
        [ValidateRange(1, [int]::MaxValue)]
        [Alias('First', 'Take')]
        [ArgumentCompleter([NumericArgumentCompleter])]
        [int]
        $Limit = 100,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $ArgumentList = @(
            'search'
            '--no-trunc'
            "--limit=$Limit"
            '--format'
            '{{ json . }}'
            if ($IsAutomated.HasValue) { "--filter=is-automated=$IsAutomated" }
            if ($IsOfficial.HasValue) { "--filter=is-official=$IsOfficial" }
            if ($MinimumStars) { "--filter=stars=$MinimumStars" }
            $Keyword
        )
        $Count = 0
        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object {
            $pso = $_ | ConvertFrom-Json
            $pso.PSTypeNames.Insert(0, 'Docker.RemoteImage')
            $pso

            if ((++$Count) -eq $Limit -and !($PSBoundParameters.ContainsKey('Limit'))) {
                Write-Warning "The number of results has reached the default limit of $Limit. There may be more results available. Use the -Limit parameter to increase the limit."
            }
        }
    }
}
function Get-DockerImage {
    [CmdletBinding(
        DefaultParameterSetName = 'Search',
        PositionalBinding = $false,
        RemotingCapability = [RemotingCapability]::OwnedByCommand
    )]
    [OutputType([DockerImage])]
    [Alias('gdi')]
    param(
        [Parameter(ValueFromPipeline, Position = 0, ParameterSetName = 'Search')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerImageCompleter])]
        [string[]]
        $InputObject,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'FullName')]
        [SupportsWildcards()]
        [Alias('Reference')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string[]]
        $FullName,

        [Parameter(Position = 0, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [Alias('RepositoryName', 'ImageName')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string[]]
        $Name,

        [Parameter(Position = 1, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerImageCompleter])]
        [string[]]
        $Tag,

        [Parameter(ParameterSetName = 'Id')]
        [SupportsWildcards()]
        [Alias('ImageId')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string[]]
        $Id,

        [Parameter()]
        [Alias('All')]
        [switch]
        $IncludeIntermediateImages,

        [Parameter()]
        [Alias('Untagged')]
        [switch]
        $Dangling,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        [List[string]]$ArgumentList = @(
            'image',
            'list',
            '--no-trunc'
            '--format'
            '{{ json . }}'
            if ($IncludeIntermediateImages) { '--all' }
            if ($Dangling) { '--filter'; 'dangling=true' }
        )

        # Track unmatched filters
        $ReportNotMatched = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)

        foreach ($_ in $InputObject) {
            if ($_ -match '^[0-9a-f]{12}$' -or $_ -match '^sha256:[0-9a-f]64$') {
                $Id += $_
            }
            elseif ($_.Contains(':')) {
                $FullName += $_
            }
            else {
                $Name += $_
            }
        }

        foreach ($i in $FullName) {
            # it could be an id, probably of a nameless image
            if (!$i.StartsWith('sha256:')) {
                $ArgumentList += '--filter'
                $ArgumentList += "reference=$i"
            }
            if (![WildcardPattern]::ContainsWildcardCharacters($i)) {
                [void]$ReportNotMatched.Add($i)
            }
        }
        if ($Tag.Count -in @(0, 1) -and $Name.Count -gt 0) {
            $TagPattern = if ($Tag) { $Tag } else { '*' }
            $Name | ForEach-Object {
                $ArgumentList += '--filter'
                $ArgumentList += "reference=${_}:$TagPattern"
            }
        }

        foreach ($i in $Name) {
            if (![WildcardPattern]::ContainsWildcardCharacters($i)) {
                [void]$ReportNotMatched.Add($i)
            }
        }

        for ($i = 0; $i -lt $Id.Length; $i++) {
            # a 12-character hex string is the default displayed image id
            # is not the actual image's id but a pattern for it: handle
            # such appropriately

            if ($id[$i].Length -eq 12 -and ![WildcardPattern]::ContainsWildcardCharacters($id[$i])) {
                $id[$i] = "sha256:$($id[$i])*"
            }

            # Track unmatched filters
            if (![WildcardPattern]::ContainsWildcardCharacters($id[$i])) {
                [void]$ReportNotMatched.Add($id[$i])
            }
        }

        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object {
            [DockerImage]$pso = ConvertFrom-Json $_

            if (-not (Test-MultipleWildcard -WildcardPattern $Name -ActualValue $pso.Repository)) {
                return
            }

            if (-not (Test-MultipleWildcard -WildcardPattern $Tag -ActualValue $pso.Tag)) {
                return
            }

            if (-not (Test-MultipleWildcard -WildcardPattern $Id -ActualValue $pso.Id)) {
                return
            }

            if (-not (Test-MultipleWildcard -WildcardPattern $FullName -ActualValue $pso.FullName)) {
                return
            }

            [void]$ReportNotMatched.Remove($pso.Id)
            [void]$ReportNotMatched.Remove($pso.Repository)
            [void]$ReportNotMatched.Remove($pso.FullName)
            $pso.PSObject.Members.Add([PSNoteProperty]::new('PSDockerContext', $Context))

            $pso
        }

        foreach ($Unmatched in $ReportNotMatched) {
            Write-Error "No image found for $($PSCmdlet.ParameterSetName) '$Unmatched'." -Category ObjectNotFound -TargetObject $Unmatched -ErrorId 'ImageNotFound'
        }
    }
}
function Import-DockerImage {
    [CmdletBinding(
        DefaultParameterSetName = 'LiteralPath',
        SupportsShouldProcess,
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        ConfirmImpact = [ConfirmImpact]::Low,
        PositionalBinding = $false
    )]
    [OutputType([DockerImage])]
    [Alias('ipdi')]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Path')]
        [SupportsWildcards()]
        [Alias('FilePath')]
        [string]
        $Path,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'LiteralPath')]
        [Alias('PSPath')]
        [string]
        $LiteralPath,

        [Parameter()]
        [switch]
        $PassThru,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        if ($Path) {
            $FullPath = Resolve-Path -Path $Path
        }
        else {
            $FullPath = Resolve-Path -LiteralPath $LiteralPath
        }

        if (!$?) {
            return
        }

        # ShouldProcess
        if (!$PSCmdlet.ShouldProcess(
                "Importing image from '$FullPath'.",
                "Import image from '$FullPath'?",
                "docker image load --input '$FullPath'"
            )) {
            return
        }

        $ArgumentList = @(
            'image'
            'load'
            '--quiet'
            '--input'
            $FullPath
        )

        $Output = Invoke-Docker -ArgumentList $ArgumentList -Context $Context

        if ($? -and $PassThru) {
            $Id = $Output.Replace('Loaded image ID: ', '')
            Get-DockerImage -Id $Id
        }
    }
}
function Install-DockerImage {
    [CmdletBinding(
        DefaultParameterSetName = 'FullName',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerImage], ParameterSetName = 'FullName')]
    [OutputType([DockerImage], ParameterSetName = 'NameTag')]
    [OutputType([DockerImage], ParameterSetName = 'NameAllTags')]
    [OutputType([DockerImage], ParameterSetName = 'NameDigest')]
    [OutputType('Docker.PowerShell.CLI.DockerPullJob', ParameterSetName = 'FullNameJob', 'NameTagJob', 'NameAllTagsJob', 'NameDigestJob')]
    [Alias('isdi')]
    param(
        [Parameter(Position = 0, ParameterSetName = 'FullName')]
        [Parameter(Position = 0, ParameterSetName = 'FullNameJob')]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string[]]
        $FullName,
        
        [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameTag')]
        [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameAllTags')]
        [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameDigest')]
        [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameTagJob')]
        [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameAllTagsJob')]
        [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameDigestJob')]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameTag')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameTagJob')]
        [ValidateScript({ $_ -notmatch '[:@ ]' })]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $Tag,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameDigest')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameDigestJob')]
        [ValidateScript({ $_ -match '^(sha256:)?[0-9a-f]+$' })]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $Digest,

        [Parameter(Mandatory, ParameterSetName = 'NameAllTags')]
        [Parameter(Mandatory, ParameterSetName = 'NameAllTagsJob')]
        [switch]
        $AllTags,

        [Parameter()]
        [switch]
        $DisableContentTrust,

        [Parameter()]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $Platform,

        [Parameter()]
        [switch]
        $PassThru,

        [Parameter(Mandatory, ParameterSetName = 'FullNameJob')]
        [Parameter(Mandatory, ParameterSetName = 'NameTagJob')]
        [Parameter(Mandatory, ParameterSetName = 'NameAllTagsJob')]
        [Parameter(Mandatory, ParameterSetName = 'NameDigestJob')]
        [switch]
        $AsJob,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {

        $ArgumentList = @(
            'image'
            'pull'
            if ($DisableContentTrust) { '--disable-content-trust' }
            if ($Platform) { '--platform'; $Platform }
        )

        if ($Name -and $Tag) {
            $FullName = "${Name}:$Tag"
        }
        if ($Name -and $Digest) {
            $FullName = "$Name@$Digest"
        }

        foreach ($f in $FullName) {
            if (!$PSCmdlet.ShouldProcess(
                    "Installing image '$f'.",
                    "Install image '$f'?",
                    "docker $ArgumentList $f"
                )) {
                continue
            }
            $FullArgumentList = @(
                $ArgumentList
                $f
            )

            if ($AsJob) {
                Assert-DockerPullJob
                $Job = [Docker.PowerShell.CLI.DockerPullJob]::new(
                    $MyInvocation.Line,
                    $FullArgumentList
                )

                $PSCmdlet.JobRepository.Add($Job)
                $Job
            }
            else {
                Invoke-Docker -ArgumentList $FullArgumentList -Context $Context | Tee-Object -Variable DockerOutput | Write-Debug

                if ($? -and $PassThru) {
                    Get-DockerImage -FullName $DockerOutput[-1]
                }
            }
        }
    }
}
function Publish-DockerImage {
    [CmdletBinding(
        DefaultParameterSetName = 'FullName',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerImage], ParameterSetName = 'FullName')]
    [OutputType('Docker.PowerShell.CLI.DockerPushJob', ParameterSetName = 'FullNameJob')]
    [Alias('pbdi')]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'FullName')]
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'FullNameJob')]
        [Alias('Reference')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string[]]
        $FullName,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Name')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameJob')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'AllTags')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'AllTagsJob')]
        [Alias('ImageName', 'RepositoryName')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 1, ParameterSetName = 'Name')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 1, ParameterSetName = 'NameJob')]
        [ValidateScript({ $_ -notmatch '[:@ ]' })]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $Tag,

        [Parameter(Mandatory, ParameterSetName = 'AllTags')]
        [Parameter(Mandatory, ParameterSetName = 'AllTagsJob')]
        [switch]
        $AllTags,

        [Parameter(Mandatory, ParameterSetName = 'Id')]
        [Parameter(Mandatory, ParameterSetName = 'IdJob')]
        [Alias('ImageId')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $Id,

        [Parameter()]
        [switch]
        $DisableContentTrust,

        [Parameter(ParameterSetName = 'Id')]
        [Parameter(ParameterSetName = 'AllTags')]
        [Parameter(ParameterSetName = 'FullName')]
        [Parameter(ParameterSetName = 'Name')]
        [switch]
        $PassThru,

        [Parameter(Mandatory, ParameterSetName = 'NameJob')]
        [Parameter(Mandatory, ParameterSetName = 'AllTagsJob')]
        [Parameter(Mandatory, ParameterSetName = 'FullNameJob')]
        [Parameter(Mandatory, ParameterSetName = 'IdJob')]
        [switch]
        $AsJob,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    begin {
        $HasPublishedFullName = [HashSet[string]]::new()
    }
    process {
        $ArgumentList = @(
            'image'
            'push'
            if ($DisableContentTrust) { '--disable-content-trust' }
            if ($AllTags) { '--all-tags' }
        )

        if ($Name -and $Tag) {
            $FullName = "${Name}:$Tag"
        }
        elseif ($Name) {
            $FullName = $Name
        }
        if ($Id) {
            $FullName = Get-DockerImageInternal -Id $Id -Context $Context -EscapeId | ForEach-Object FullName
        }

        foreach ($f in $FullName) {
            # Only publish once, in case of duplicate in pipeline
            if ($HasPublishedFullName.Contains($f)) {
                Write-Warning "Image '$f' has already just been published."
                continue
            }
            else {
                [void]$HasPublishedFullName.Add($f)
            }

            # Make sure the image exists
            if ($AllTags) {
                $Image = Get-DockerImageInternal -Name $f -Context $Context
            }
            else {
                $Image = Get-DockerImageInternal -FullName $f -Context $Context
            }
            if (!$? -or !$Image) {
                continue
            }

            if (!$PSCmdlet.ShouldProcess(
                    "Publishing image '$f'.",
                    "Publish image '$f'?",
                    "docker $ArgumentList $f"
                )) {
                continue
            }

            $FullArgumentList = @(
                $ArgumentList
                $f
            )

            if ($AsJob) {
                Assert-DockerPushJob
                $Job = [Docker.PowerShell.CLI.DockerPushJob]::new(
                    $MyInvocation.Line,
                    $FullArgumentList
                )

                $PSCmdlet.JobRepository.Add($Job)
                $Job
            }
            else {
                Invoke-Docker -ArgumentList $FullArgumentList -Context $Context | Write-Debug
                if ($PassThru) {
                    $Image
                }
            }
        }
    }
}
function Remove-DockerImage {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([System.Management.Automation.Internal.AutomationNull])]
    [Alias('rdi')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'FullName', ValueFromPipelineByPropertyName)]
        [SupportsWildcards()]
        [Alias('Reference')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string[]]
        $FullName,

        [Parameter(Mandatory, ParameterSetName = 'Name')]
        [Alias('RepositoryName', 'ImageName')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $Name,

        [Parameter(Mandatory, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerImageCompleter])]
        [string]
        $Tag,

        [Parameter(Mandatory, ParameterSetName = 'Id')]
        [Alias('ImageId')]
        [ArgumentCompleter([DockerImageCompleter])]
        [string[]]
        $Id,

        [Parameter()]
        [switch]
        $Force,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    begin {
        $ArgumentList = [List[string]]::new()
        $ArgumentList.Add('image')
        $ArgumentList.Add('remove')
        if ($Force) {
            $ArgumentList.Add('--force')
        }
    }
    process {
        $Images = Get-DockerImageInternal -Name $Name -Tag $Tag -Id $Id -FullName $FullName -Context $Context -EscapeId

        if ($Images.Count -eq 0) {
            Write-Verbose 'No images to process.'
        }

        foreach ($Image in $Images) {
            if ($PSCmdlet.ShouldProcess(
                    "Removing docker image $($Image.FullName) ($($Image.Id)).",
                    "Remove docker image $($Image.FullName) ($($Image.Id))?",
                    "docker image remove $($Image.FullName)"
                )) {
                $ArgumentList.Add($Image.FullName)
            }
        }
    }
    end {
        if ($ArgumentList.Count -eq 2) {
            # no images
            return
        }
        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug
    }
}
function Add-DockerNetworkConnection {
    [CmdletBinding(
        DefaultParameterSetName = 'Name+Name',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [Alias('adnc')]
    [OutputType([DockerContainerNetworkConnection])]
    param(
        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'Name+Id'
        )]
        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'Name+Name'
        )]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $NetworkName,

        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'Id+Name'
        )]
        [Parameter(
            Mandatory,
            Position = 1,
            ParameterSetName = 'Name+Name'
        )]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $ContainerName,

        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Id+Id'
        )]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $NetworkId,

        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Name+Id'
        )]
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Id+Id'
        )]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $ContainerId,


        # Network-scoped alias for the container.
        [Parameter()]
        [string[]]
        $Alias,

        # IP address for the container on the network.
        [Parameter()]
        [ValidateScript({ $_.AddressFamily -in @('InterNetwork', 'InterNetworkv6' ) })]
        [IPAddress]
        $IPAddress,

        [Parameter()]
        [switch]
        $PassThru,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    begin {
        $PassThruConnections = [Dictionary[string, HashSet[string]]]::new([StringComparer]::OrdinalIgnoreCase)
    }
    process {
        $Networks = Get-DockerNetworkInternal -Name $NetworkName -Id $NetworkId -EscapeId
        $Containers = Get-DockerContainerInternal -Name $ContainerName -Id $ContainerId -EscapeId

        if ($Networks.Count -eq 0) {
            Write-Verbose 'No networks to process.'
            return
        }
        if ($Containers.Count -eq 0) {
            Write-Verbose 'No containers to process.'
            return
        }

        $RequiresSingle = $Alias -or $IPAddress
        if ($RequiresSingle -and $Containers.Count -gt 1) {
            throw [InvalidOperationException]::new('Cannot specify multiple containers when using -Alias or -IPAddress.')
        }

        foreach ($Network in $Networks) {
            foreach ($Container in $Containers) {

                $ArgumentList = @(
                    'network'
                    'connect'
                    foreach ($a in $Alias) { '--alias'; $a }
                    if ($IPAddress.AddressFamily -eq 'InterNetwork') { '--ip'; $IPAddress.ToString() }
                    if ($IPAddress.AddressFamily -eq 'InterNetworkv6') { '--ip6'; $IPAddress.ToString() }
                    $Network.Id
                    $Container.Id
                )

                if (!$PSCmdlet.ShouldProcess(
                        "Connecting docker container '$($Container.Name)' ($($Container.Id)) to network '$($Network.Name)' ($($Network.Id)).",
                        "Connect docker container '$($Container.Name)' ($($Container.Id)) to network '$($Network.Name)' ($($Network.Id))?",
                        "docker $ArgumentList"
                    )) {
                    continue
                }
                
                Invoke-Docker network connect $Network.Id $Container.Id -Context $Context

                if ($PassThru) {
                    if (!$PassThruConnections.ContainsKey($Container.Id)) {
                        $PassThruConnections[$Container.Id] = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
                    }
                    [void]$PassThruConnections[$Container.Id].Add($Network.Id)
                }
            }
        }
    }
    end {
        foreach ($Container in $PassThruConnections.Keys) {
            Get-DockerNetworkConnection -ContainerId $Container -NetworkId $PassThruConnections[$Container]
        }
    }
}
function Get-DockerNetwork {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::None,
        PositionalBinding = $false
    )]
    [OutputType([DockerNetwork])]
    [Alias('gdn')]
    param(
        [Parameter(Position = 0, ParameterSetName = 'Name')]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $Name,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Id')]
        [SupportsWildcards()]
        [Alias('NetworkId')]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $Id,

        [Parameter()]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $Driver,

        [Parameter()]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $Scope,

        [Parameter()]
        [ValidateSet('custom', 'builtin')]
        [string[]]
        $Type,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $ReportNotMatched = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)

        foreach ($i in $Id) {
            if (![WildcardPattern]::ContainsWildcardCharacters($i)) {
                [void]$ReportNotMatched.Add($i)
            }
        }
        foreach ($n in $Name) {
            if (![WildcardPattern]::ContainsWildcardCharacters($n)) {
                [void]$ReportNotMatched.Add($n)
            }
        }

        $ArgumentList = @(
            'network'
            'list'
            '--format'
            '{{ json . }}'
            '--no-trunc'
            foreach ($i in $Id | ConvertTo-DockerWildcard) {
                '--filter'
                "id=$i"
            }
            foreach ($n in $Name | ConvertTo-DockerWildcard) {
                '--filter'
                "name=$n"
            }
            foreach ($d in $Driver) {
                '--filter'
                "driver=$d"
            }
            foreach ($s in $Scope) {
                '--filter'
                "scope=$s"
            }
            foreach ($t in $Type) {
                '--filter'
                "type=$t"
            }
        )

        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ConvertFrom-Json | ForEach-Object {
            $Network = [DockerNetwork]::new($_)
            if (-not (Test-MultipleWildcard -WildcardPattern $Name -ActualValue $Network.Name)) {
                return
            }
            if (-not (Test-MultipleWildcard -WildcardPattern $Id -ActualValue $Network.Id)) {
                return
            }

            [void]$ReportNotMatched.Remove($Network.Id)
            [void]$ReportNotMatched.Remove($Network.Name)

            $Network
        }

        foreach ($NotMatched in $ReportNotMatched) {
            $Exception = [ItemNotFoundException]::new(
                'The docker network was not found.'
            )
            $ErrorRecord = [ErrorRecord]::new(
                $Exception,
                'NetworkNotFound',
                [ErrorCategory]::ObjectNotFound,
                $NotMatched
            )
            $ErrorRecord.ErrorDetails = "No network found with the $($PSCmdlet.ParameterSetName) '$NotMatched'."
            Write-Error -ErrorRecord $ErrorRecord
        }
    }
}
function Get-DockerNetworkConnection {
    [CmdletBinding(
        DefaultParameterSetName = 'Name',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false
    )]
    [OutputType([DockerContainerNetworkConnection])]
    [Alias('gdnc')]
    param(
        [Parameter(Position = 0, ParameterSetName = 'Name')]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $NetworkName,

        [Parameter(Position = 1, ParameterSetName = 'Name')]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $ContainerName,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Parent')]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $ContainerId,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Parent')]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $NetworkId,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        $Context
    )
    begin {
        $EndpointIds = [HashSet[string]]::new()
    }
    process {
        $Containers = Get-DockerContainerInternal -Context $Context -Id $ContainerId -Name $ContainerName -ErrorAction SilentlyContinue -ErrorVariable DockerContainerErrors
        
        foreach ($e in $DockerContainerErrors) {
            $exn = [ItemNotFoundException]::new(
                "No docker network connection found for container '$($e.TargetObject)' because the container does not exist.",
                $exn.Exception
            )
            $er = [ErrorRecord]::new(
                $exn,
                'NetworkConnectionNotFound.Container',
                [ErrorCategory]::ObjectNotFound,
                $e.TargetObject
            )
            $PSCmdlet.WriteError($er)
        }

        if ($Containers.Count -eq 0) {
            return
        }

        $NotMatchedNetworkName = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
        $NetworkName.Where({ ![WildcardPattern]::ContainsWildcardCharacters($_) }).ForEach({ [void]$NotMatchedNetworkName.Add($_) })
        $NotMatchedEndpointId = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
        $EndpointId.Where({ ![WildcardPattern]::ContainsWildcardCharacters($_) }).ForEach({ [void]$NotMatchedEndpointId.Add($_) })
        $NotMatchedNetworkId = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
        $NetworkId.Where({ ![WildcardPattern]::ContainsWildcardCharacters($_) }).ForEach({ [void]$NotMatchedNetworkId.Add($_) })

        Invoke-Docker container inspect $Containers.Id `
        | ConvertFrom-Json `
        | ForEach-Object {
            $ConnectionContainerId = $_.Id
            $ConnectionContainerName = $_.Name
            $ConnectionImageId = $_.Image
            $ConnectionImageName = $_.Config.Image
            foreach ($Property in $_.NetworkSettings.Networks.PSObject.Properties) {
                $ConnectionNetworkName = $Property.Name
                $Connection = [DockerContainerNetworkConnection]::new(
                    $Property.Value,
                    $ConnectionContainerId,
                    ($ConnectionContainerName -replace '^/', ''),
                    $ConnectionImageId,
                    $ConnectionImageName,
                    $ConnectionNetworkName
                )
                $Connection.PSObject.Properties.Add([psnoteproperty]::new('PSDockerContext', $Context))

                if ($EndpointIds.Add($Connection.EndpointId) -and
                    (Test-MultipleWildcard -WildcardPattern $EndpointId -ActualValue $Connection.EndpointId) -and
                    (Test-MultipleWildcard -WildcardPattern $NetworkName -ActualValue $Connection.NetworkName) -and
                    (Test-MultipleWildcard -WildcardPattern $NetworkId -ActualValue $Connection.NetworkId)) {

                    [void]$NotMatchedEndpointId.Remove($Connection.EndpointId)
                    [void]$NotMatchedNetworkName.Remove($Connection.NetworkName)
                    [void]$NotMatchedNetworkId.Remove($Connection.NetworkId)
                        
                    $Connection
                }
            }
        }

        foreach ($i in $NotMatchedEndpointId) {
            Write-Error -Exception ([ItemNotFoundException]"No docker network connection found with with endpoint '$i'.") -Category ObjectNotFound -ErrorId 'NetworkConnectionNotFound.EndpointId' -TargetObject $i
        }
        foreach ($i in $NotMatchedNetworkName) {
            Write-Error -Exception ([ItemNotFoundException]"No docker network connection found for network '$i'.") -Category ObjectNotFound -ErrorId 'NetworkConnectionNotFound.NetworkName' -TargetObject $i
        }
        foreach ($i in $NotMatchedNetworkId) {
            Write-Error -Exception ([ItemNotFoundException]"No docker network connection found for network '$i'.") -Category ObjectNotFound -ErrorId 'NetworkConnectionNotFound.NetworkId' -TargetObject $i
        }
    }
}
function New-DockerNetwork {
    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        RemotingCapability = [RemotingCapability]::None,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([DockerNetwork])]
    [Alias('ndn')]
    param(
        [Parameter(Position = 0, Mandatory)]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string]
        $Name,

        [Parameter()]
        # [ValidateSet('bridge', 'host', 'none', 'overlay', 'macvlan', 'ipvlan', 'null')]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [PSDefaultValue(Value = 'bridge')]
        [string]
        $Driver,

        [Parameter()]
        [ValidateSet('local', 'global')]
        [string]
        $Scope,

        [Parameter()]
        [switch]
        $Attachable,

        [Parameter()]
        [switch]
        $Internal,

        [Parameter()]
        [switch]
        $IPv6,

        [Parameter()]
        [switch]
        $Ingress,

        # Driver-specific options
        [Parameter()]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string[]]
        $Options,

        [Parameter()]
        [ArgumentCompleter([EmptyIpAddressArgumentCompleter])]
        [ipaddress[]]
        $Gateway,

        # Subnet in CIDR format that represents a network segment
        [Parameter()]
        [ValidatePattern('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d+$')]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string[]]
        $Subnet,

        [Parameter()]
        [ValidatePattern('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d+$')]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string[]]
        $IpRange,

        [Parameter()]
        [Alias('AuxAddress')]
        [ArgumentCompleter([EmptyHashtableArgumentCompleter])]
        [DockerNetworkAuxAddressTransformation()]
        [Dictionary[string, HashSet[IPAddress]]]
        $AuxiliaryAddressMapping,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $ArgumentList = @(
            'network'
            'create'
            if ($Attachable) { '--attachable' }
            if ($AuxiliaryAddressMapping) {
                foreach ($k in $AuxiliaryAddressMapping.Keys) {
                    foreach ($v in $AuxiliaryAddressMapping[$k]) {
                        '--aux-address'
                        "$k=$v"
                    }
                }
            }
            if ($Driver) { '--driver'; $Driver.ToLower() }
            foreach ($g in $Gateway) {
                '--gateway'
                $g.ToString()
            }
            if ($Ingress) { '--ingress' }
            if ($Internal) { '--internal' }
            foreach ($i in $IpRange) {
                '--ip-range'
                $i
            }
            if ($IPv6) { '--ipv6' }
            if ($Options) {
                foreach ($o in $Options) {
                    '--opt'
                    $o
                }
            }
            if ($Scope) { '--scope'; $Scope.ToLower() }
            foreach ($s in $Subnet) { '--subnet'; $s }
            $Name
        )

        if (!$Driver) { $Driver = 'bridge' }
        if (!$PSCmdlet.ShouldProcess(
                "Creating docker network '$Name' with driver '$Driver'.",
                "Create docker network '$Name' with driver '$Driver'?",
                "docker $ArgumentList"
            )) {
            return
        }

        $Id = Invoke-Docker -ArgumentList $ArgumentList -Context $Context
        if ($?) {
            Get-DockerNetworkInternal -Id $Id -Context $Context
        }
    }
}
function Remove-DockerNetwork {
    [CmdletBinding(
        DefaultParameterSetName = 'Id',
        RemotingCapability = [RemotingCapability]::None,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [OutputType([System.Management.Automation.Internal.AutomationNull])]
    [Alias('rdn')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $Id,

        [Parameter(ParameterSetName = 'Prune')]
        [Alias('Prune')]
        [switch]
        $UnusedNetworks,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    begin {
        $RemoveNetworks = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
    }
    process {
        if ($Prune) {
            if (!$PSCmdlet.ShouldProcess(
                    'Removing unused docker networks.',
                    'Remove unused docker networks?',
                    'docker network prune'
                )) {
                return
            }

            Invoke-Docker network prune '--force' -Context $Context | Write-Debug
            return
        }

        $Networks = Get-DockerNetworkInternal -Name $Name -Id $Id -Context $Context -EscapeId

        foreach ($Network in $Networks) {
            if (!$PSCmdlet.ShouldProcess(
                    "Removing docker network '$($Network.Name)' ($($Network.Id)).",
                    "Remove docker network '$($Network.Name)' ($($Network.Id))?",
                    "docker network remove '$($Network.Id)'"
                )) {
                continue
            }

            [void]$RemoveNetworks.Add($Network.Id)
        }
    }
    end {
        if ($RemoveNetworks.Count -eq 0) {
            return
        }
        $ArgumentList = @(
            'network'
            'remove'
            $RemoveNetworks
        )
        Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug
    }
}
function Remove-DockerNetworkConnection {
    [CmdletBinding(
        DefaultParameterSetName = 'Name+Name',
        RemotingCapability = [RemotingCapability]::OwnedByCommand,
        PositionalBinding = $false,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    [Alias('rdnc')]
    [OutputType([System.Management.Automation.Internal.AutomationNull])]
    param(
        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'Name+Id'
        )]
        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'Name+Name'
        )]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $NetworkName,

        [Parameter(
            Mandatory,
            Position = 0,
            ParameterSetName = 'Id+Name'
        )]
        [Parameter(
            Mandatory,
            Position = 1,
            ParameterSetName = 'Name+Name'
        )]
        [SupportsWildcards()]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $ContainerName,

        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Id+Id'
        )]
        [ArgumentCompleter([DockerNetworkCompleter])]
        [string[]]
        $NetworkId,

        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Name+Id'
        )]
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Id+Id'
        )]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string[]]
        $ContainerId,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        $Networks = Get-DockerNetworkInternal -Name $NetworkName -Id $NetworkId -EscapeId
        $Containers = Get-DockerContainerInternal -Name $ContainerName -Id $ContainerId -EscapeId

        if ($Networks.Count -eq 0) {
            Write-Verbose 'No networks to process.'
            return
        }
        if ($Containers.Count -eq 0) {
            Write-Verbose 'No containers to process.'
            return
        }

        $RequiresSingle = $Alias -or $IPAddress
        if ($RequiresSingle -and $Containers.Count -gt 1) {
            throw [InvalidOperationException]::new('Cannot specify multiple containers when using -Alias or -IPAddress.')
        }

        foreach ($Network in $Networks) {
            foreach ($Container in $Containers) {

                $ArgumentList = @(
                    'network'
                    'connect'
                    $Network.Id
                    $Container.Id
                )

                if (!$PSCmdlet.ShouldProcess(
                        "Disconnecting docker container '$($Container.Name)' ($($Container.Id)) from network '$($Network.Name)' ($($Network.Id)).",
                        "Disconnect docker container '$($Container.Name)' ($($Container.Id)) from network '$($Network.Name)' ($($Network.Id))?",
                        "docker $ArgumentList"
                    )) {
                    continue
                }
                
                Invoke-Docker network disconnect $Network.Id $Container.Id -Context $Context
            }
        }
    }
}
function Get-DockerPath {
    [CmdletBinding()]
    param()
    process {
        Get-Item $Docker
    }
}
function Get-DockerVersion {
    [CmdletBinding(
        RemotingCapability = [RemotingCapability]::OwnedByCommand
    )]
    [OutputType('Docker.DockerVersion')]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    process {
        Invoke-Docker version --format '{{ json . }}' -Context $Context | ForEach-Object {
            $pso = $_ | ConvertFrom-Json
            $pso.PSTypeNames.Insert(0, 'Docker.DockerVersion')
            $pso.Client.PSTypeNames.Insert(0, 'Docker.ClientVersion')
            $pso.Server.PSTypeNames.Insert(0, 'Docker.ServerVersion')
            $ModuleVersionInfo = [pscustomobject]@{
                Version    = $MyInvocation.MyCommand.Module.Version
                Prerelease = $MyInvocation.MyCommand.Module.PrivateData.PSData.Prerelease
                PSTypeName = 'Docker.ModuleVersion'
            }
            $pso.PSObject.Members.Add([PSNoteProperty]::new('Module', $ModuleVersionInfo))
            $pso
        }
    }
}
function Invoke-DockerCommand {
    [CmdletBinding(
        RemotingCapability = [RemotingCapability]::OwnedByCommand
    )]
    param(
        [Parameter(Mandatory)]
        [ArgumentCompleter([DockerContainerCompleter])]
        [string]
        $ContainerName,

        [Parameter(ValueFromRemainingArguments)]
        [ArgumentCompleter([EmptyStringArgumentCompleter])]
        [string[]]
        $ArgumentList,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter([DockerContextCompleter])]
        [string]
        $Context
    )
    $PassArgumentList = [List[string]]::new($ArgumentList)
    $PassArgumentList.Insert(0, 'exec')
    $PassArgumentList.Insert(1, $ContainerName)
    Invoke-Docker -ArgumentList $PassArgumentList -Context $Context
}
function Set-DockerPath {
    [CmdletBinding(
        DefaultParameterSetName = 'LiteralPath',
        RemotingCapability = [RemotingCapability]::None,
        SupportsShouldProcess,
        ConfirmImpact = [ConfirmImpact]::Medium
    )]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Path')]
        [string]
        $Path,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'LiteralPath')]
        [Alias('PSPath')]
        [string]
        $LiteralPath,

        [Parameter()]
        [switch]
        $PassThru
    )
    process {
        if ($Path) {
            $ActualPath = Resolve-Path $Path -ErrorAction Stop
        }
        else {
            $ActualPath = Resolve-Path $LiteralPath -ErrorAction Stop
        }

        $ProviderPath = $ActualPath.ProviderPath

        # Test if the path is docker
        Write-Debug "$ProviderPath --version"
        $DockerVersionOutput = & $ProviderPath --version

        if ($DockerVersionOutput -notmatch 'Docker version') {
            $exn = [ArgumentException]'The provided path is not a valid docker executable.'
            $err = [ErrorRecord]::new(
                $exn,
                'InvalidDockerPath',
                [ErrorCategory]::InvalidArgument,
                $ActualPath
            )
            $err.ErrorDetails = "The provided path '$ProviderPath' is not a valid docker executable."
            $PSCmdlet.ThrowTerminatingError($err)
            return
        }

        if (!$PSCmdlet.ShouldProcess(
                "Setting docker executable to '$ProviderPath' for the 'Docker.PowerShell.CLI' module.",
                "Use the docker executable at '$ProviderPath' for the 'Docker.PowerShell.CLI' module?",
                'Set-DockerPath'
            )) {
            return
        }
        
        # Set the $Docker variable which contains the path to the docker executable
        Set-Variable -Name Docker -Value $ProviderPath -Scope 1

        if ($PassThru) {
            Get-Item -LiteralPath $ActualPath.Path
        }
    }
}

$ExportModuleMember = @{
    Function = @(
        'Connect-DockerContainer',
        'Enter-DockerContainer',
        'Get-DockerContainer',
        'Get-DockerContainerLog',
        'New-DockerContainer',
        'Remove-DockerContainer',
        'Rename-DockerContainer',
        'Restart-DockerContainer',
        'Resume-DockerContainer',
        'Start-DockerContainer',
        'Stop-DockerContainer',
        'Suspend-DockerContainer',
        'Wait-DockerContainer',
        'Get-DockerContext',
        'New-DockerContext',
        'Remove-DockerContext',
        'Use-DockerContext',
        'Build-DockerImage',
        'Copy-DockerImage',
        'Export-DockerImage',
        'Find-DockerImage',
        'Get-DockerImage',
        'Import-DockerImage',
        'Install-DockerImage',
        'Publish-DockerImage',
        'Remove-DockerImage',
        'Add-DockerNetworkConnection',
        'Get-DockerNetwork',
        'Get-DockerNetworkConnection',
        'New-DockerNetwork',
        'Remove-DockerNetwork',
        'Remove-DockerNetworkConnection',
        'Get-DockerPath',
        'Get-DockerVersion',
        'Invoke-DockerCommand',
        'Set-DockerPath'
    )
    Alias = @(
        'ccdc',
        'etdc',
        'gdc',
        'ndc',
        'rndc',
        'rtdc',
        'rudc',
        'sadc',
        'spdc',
        'ssdc',
        'wdc',
        'gdcx',
        'ndcx',
        'rdcx',
        'udcx',
        'bddi',
        'cpdi',
        'epdi',
        'gdi',
        'ipdi',
        'isdi',
        'pbdi',
        'rdi',
        'adnc',
        'gdn',
        'gdnc',
        'ndn',
        'rdn',
        'rdnc'
    )
    Variable = @(
        
    )
}
Export-ModuleMember @ExportModuleMember