Private/DoDockerInstall.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
function DoDockerInstall {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$False)]
        [int]$MobyLinuxVMMemoryInGB = 2,

        [Parameter(Mandatory=$False)]
        [switch]$AllowRestarts,

        [Parameter(Mandatory=$False)]
        [switch]$SkipHyperVInstallCheck,

        [Parameter(Mandatory=$False)]
        [switch]$RecreateMobyLinuxVM,

        [Parameter(Mandatory=$False)]
        [switch]$PreRelease,

        [Parameter(Mandatory=$False)]
        [switch]$AllowLogout
    )

    # Make sure we have the ProgramManagement Module installed and imported
    if (![bool]$(Get-Module -ListAvailable ProgramManagement)) {Install-Module ProgramManagement}
    if (![bool]$(Get-Module ProgramManagement)) {Import-Module ProgramManagement}


    try {
        $DockerForWindowsUninstallResult = Uninstall-Program -ProgramName "docker-for-windows" -ErrorAction SilentlyContinue
    }
    catch {
        if ($_.Exception.Message -match "^Unable to find an installed program matching the name") {
            Write-Verbose $($_.Exception.Message)
        }
        else {
            Write-Error $_
            $global:FunctionResult = "1"
            return
        }
    }

    # Do some basic Memory Checks before attempting to create/start the MobyLinux VM
    $OSInfo = Get-CimInstance Win32_OperatingSystem
    $TotalMemory = $OSInfo.TotalVisibleMemorySize
    $MemoryAvailable = $OSInfo.FreePhysicalMemory
    $TotalMemoryInGB = [Math]::Round($TotalMemory / 1MB)
    $MemoryAvailableInGB = [Math]::Round($MemoryAvailable / 1MB)
    if ($TotalMemoryInGB -lt 8) {
        Write-Error "The host machine should have at least 8GB total memory installed in order to run VMs. Halting!"
        $global:FunctionResult = "1"
        return
    }
    if ($MemoryAvailableInGB -lt 4) {
        $MemoryErrorMsg = "The host machine should have at least 4GB of memory readily available in order to run a VM. " +
        "It currently only has about $MemoryAvailableInGB GB available for immediate use. Halting!"
        Write-Error $MemoryErrorMsg
        $global:FunctionResult = "1"
        return
    }

    if ([bool]$PSBoundParameters['MobyLinuxVMMemoryInGB']) {
        $MobyLinuxVMMemoryInMB = [Math]::Round($MobyLinuxVMMemoryInGB * 1KB)
    }

    if (!$SkipHyperVInstallCheck) {
        # Check to see if all Hyper-V features are installed...

        # NOTE: Below $HyperVFeaturesInstallResults contains properties 'InstallResults' (array of InstallFeatureDism
        # pscustomobjects which contiain properties contains properties [string]Path, [bool]Online, [string]WinPath,
        # [string]SysDrivePath, [bool]RestartNeeded, [string]$LogPath, [string]ScratchDirectory,
        # [string]LogLevel), and 'InstallFailures' (array of strings of Dism Feature Names that
        # failed to install).
        # NOTE: InstallHyperVFeatures returns $null if everything is already installed.
        try {
            $HyperVFeaturesInstallResults = InstallHyperVFeatures -ParentFunction $MyInvocation.MyCommand.Name
        }
        catch {
            Write-Error $_
            Write-Error "The InstallHyperVFeatures function (as executed by the $($MyInvocation.MyCommand.Name) function) failed! Halting!"
            $global:FunctionResult = "1"
            return
        }

        if ($HyperVFeaturesInstallResults.InstallFailures.Count -gt 0) {
            Write-Error "Please remedy the Hyper-V Features that failed to install before proceeding. Halting!"
            $global:FunctionResult = "1"
            return
        }

        # Check to see if Windows Containers Feature is installed...
        # NOTE: The below InstallFeatureDism returns $null if Containers is already installed
        try {
            $InstallContainersFeatureDismResult = InstallFeatureDism -Feature Containers -ParentFunction $MyInvocation.MyCommand.Name
        }
        catch {
            Write-Error $_
            Write-Error "The InstallFeatureDism function (as executed by the $($MyInvocation.MyCommand.Name) function) failed! Halting!"
            $global:FunctionResult = "1"
            return
        }
        
        if ($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -notcontains $True -and 
        $($InstallContainersFeatureDismResult.RestartNeeded -eq $False -or $InstallContainersFeatureDismResult.RestartNeeded -eq $null)) {
            Write-Host "All dependencies are already installed...proceeding..." -ForegroundColor Green
        }
        else {
            if ($($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -contains $True -or $InstallContainersFeatureDismResult.RestartNeeded) -and $AllowRestarts) {
                Write-Host "Restarting $env:ComputerName..."
                # NOTE: The below output "Restarting" is important when running this function via Invoke-Command
                Write-Output "Restarting"
                Restart-Computer -Confirm:$false -Force
            }
            else {
                Write-Error "You must restart $env:ComputerName before proceeding! Halting!"
                return
            }
        }
    }

    # At this point, we know that Hyper-V is installed one way or another, so import the NetNat cmdlet
    if ($PSVersionTable.PSEdition -eq "Core") {
        Import-WinModule -Name NetNat -ErrorAction SilentlyContinue
    }
    else {
        Import-Module -Name NetNat -ErrorAction SilentlyContinue
    }
    if ($(Get-Module).Name -notcontains "NetNat" -and $(Get-Module -ListAvailable).Name -notcontains "NetNat") {
        Write-Error $_
        Write-Error "Unable to import the NetNat Module! Is Hyper-V installed? Halting!"
        $global:FunctionResult = "1"
        return
    }

    # Make sure that OpenSSH is installed and that the ssh-agent service is installed and running
    if (![bool]$(Get-Command ssh -ErrorAction SilentlyContinue)) {
        if (![bool]$(Get-Module -ListAvailable ProgramManagement)) {Install-Module ProgramManagement}
        if (![bool]$(Get-Module ProgramManagement)) {Import-Module ProgramManagement}
        $InstallOpenSSHResult = Install-Program -ProgramName openssh -CommandName ssh.exe -ExpectedInstallLocation "C:\Program Files\OpenSSH-Win64"

        if (![bool]$(Get-Service ssh-agent -ErrorAction SilentlyContinue)) {
            if (Test-Path "C:\Program Files\OpenSSH-Win64\install-sshd.ps1") {
                & "C:\Program Files\OpenSSH-Win64\install-sshd.ps1"
            }
            else {
                Write-Warning "Unable to find 'C:\Program Files\OpenSSH-Win64\install-sshd.ps1'! The services 'ssh-agent' and 'sshd' will NOT be installed."
            }
        }
    }
    
    try {
        Write-Host "Installing Docker CE (i.e. Docker For Windows)..."
        $InstallDockerSplatParams = @{
            ProgramName                 = "docker-for-windows"
            CommandName                 = "docker.exe"
            ExpectedInstallLocation     = "$env:ProgramFiles\Docker"
            ErrorAction                 = "SilentlyContinue"
            ErrorVariable               = "IPErr"
            WarningAction               = "SilentlyContinue"
            InformationAction           = "SilentlyContinue"
        }
        if ($PreRelease) {
            $InstallDockerSplatParams.Add("PreRelease",$True)
        }
        $InstallDockerCEResult = Install-Program @InstallDockerSplatParams
        if (!$InstallDockerCEResult) {throw "The Install-Program function failed while installing DockerCE! Halting!"}
    }
    catch {
        Write-Error $_
        Write-Host "Errors for the Install-Program function are as follows:"
        Write-Error $($IPErr | Out-String)
        $global:FunctionResult = "1"
        return
    }

    if ($InstallDockerCEResult.InstallAction -eq "FreshInstall") {
        Write-Host "Docker CE (i.e. Docker For Windows) has successfully been installed" -ForegroundColor Green
    }
    if ($InstallDockerCEResult.InstallAction -eq "AlreadyInstalled") {
        Write-Warning "Docker CE (i.e. Docker For Windows) is already installed!"

        if ($RecreateMobyLinuxVM) {
            $RecreateMobyLinuxVMResult = Recreate-MobyLinuxVM
        }

        $Output = [ordered]@{
            DockerCEInstallResult   = $InstallDockerCEResult
        }
        if ($RecreateMobyLinuxVMResult) {
            $Output.Add("RecreateMobyLinuxVMResult",$RecreateMobyLinuxVMResult)
        }

        [pscustomobject]$Output
        return
    }

    # Before configuring Docker CE, make sure there is NOT already an Internal vSwitch named DockerNAT or
    # a Network Adapter with IP 10.0.75.1
    $vSwitchInfoByIP = GetvSwitchAllRelatedInfo -IPAddress 10.0.75.1 -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
    if ($vSwitchInfoByIP) {
        Remove-VMSwitch -Name $vSwitchInfoByIP.BasicvSwitchInfo.Name -Confirm:$False -Force
    }
    if ([bool]$(Get-NetIPAddress -IPAddress 10.0.75.1 -ErrorAction SilentlyContinue)) {
        Remove-NetIPAddress -InterfaceAlias $(Get-NetIPAddress -IPAddress 10.0.75.1).InterfaceAlias -Confirm:$False
    }
    $vSwitchInfoByName = GetvSwitchAllRelatedInfo -vSwitchName "LocalNAT" -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
    if ($vSwitchInfoByName) {
        Remove-VMSwitch -Name $vSwitchInfoByName.BasicvSwitchInfo.Name -Confirm:$False -Force
    }
    try {
        $NetNatToRemove = Get-NetNat | Where-Object {$_.InternalIPInterfaceAddressPrefix -eq "10.0.75.0/24"}
        if ($NetNatToRemove) {
            Remove-NetNat -InputObject $NetNatToRemove -Confirm:$False
        }
    }
    catch {
        Write-Warning $_.ToString()
    }

    if ($InstallDockerCEResult.InstallAction -eq "FreshInstall") {
        Recreate-MobyLinuxVM
    }
    if ($InstallDockerCEResult.InstallAction -eq "AlreadyInstalled") {
        $RecreateMobyLinuxVMChoice = Read-Host -Prompt "Would you like to re-create the MobyLinux VM? (IMPORTANT NOTE: This could destroy existing Docker Containers.) [Yes\No]"
        while ($RecreateMobyLinuxVMChoice -notmatch "Yes|yes|Y|y|No|no|N|n") {
            Write-Host "'$RecreateMobyLinuxVMChoice' in not a valid option. Pleas enter 'Yes' or 'No'"
            $RecreateMobyLinuxVMChoice = Read-Host -Prompt "Would you like to re-create the MobyLinux VM? (IMPORTANT NOTE: This could destroy existing Docker Containers.) [Yes\No]"
        }

        if ($RecreateMobyLinuxVMChoice -match "^yes$|^y$") {
            Recreate-MobyLinuxVM
        }
    }

    # Finally, we're ready to start Docker For Windows aka DockerCE
    $DockerForWindowsEXE = $(Get-ChildItem -Path "C:\Program Files\Docker" -Recurse -File -Filter "*Docker For Windows.exe").FullName
    try {
        & $DockerForWindowsEXE
    }
    catch {
        Write-Error $_
        Write-Error "'$DockerForWindowsExe' failed! Halting!"
        $global:FunctionResult = "1"
        return
    }

    # $Host is an Automatic Variable. Values for $Host.Name can be "ConsoleHost" (for normal local PowerShell
    # Session) or "ServerRemoteHost" (if we're within a Remote PSSession)
    if ($Host.Name -eq "ConsoleHost") {
        if (!$AllowLogout) {
            Write-Warning "Docker CE (i.e. Docker For Windows) has added the current user (i.e. $(whoami)) to the 'docker-users' security group, however, logout/login is required docker can be used by $(whoami)!"
            $LogoutChoice = Read-Host -Prompt "Would you like to logout now? [Yes/No]"
            
            while ($LogoutChoice -notmatch "Yes|yes|Y|y|No|no|N|n") {
                Write-Host "$LogoutChoice is not a valid choice! Please enter 'Yes' or 'No'"
                $LogoutChoice = Read-Host -Prompt "Would you like to logout now? [Yes/No]"
            }
        }

        if ($LogoutChoice -match "Yes|yes|Y|y" -or $AllowLogout) {
            logoff
        }
        else {
            Write-Host "Please logout/login at your discretion in order to begin using docker."
            Write-Host "Install-Docker function completed successfully!" -ForegroundColor Green
        }
    }

    $Output = [ordered]@{
        DockerCEInstallResult   = $InstallDockerCEResult
    }
    if ($MobyLinuxScriptResult) {
        $Output.Add("MobyLinuxScriptResult",$MobyLinuxScriptResult)
    }
    if ($RecreateMobyLinuxVMResult) {
        $Output.Add("RecreateMobyLinuxVMResult",$RecreateMobyLinuxVMResult)
    }

    [pscustomobject]$Output
}