Skip to content

Instantly share code, notes, and snippets.

@tomudding
Last active May 17, 2020 13:59
Show Gist options
  • Save tomudding/18edf380bb3f2c01d3af6742fcbdd58e to your computer and use it in GitHub Desktop.
Save tomudding/18edf380bb3f2c01d3af6742fcbdd58e to your computer and use it in GitHub Desktop.
Modified version of Docker Desktop's MobyLinux.ps1. This modified version allows computers on a network domain to run Docker Desktop (2.2.0.5) using Linux Containers.
<#
.SYNOPSIS
Manages a MobyLinux VM to run Linux Docker on Hyper-V
.DESCRIPTION
Creates/Destroys/Starts/Stops A MobyLinux VM to run Docker on Hyper-V
.PARAMETER VmName
If passed, use this name for the MobyLinux VM, otherwise 'MobyLinuxVM'
.PARAMETER IsoFile
Path to the MobyLinux ISO image, must be set for Create/ReCreate
.PARAMETER SwitchName
If passed, use this name for the Hyper-V virtual switch,
otherwise the VM will have no Hyper-V NIC.
.PARAMETER Create
Create a MobyLinux VM
.PARAMETER SwitchSubnetMaskSize
Switch subnet mask size (default: 24)
.PARAMETER SwitchSubnetAddress
Switch subnet address (default: 10.0.75.0)
.PARAMETER Memory
Memory allocated for the VM at start in MB (optional on Create, default: 2048 MB)
.PARAMETER CPUs
CPUs used in the VM (optional on Create, default: min(2, number of CPUs on the host))
.PARAMETER PreferredIPAddress
Preferred IP address for the the MobyLinux virtual switch. If this IP address is not available or not within the allowed subnet range then a different IP address will be selected.
.PARAMETER Destroy
Remove a MobyLinux VM
.PARAMETER KeepVolume
if passed, will not delete the vmhd on Destroy
.PARAMETER Start
Start an existing MobyLinux VM
.PARAMETER Stop
Stop a running MobyLinux VM
.EXAMPLE
.\MobyLinux.ps1 -IsoFile .\docker-desktop.iso -Create
.\MobyLinux.ps1 -Start
#>
[CmdletBinding()]Param(
[string] $VmName = "DockerDesktopVM",
[string] $IsoFile = ".\docker-desktop.iso",
[string] $SwitchName = "",
[string] $VhdPathOverride = $null,
[long] $VhdSize = 64*1000*1000*1000,
[string] $confIsoFile = $null,
[string] $DockerIsoFile = $null,
[string] $KubeImageIsoFile = $null,
[Parameter(ParameterSetName='Create',Mandatory=$false)][switch] $Create,
[Parameter(ParameterSetName='Create',Mandatory=$false)][int] $CPUs = 2,
[Parameter(ParameterSetName='Create',Mandatory=$false)][long] $Memory = 2048,
[Parameter(ParameterSetName='Create',Mandatory=$false)][string] $SwitchSubnetAddress = "10.0.75.0",
[Parameter(ParameterSetName='Create',Mandatory=$false)][int] $SwitchSubnetMaskSize = 24,
[Parameter(ParameterSetName='Create',Mandatory=$false)][string] $PreferredIPAddress,
[Parameter(ParameterSetName='Destroy',Mandatory=$false)][switch] $Destroy,
[Parameter(ParameterSetName='Destroy',Mandatory=$false)][switch] $KeepVolume,
[Parameter(ParameterSetName='Start',Mandatory=$false)][switch] $Start,
[Parameter(ParameterSetName='Stop',Mandatory=$false)][switch] $Stop
)
# This makes sure the system modules can be imported
$envPSModulePath = [Environment]::GetEnvironmentVariable('PSModulePath','Machine')
$env:PSModulePath = [System.Management.Automation.ModuleIntrinsics]::GetModulePath($envPSModulePath, $envPSModulePath, "")
# Make sure we stop at Errors unless otherwise explicitly specified
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$VerbosePreference = "SilentlyContinue"
# Explicitly disable Module autoloading and explicitly import the
# Modules this script relies on. This is not strictly necessary but
# good practice as it prevents arbitrary errors
$PSModuleAutoloadingPreference = 'None'
Import-Module Microsoft.PowerShell.Utility -Verbose:$false
Import-Module Microsoft.PowerShell.Management -Verbose:$false
Import-Module Hyper-V -Verbose:$false
Import-Module NetAdapter -Verbose:$false
Import-Module NetTCPIP -Verbose:$false
# Get correct hostname
if ((gwmi win32_computersystem).partofdomain -eq $true) {
$cname = [System.Net.DNS]::GetHostByName('').HostName
} else {
$cname = Hostname
}
$PSDefaultParameterValues["Get-VMHost:ComputerName"] = $cname
$PSDefaultParameterValues["Get-VMNetworkAdapter:ComputerName"] = $cname
$PSDefaultParameterValues["Remove-VMSwitch:ComputerName"] = $cname
$PSDefaultParameterValues["New-VMSwitch:ComputerName"] = $cname
$PSDefaultParameterValues["Get-VM:ComputerName"] = $cname
$PSDefaultParameterValues["New-VM:ComputerName"] = $cname
$PSDefaultParameterValues["Set-VM:ComputerName"] = $cname
$PSDefaultParameterValues["Get-VMMemory:ComputerName"] = $cname
$PSDefaultParameterValues["Add-VMNetworkAdapter:ComputerName"] = $cname
$PSDefaultParameterValues["Remove-VMNetworkAdapter:ComputerName"] = $cname
$PSDefaultParameterValues["Connect-VMNetworkAdapter:ComputerName"] = $cname
$PSDefaultParameterValues["Get-VMSwitch:ComputerName"] = $cname
$PSDefaultParameterValues["Remove-VMDvdDrive:ComputerName"] = $cname
$PSDefaultParameterValues["Add-VMDvdDrive:ComputerName"] = $cname
$PSDefaultParameterValues["Get-VMFirmware:ComputerName"] = $cname
$PSDefaultParameterValues["Set-VMFirmware:ComputerName"] = $cname
$PSDefaultParameterValues["Set-VMComPort:ComputerName"] = $cname
$PSDefaultParameterValues["Get-VMIntegrationService:ComputerName"] = $cname
$PSDefaultParameterValues["Enable-VMIntegrationService:ComputerName"] = $cname
$PSDefaultParameterValues["Disable-VMIntegrationService:ComputerName"] = $cname
$PSDefaultParameterValues["Disable-VMConsoleSupport:ComputerName"] = $cname
$PSDefaultParameterValues["Remove-VM:ComputerName"] = $cname
$PSDefaultParameterValues["Remove-VMHardDiskDrive:ComputerName"] = $cname
$PSDefaultParameterValues["Add-VMHardDiskDrive:ComputerName"] = $cname
$PSDefaultParameterValues["Start-VM:ComputerName"] = $cname
$PSDefaultParameterValues["Stop-VM:ComputerName"] = $cname
$PSDefaultParameterValues["Get-VHD:ComputerName"] = $cname
$PSDefaultParameterValues["New-VHD:ComputerName"] = $cname
$PSDefaultParameterValues["Get-VMScsiController:ComputerName"] = $cname
$PSDefaultParameterValues["Add-VMScsiController:ComputerName"] = $cname
Write-Information "ComputerName successfully set to $cname"
function Enable-MobyLinuxRequiredService([string]$Name) {
$service = Get-Service $Name
if (-not $service) {
throw "$Name service not found. Ensure that the Hyper-V feature is installed."
}
elseif ($service.StartType -eq [System.ServiceProcess.ServiceStartMode]::Disabled) {
throw "$Name service is disabled. The service must be enabled to run Docker Desktop."
}
elseif ($service.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Stopped) {
Start-Service $Name
}
}
function New-MobyLinux {
<#
.SYNOPSIS
Creates a new MobyLinux virtual machine and switch
.DESCRIPTION
Creates a new MobyLinux virtual machine and switch, configuring the devices as required.
.PARAMETER SwitchName
Name of the MobyLinux virtual switch
.PARAMETER SubnetAddress
Subnet address of the MobyLinux virtual switch
.PARAMETER SubnetMaskSize
Subnet mask size of the MobyLinux virtual switch
.PARAMETER PreferredIPAddress
Preferred IP address for the the MobyLinux virtual switch. If this IP address is not available or not within the allowed subnet range then a different IP address will be selected.
.PARAMETER VmName
Name of the MobyLinux virtual machine
.PARAMETER Memory
Memory allocated for the VM at start in MB
.PARAMETER CPUs
CPUs used in the VM
.PARAMETER SwitchName
Name of the VM's virtual switch. If not supplied then no NIC and switch are created.
.PARAMETER VhdFile
Path of the VHD file
.PARAMETER VhdSize
Size of the VHD to create if does not already exist
.PARAMETER IsoFile
Path to the MobyLinux ISO image, must be set for Create/ReCreate
.PARAMETER ConfigIsoFile
Path to the MobyLinux config ISO image to be attached as a drive to the VM
.PARAMETER DockerIsoFile
Path to the MobyLinux docker ISO image to be attached as a drive to the VM
.PARAMETER KubeImageIsoFile
Path to the MobyLinux kubernetes ISO image to be attached as a drive to the VM
.OUTPUTS
PSObject. Returns the IP address of the MobyLinux virtual switch.
#>
[CmdletBinding()]param (
[string] $SwitchName = '',
[string] $SubnetAddress = '10.0.75.0',
[int] $SubnetMaskSize = 24,
[string] $PreferredIPAddress,
[string] $VmName = 'DockerDesktopVM',
[int] $CPUs = 2,
[long] $Memory = 2048,
[string] $VhdFile,
[long] $VhdSize = 64*1000*1000*1000,
[string] $IsoFile = '.\docker-desktop.iso',
[string] $ConfigIsoFile,
[string] $DockerIsoFile,
[string] $KubeImageIsoFile
)
if ($SwitchName -ne "") {
$ip = New-MobyLinuxSwitch -Name $SwitchName -SubnetAddress $SubnetAddress -SubnetMaskSize $SubnetMaskSize -PreferredIPAddress $PreferredIPAddress
$output = New-Object PSObject
$output | Add-Member -NotePropertyName SwitchAddress -NotePropertyValue $ip
Write-Output $output
}
New-MobyLinuxVM -Name $VmName -SwitchName $SwitchName -CPUs $CPUs -Memory $Memory -VhdFile $VhdFile -IsoFile $IsoFile -ConfigIsoFile $ConfigIsoFile -DockerIsoFile $DockerIsoFile -KubeImageIsoFile $KubeImageIsoFile
}
function New-MobyLinuxSwitch {
<#
.SYNOPSIS
Creates a new MobyLinux virtual switch
.DESCRIPTION
Creates a new MobyLinux virtual switch with an IPv4 IP Address within the subnet address/mask. If the switch already exists then ensures that the switch has an IP address within the allowed range.
.PARAMETER Name
Name of the MobyLinux virtual switch
.PARAMETER SubnetAddress
Subnet address of the MobyLinux virtual switch
.PARAMETER SubnetMaskSize
Subnet mask size of the MobyLinux virtual switch
.PARAMETER PreferredIPAddress
Preferred IP address for the the MobyLinux virtual switch. If this IP address is not available or not within the allowed subnet range then a different IP address will be selected.
.OUTPUTS
System.String. Returns the IP address of the MobyLinux virtual switch.
.EXAMPLE
New-MobyLinuxSwitch
.EXAMPLE
New-MobyLinuxSwitch -Name DockerNATSwitch
.EXAMPLE
New-MobyLinuxSwitch -Name DockerNATSwitch -SubnetAddress 10.75.0.0 -SubnetMaskSize 28 -PreferredIPAddress 10.75.0.2
#>
[CmdletBinding()]param (
[string] $Name = "",
[string] $SubnetAddress = "10.0.75.0",
[int] $SubnetMaskSize = 24,
[string] $PreferredIPAddress
)
$vmSwitch = Hyper-V\Get-VMSwitch $Name -SwitchType Internal -ea SilentlyContinue
$vmNetAdapter = Hyper-V\Get-VMNetworkAdapter -ManagementOS -SwitchName $Name -ea SilentlyContinue
if ($vmSwitch -and $vmNetAdapter) {
Write-Verbose "Using existing Switch: $Name"
}
else {
Write-Verbose "Creating Switch: $Name..."
Hyper-V\Remove-VMSwitch $Name -Force -ea SilentlyContinue
Hyper-V\New-VMSwitch $Name -SwitchType Internal -ea SilentlyContinue | Out-Null
$vmNetAdapter = Hyper-V\Get-VMNetworkAdapter -ManagementOS -SwitchName $Name
Write-Verbose "Switch created."
}
# Make sure there are no lingering net adapters
$netAdapters = Get-NetAdapter | Where-Object { $_.Name.StartsWith("vEthernet ($Name)") }
if (($netAdapters).Length -gt 1) {
Write-Verbose "Disable and rename invalid NetAdapters"
$now = (Get-Date -Format FileDateTimeUniversal)
$index = 1
$invalidNetAdapters = $netAdapters | Where-Object { $_.DeviceID -ne $vmNetAdapter.DeviceId }
foreach ($netAdapter in $invalidNetAdapters) {
$netAdapter `
| Disable-NetAdapter -Confirm:$false -PassThru `
| Rename-NetAdapter -NewName "Broken Docker Adapter ($now) ($index)" `
| Out-Null
$index++
}
}
# Make sure the Switch has the right IP address
$networkAdapter = Get-NetAdapter | Where-Object { $_.DeviceID -eq $vmNetAdapter.DeviceId }
$switchAddress = Set-SwitchIPAddress -NetAdapter $networkAdapter -SubnetAddress $SubnetAddress -SubnetMaskSize $SubnetMaskSize -PreferredIPAddress $PreferredIPAddress
Write-Verbose "Removing any other IP addresses from $Name switch"
$networkAdapter | Get-NetIPAddress | Where-Object { $_.IPAddress -ne $switchAddress } | Remove-NetIPAddress -Confirm:$false -ea SilentlyContinue
$networkAdapter | Disable-NetAdapterBinding -ComponentID ms_server -ea SilentlyContinue
$networkAdapter | Enable-NetAdapterBinding -ComponentID ms_server -ea SilentlyContinue
Write-Output $switchAddress
}
function ConvertTo-BinaryIPAddress($IPAddress) {
$bytes = ([IPAddress]$IPAddress).GetAddressBytes()
if ([BitConverter]::IsLittleEndian) {
[Array]::Reverse($bytes)
}
return [BitConverter]::ToUInt32($bytes, 0)
}
function Set-IPAddress($NetAdapter, $BinaryIPAddress, $MinIPAddress, $MaxIPAddress) {
if (-not $BinaryIPAddress -or $BinaryIPAddress -le $MinIPAddress -or $BinaryIPAddress -gt $MaxIPAddress) {
return $null
}
$ip = [IPAddress]::Parse($BinaryIPAddress).IPAddressToString
Write-Verbose "Setting docker switch IP address to $ip"
if ($NetAdapter | Get-NetIPAddress -IPAddress $ip -ea SilentlyContinue) {
return $ip
}
elseif ($existingNetIPAddress = Get-NetIPAddress -IPAddress $ip -ea SilentlyContinue) {
Write-Verbose "IP address '$ip' is already in use by '$($existingNetIPAddress.InterfaceAlias)'"
return $null
}
$NetAdapter | New-NetIPAddress -AddressFamily IPv4 -IPAddress $ip -PrefixLength $SubnetMaskSize -ea Stop | Out-Null
return $ip
}
function Set-SwitchIPAddress($NetAdapter, $SubnetAddress, $SubnetMaskSize, $PreferredIPAddress) {
$mask = [uint32]::MaxValue -shl (32 - $SubnetMaskSize)
$minAddress = ConvertTo-BinaryIPAddress -IPAddress $SubnetAddress
$maxAddress = $minAddress -bor (-bnot $mask)
$ipAddress = $minAddress + 1
# Try using IP address from settings.json
if ($PreferredIPAddress) {
$ip = Set-IPAddress -NetAdapter $NetAdapter -BinaryIPAddress (ConvertTo-BinaryIPAddress -IPAddress $PreferredIPAddress) -MinIPAddress $minAddress -MaxIPAddress $maxAddress
if ($ip) {
return $ip
}
}
# Try using existing IP address
if ($currentIPAddress = $NetAdapter |
Get-NetIPAddress -AddressFamily IPv4 -ea SilentlyContinue |
Where-Object { $ip = ConvertTo-BinaryIPAddress -IPAddress $_.IPAddress; $ip -gt $minAddress -and $ip -le $maxAddress } |
Select-Object -First 1) {
Write-Verbose "Using existing switch IP address $($currentIPAddress.IPAddress)"
return $currentIPAddress.IPAddress
}
# Try all addresses in range
$NetAdapter | Set-NetIPInterface -Dhcp Disabled -ea SilentlyContinue
for ($ipAddress = $minAddress + 1; $ipAddress -le $maxAddress; $ipAddress++) {
$ip = Set-IPAddress -NetAdapter $NetAdapter -BinaryIPAddress $ipAddress -MinIPAddress $minAddress -MaxIPAddress $maxAddress
if ($ip) {
return $ip
}
}
throw "No free IP address found in range $SubnetAddress/$SubnetMaskSize"
}
function New-MobyLinuxVM {
<#
.SYNOPSIS
Creates a new MobyLinux virtual machine
.DESCRIPTION
Creates a new MobyLinux virtual machine, configuring the devices as required.
.PARAMETER Name
Name of the MobyLinux virtual machine
.PARAMETER Memory
Memory allocated for the VM at start in MB
.PARAMETER CPUs
CPUs used in the VM
.PARAMETER SwitchName
Name of the VM's virtual switch
.PARAMETER VhdFile
Path of the VHD file
.PARAMETER VhdSize
Size of the VHD to create if does not already exist
.PARAMETER IsoFile
Path to the MobyLinux ISO image, must be set for Create/ReCreate
.PARAMETER ConfigIsoFile
Path to the MobyLinux config ISO image to be attached as a drive to the VM
.PARAMETER DockerIsoFile
Path to the MobyLinux docker ISO image to be attached as a drive to the VM
.PARAMETER KubeImageIsoFile
Path to the MobyLinux kubernetes ISO image to be attached as a drive to the VM
#>
[CmdletBinding()]param(
[string] $Name = 'DockerDesktopVM',
[int] $CPUs = 2,
[long] $Memory = 2048,
[string] $SwitchName = "",
[string] $VhdFile,
[long] $VhdSize = 64*1000*1000*1000,
[string] $IsoFile = '.\docker-desktop.iso',
[string] $ConfigIsoFile,
[string] $DockerIsoFile,
[string] $KubeImageIsoFile
)
if (!(Test-Path $IsoFile)) {
throw "ISO file at $IsoFile does not exist"
}
$CPUs = ((Hyper-V\Get-VMHost).LogicalProcessorCount,$CPUs | Measure-Object -Minimum).Minimum
$vm = Hyper-V\Get-VM $Name -ea SilentlyContinue
if ($vm) {
if ($vm.Length -ne 1) {
throw "Multiple VMs exist with the name $Name. Delete invalid ones or reset Docker to factory defaults."
}
} else {
Write-Verbose "Creating VM $Name..."
$vm = Hyper-V\New-VM -Name $Name -Generation 2 -NoVHD
Hyper-V\Set-VM -Name $Name -AutomaticStartAction Nothing -AutomaticStopAction ShutDown -CheckpointType Disabled
}
if ($vm.Generation -ne 2) {
throw "VM $Name is a Generation $($vm.Generation) VM. It should be a Generation 2."
}
if ($vm.State -ne "Off") {
Write-Verbose "VM $Name is $($vm.State). Cannot change its settings."
return
}
Write-Verbose "Setting CPUs to $CPUs and Memory to $Memory MB"
$Memory = ($Memory,(Hyper-V\Get-VMMemory -VMName $Name).MaximumPerNumaNode | Measure-Object -Minimum).Minimum
Hyper-V\Set-VM -Name $Name -MemoryStartupBytes ($Memory*1024*1024) -ProcessorCount $CPUs -StaticMemory
Set-MobyLinuxVHDPath -VM $vm -VhdFile $VhdFile -VhdSize $VhdSize
Hyper-V\Remove-VMNetworkAdapter -VMName $Name
if ($SwitchName -ne "") {
$vmNetAdapter = Hyper-V\Get-VMNetworkAdapter -VMName $Name
if (!$vmNetAdapter) {
Write-Verbose "Attach Net Adapter"
$vmNetAdapter = Hyper-V\Add-VMNetworkAdapter -VMName $Name -Passthru
}
Write-Verbose "Connect Internal Switch $SwitchName"
$vmNetAdapter | Hyper-V\Connect-VMNetworkAdapter -VMSwitch $(Hyper-V\Get-VMSwitch $SwitchName -SwitchType Internal)
}
if ($vm.DVDDrives) {
Write-Verbose "Remove existing DVDs"
Hyper-V\Remove-VMDvdDrive $vm.DVDDrives -ea SilentlyContinue
}
Write-Verbose "Attach DVD $IsoFile"
Hyper-V\Add-VMDvdDrive -VMName $Name -Path $IsoFile
$iso = Hyper-V\Get-VMFirmware -VMName $Name | Select-Object -ExpandProperty BootOrder | Where-Object { $_.FirmwarePath.EndsWith("Scsi(0,1)") }
Hyper-V\Set-VMFirmware -VMName $Name -EnableSecureBoot Off -FirstBootDevice $iso
Hyper-V\Set-VMComPort -VMName $Name -number 1 -Path "\\.\pipe\docker$Name-com1"
Hyper-V\Set-VMComPort -VMName $Name -number 2 -Path "\\.\pipe\docker$Name-com2"
# Enable only required VM integration services
$intSvc = @()
$intSvc += "Microsoft:$($vm.Id)\84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47" # Heartbeat
$intSvc += "Microsoft:$($vm.Id)\9F8233AC-BE49-4C79-8EE3-E7E1985B2077" # Shutdown
$intSvc += "Microsoft:$($vm.Id)\2497F4DE-E9FA-4204-80E4-4B75C46419C0" # TimeSynch
Hyper-V\Get-VMIntegrationService -VMName $Name | ForEach-Object {
if ($intSvc -contains $_.Id) {
Hyper-V\Enable-VMIntegrationService $_
Write-Verbose "Enabled $($_.Name)"
} else {
Hyper-V\Disable-VMIntegrationService $_
Write-Verbose "Disabled $($_.Name)"
}
}
Hyper-V\Disable-VMConsoleSupport -VMName $Name
Write-Verbose "VM created."
}
function Remove-MobyLinux {
<#
.SYNOPSIS
Removes the MobyLinux virtual machine and switch
.DESCRIPTION
Removes the MobyLinux virtual machine and switch
.PARAMETER VmName
Name of the MobyLinux virtual machine
.PARAMETER SwitchName
Name of the MobyLinux virtual network switch
.PARAMETER KeepVolume
If set the VHD file is not removed with the virtual machine
.PARAMETER VhdFile
Path of the VHD file
.EXAMPLE
Stop-MobyLinuxVM
.EXAMPLE
Stop-MobyLinuxVM -Name DockerVM
#>
[CmdletBinding()]param(
[string] $VmName = 'DockerDesktopVM',
[string] $SwitchName = '',
[switch] $KeepVolume,
[string] $VhdFile
)
Stop-MobyLinuxVM -Name $VmName -SwitchName $SwitchName
if ($SwitchName -ne "") {
Remove-MobyLinuxSwitch -Name $SwitchName
}
Remove-MobyLinuxVM -Name $VmName -KeepVolume:$KeepVolume -VhdFile $VhdFile
}
function Remove-MobyLinuxSwitch {
<#
.SYNOPSIS
Removes the MobyLinux virtual network switch
.DESCRIPTION
Removes the MobyLinux virtual network switch and all of it's IP addresses
.PARAMETER Name
Name of the MobyLinux virtual network switch
.EXAMPLE
Remove-MobyLinuxSwitch
.EXAMPLE
Remove-MobyLinuxSwitch -Name DockerNATSwitch
#>
[CmdletBinding()]
param (
[string] $Name = ''
)
Write-Verbose "Destroying Switch $Name..."
# Let's remove the IP otherwise a nasty bug makes it impossible
# to recreate the vswitch
$vmNetAdapter = Hyper-V\Get-VMNetworkAdapter -ManagementOS -SwitchName $Name -ea SilentlyContinue
if ($vmNetAdapter) {
$networkAdapter = Get-NetAdapter | Where-Object { $_.DeviceID -eq $vmNetAdapter.DeviceId }
$networkAdapter | Remove-NetIPAddress -Confirm:$false -ea SilentlyContinue
}
Hyper-V\Remove-VMSwitch $Name -Force -ea SilentlyContinue
}
function Remove-MobyLinuxVM {
<#
.SYNOPSIS
Removes the MobyLinux virtual machine
.DESCRIPTION
Removes the MobyLinux virtual network switch and VHD file unless specified not to
.PARAMETER Name
Name of the MobyLinux virtual machine
.PARAMETER KeepVolume
If set the VHD file is not removed with the virtual machine
.PARAMETER VhdFile
Path of the VHD file
.EXAMPLE
Remove-MobyLinuxVM -VhdFile ~/my-vhd.vhd
.EXAMPLE
Remove-MobyLinuxVM -Name DockerVM -KeepVolume
#>
[CmdletBinding()]param (
[string] $Name = 'DockerDesktopVM',
[switch] $KeepVolume,
[string] $VhdFile
)
Write-Verbose "Removing VM $Name..."
Hyper-V\Remove-VM $Name -Force -ea SilentlyContinue
if (!$KeepVolume) {
Write-Verbose "Delete VHD $VhdFile"
Remove-Item $VhdFile -ea SilentlyContinue
}
}
function Set-MobyLinuxVHDPath {
[CmdletBinding()]param (
$VM,
[string] $VhdFile,
[long] $VhdSize = 64*1000*1000*1000
)
$vhd = Get-VHD -Path $VhdFile -ea SilentlyContinue
if (!$vhd) {
Write-Verbose "Creating dynamic VHD: $VhdFile"
$vhd = New-VHD -Path $VhdFile -Dynamic -SizeBytes $VhdSize -BlockSizeBytes 1MB
}
if ($vm.HardDrives.Path -ne $VhdFile) {
if ($vm.HardDrives) {
Write-Verbose "Remove existing VHDs"
Hyper-V\Remove-VMHardDiskDrive $vm.HardDrives -ea SilentlyContinue
}
Write-Verbose "Attach VHD $VhdFile"
Hyper-V\Add-VMHardDiskDrive -VMName $vm.Name -Path $VhdFile
}
}
function Start-MobyLinuxVM {
<#
.SYNOPSIS
Starts the MobyLinux virtual machine
.DESCRIPTION
Starts the MobyLinux virtual network switch, configuring the devices as required.
.PARAMETER Name
Name of the MobyLinux virtual machine
.PARAMETER VhdFile
Path of the VHD file
.PARAMETER VhdSize
Size of the VHD to create if does not already exist
.PARAMETER IsoFile
Path to the MobyLinux ISO image, must be set for Create/ReCreate
.PARAMETER ConfigIsoFile
Path to the MobyLinux config ISO image to be attached as a drive to the VM
.PARAMETER DockerIsoFile
Path to the MobyLinux docker ISO image to be attached as a drive to the VM
.PARAMETER KubeImageIsoFile
Path to the MobyLinux kubernetes ISO image to be attached as a drive to the VM
#>
[CmdletBinding()]param(
[string] $Name = 'DockerDesktopVM',
[string] $VhdFile,
[long] $VhdSize = 64*1000*1000*1000,
[string] $IsoFile = '.\docker-desktop.iso',
[string] $ConfigIsoFile,
[string] $DockerIsoFile,
[string] $KubeImageIsoFile
)
Write-Verbose "Starting VM $Name..."
$vm = Hyper-V\Get-VM $Name -ea SilentlyContinue
if ($vm.DVDDrives) {
Write-Verbose "Remove existing DVDs"
Hyper-V\Remove-VMDvdDrive $vm.DVDDrives -ea SilentlyContinue
}
Write-Verbose "Attach DVD $IsoFile"
Hyper-V\Add-VMDvdDrive -VMName $Name -ControllerNumber 0 -ControllerLocation 1 -Path $IsoFile
if ((Get-Item $ConfigIsoFile).length -gt 0) {
Write-Verbose "Attach Config ISO $ConfigIsoFile"
if ((Get-VMScsiController -VMName $Name).length -le 1) {
Add-VMScsiController -VMName $Name
}
Hyper-V\Add-VMDvdDrive -VMName $Name -ControllerNumber 1 -ControllerLocation 1 -Path $ConfigIsoFile
}
if ((Get-Item $DockerIsoFile).length -gt 0) {
Write-Verbose "Attach Docker ISO $DockerIsoFile"
if ((Get-VMScsiController -VMName $Name).length -le 2) {
Add-VMScsiController -VMName $Name
}
Hyper-V\Add-VMDvdDrive -VMName $Name -ControllerNumber 2 -ControllerLocation 1 -Path $DockerIsoFile
}
if (($KubeImageIsoFile) -And (Test-Path $KubeImageIsoFile)) {
Write-Verbose "Attach Kube images ISO $KubeImageIsoFile"
if ((Get-VMScsiController -VMName $Name).length -le 3) {
Add-VMScsiController -VMName $Name
}
Hyper-V\Add-VMDvdDrive -VMName $Name -ControllerNumber 3 -ControllerLocation 1 -Path $KubeImageIsoFile
}
Set-MobyLinuxVHDPath -VM $vm -VhdSize $VhdSize -VhdFile $VhdFile
$iso = Hyper-V\Get-VMFirmware -VMName $Name | Select-Object -ExpandProperty BootOrder | Where-Object { $_.FirmwarePath.EndsWith("Scsi(0,1)") }
Hyper-V\Set-VMFirmware -VMName $Name -EnableSecureBoot Off -BootOrder $iso
Hyper-V\Start-VM -VMName $Name
}
function Stop-MobyLinuxVM {
<#
.SYNOPSIS
Stops the MobyLinux virtual machine
.DESCRIPTION
Stops the MobyLinux virtual machine
.PARAMETER Name
Name of the MobyLinux virtual machine
.PARAMETER SwitchName
Name of the MobyLinux virtual network switch
.EXAMPLE
Stop-MobyLinuxVM
.EXAMPLE
Stop-MobyLinuxVM -Name DockerVM
#>
[CmdletBinding()]param(
[string] $Name = 'DockerDesktopVM',
[string] $SwitchName = ''
)
$vms = Hyper-V\Get-VM $Name -ea SilentlyContinue
if (!$vms) {
Write-Verbose "VM $Name does not exist"
return
}
# Don't use a foreach here as the collection is modified during enumeration in the rare case there is >1 VM
for ($i = 0; $i -lt $vms.Count; $i++) {
$vm = $vms[$i];
Stop-VMForce -VM $vm -Name $Name -SwitchName $SwitchName
}
}
function Stop-VMForce {
[CmdletBinding()]param(
$VM,
[string] $Name = 'DockerDesktopVM',
[string] $SwitchName = ''
)
if ($VM.State -eq 'Off') {
Write-Verbose "VM $Name is stopped"
return
}
$code = {
Param($vmId) # Passing the $VM ref is not possible because it will be disposed already
$VM = Hyper-V\Get-VM -Id $vmId -ea SilentlyContinue
if (!$VM) {
Write-Verbose "VM with Id $vmId does not exist"
return
}
$shutdownService = Hyper-V\Get-VMIntegrationService -Name $Name Shutdown -ea SilentlyContinue
if ($shutdownService -and $shutdownService.PrimaryOperationalStatus -eq 'Ok') {
Write-Verbose "Shutdown VM $Name..."
Hyper-V\Stop-VM -Name $Name -Confirm:$false -Force -ea SilentlyContinue
if ($VM.State -eq 'Off') {
return
}
}
Write-Verbose "Turn Off VM $Name..."
Hyper-V\Stop-VM -Name $Name -Confirm:$false -TurnOff -Force -ea SilentlyContinue
}
Write-Verbose "Stopping VM $Name..."
$job = Start-Job -ScriptBlock $code -ArgumentList $VM.VMId.Guid
if (Wait-Job $job -Timeout 20) { Receive-Job $job }
Remove-Job -Force $job -ea SilentlyContinue
if ($VM.State -eq 'Off') {
Write-Verbose "VM $Name is stopped"
return
}
# If the VM cannot be stopped properly after the timeout
# then we have to kill the process and wait till the state changes to "Off"
for ($count = 1; $count -le 10; $count++) {
$ProcessID = (Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "Name = '$($VM.Id.Guid)'").ProcessID
if (!$ProcessID) {
Write-Verbose "VM $Name killed. Waiting for state to change"
for ($count = 1; $count -le 20; $count++) {
if ($VM.State -eq 'Off') {
Write-Verbose "Killed VM $Name is off"
Remove-MobyLinuxSwitch -Name $SwitchName
Remove-MobyLinuxVM -Name $Name -KeepVolume
return
}
Start-Sleep -Seconds 1
}
throw "Killed VM $Name did not stop"
}
Write-Verbose "Kill VM $Name process..."
Stop-Process $ProcessID -Force -Confirm:$false -ea SilentlyContinue
Start-Sleep -Seconds 1
}
throw "Couldn't stop VM $Name"
}
# Main entry point
Try {
Enable-MobyLinuxRequiredService -Name vmcompute
Enable-MobyLinuxRequiredService -Name vmms
Switch ($PSCmdlet.ParameterSetName) {
'Stop' { Stop-MobyLinuxVM -Name $VmName -SwitchName $SwitchName -Verbose }
'Destroy' { Remove-MobyLinux -VmName $VmName -SwitchName $SwitchName -KeepVolume:$KeepVolume -VhdFile $VhdPathOverride -Verbose }
'Create' { New-MobyLinux -SwitchName $SwitchName -SubnetAddress $SwitchSubnetAddress -SubnetMaskSize $SwitchSubnetMaskSize -VmName $VmName -CPUs $CPUs -Memory $Memory -VhdFile $VhdPathOverride -VhdSize $VhdSize -IsoFile $IsoFile -ConfigIsoFile $confIsoFile -DockerIsoFile $DockerIsoFile -KubeImageIsoFile $KubeImageIsoFile -PreferredIPAddress $PreferredIPAddress -Verbose }
'Start' { Start-MobyLinuxVM -Name $VmName -VhdFile $VhdPathOverride -VhdSize $VhdSize -IsoFile $IsoFile -ConfigIsoFile $confIsoFile -DockerIsoFile $DockerIsoFile -KubeImageIsoFile $KubeImageIsoFile -Verbose }
}
} Catch {
throw
Exit 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment