Skip to content

Instantly share code, notes, and snippets.

@amigus
Last active February 3, 2024 21:41
Show Gist options
  • Save amigus/9b68b2e25f5876505d4b81987bd05055 to your computer and use it in GitHub Desktop.
Save amigus/9b68b2e25f5876505d4b81987bd05055 to your computer and use it in GitHub Desktop.
Create a Generation 2 Hyper-V VM easily with reasonable defaults.
<#
.SYNOPSIS
Easily create a new Gen 2 Hyper-V VM with reasonable defaults.
.DESCRIPTION
Easily create a Generation 2 VM using a VHD or ISO image.
The defaults are also meant to be "reasonable in most cases," so as to allow
lazy people like me to get more VMs for less typing. 🦥
Hyper-V creates a 'Default Switch' by default. This script uses that switch by
default too. 🪞
It assumes that a VM should get about 10% of the host's memory but never
less than 1GB and never more than 16GB. 🍕
It also assumes that a VM should get all the host's logical processors. 🚀
.FUNCTIONALITY
Using it to create space-efficient testing environments:
Create a VM with a new VHD that is set to boot from an installation ISO as in the
first example.
Next, after Sysprep, create a duplicate or (for space efficiency) a differencing
disk based on the VHD of the first VM. Create each new VHD using the same name as
the VM that will use it to avoid specifying it as a parameter.
The third example is for edge cases like spinning up a VM from a VHD that is not
in the default VHD folder.
.EXAMPLE
# Create a VM with a new VHD and a DVD with an ISO image to boot from.
PS > .\Create-VM.ps1 -Name 'base' -ISOImage 'C:\windows_server.iso'
.EXAMPLE
# First, lets override these completely unreasonable defaults... 🙄
PS > $PSDefaultParameterValues['Create-VM.ps1:GuestServices'] = $true
PS > $PSDefaultParameterValues['Create-VM.ps1:MemoryStartupBytes'] = '5GB'
PS > $PSDefaultParameterValues['Create-VM.ps1:SwitchName'] = 'My Switch'
# Next, create some VHDs with the same names that you intend to use for the VMs.
PS > New-VHD -Path 'C:\dc.vhdx' -ParentPath 'C:\base.vhdx'
PS > .\Create-VM.ps1 -Name 'dc'
PS > New-VHD -Path 'C:\mssql.vhdx' -ParentPath 'C:\base.vhdx'
PS > .\Create-VM.ps1 -Name 'mssql' -MemoryStartupBytes 8GB
PS > New-VHD -Path 'C:\web.vhdx' -ParentPath 'C:\base.vhdx'
PS > .\Create-VM.ps1 -Name 'web' -Processors 4
# Lastly, create a VM using an existing VHD in a non-default folder.
PS > .\Create-VM.ps1 -Name 'newone' -VHDPath 'C:\imageigotonline.vhdx'
#>
#region Parameters
[CmdletBinding(DefaultParameterSetName = 'VHDName')]
param (
# Name of the new Virtual Machine
[Parameter(Mandatory, Position = 1)][string]$Name,
# An existing VHD in a non-default folder
[Parameter(Mandatory, ParameterSetName = 'VHDPath', Position = 2)][string]$VHDPath,
# The size of the VHD of the new Virtual Hard Disk
[Parameter(Mandatory, ParameterSetName = 'ISO', Position = 2)][Int64]$NewDiskSizeBytes,
# The ISO to boot the Virtual Machine from
[Parameter(Mandatory, ParameterSetName = 'ISO', Position = 3)][string]$ISOImagePath,
# The name of an existing VHD in the host's Virtual Hard Disk Folder
[Parameter(ParameterSetName = 'VHDName', Position = 2)]
[string]$VHDName = ('{0}.vhdx' -f $Name, '.vhdx'),
# The name of Virtual Network Switch to bind the Network Adapter of to
[Parameter()][string]$SwitchName = 'Default Switch',
# The amount of startup memory in bytes; 10% of the host's memory up to 16GB
[Parameter()][Int64]$MemoryStartupBytes = (
[Math]::Clamp(
[Math]::Pow(2, [Math]::Round([Math]::Log2((Get-VMHost).MemoryCapacity * 0.1))),
[Math]::Pow(1024, 3) * 1,
[Math]::Pow(1024, 3 * 16)
)
),
# The number of logical processors; defaults to all of them
[Parameter()][int]$Processors = (
Get-VMHost | Select-Object -ExpandProperty LogicalProcessorCount
),
[Parameter()][switch]$GuestServices = $false,
[Parameter()][switch]$SecureBoot = $false,
[Parameter()][switch]$UseTPM = $false,
[Parameter()][switch]$EncryptStateAndVmMigrationTraffic = $false
)
#endregion
#region Create
$SetVMParameters = @{
ProcessorCount = $Processors
DynamicMemory = $true
AutomaticStartAction = 'Nothing'
AutomaticStopAction = 'ShutDown'
AutomaticCheckpointsEnabled = $false
CheckpointType = 'Disabled'
}
New-VM $Name $MemoryStartupBytes 2 -SwitchName $SwitchName |
Set-VM @SetVMParameters -Passthru |
Set-Variable VM
if ($GuestServices.IsPresent) {
Enable-VMIntegrationService -Name "Guest Service Interface" -VM $VM
}
#endregion
#region Security
Set-VMKeyProtector -VM $VM -NewLocalKeyProtector
if ($UseTPM.IsPresent -or $EncryptStateAndVmMigrationTraffic.IsPresent) {
Enable-VMTPM -VM $VM
Set-VMSecurityPolicy -VM $VM -BindToHostTpm $true
}
Set-VMSecurity -VM $VM -EncryptStateAndVmMigrationTraffic $EncryptStateAndVmMigrationTraffic.IsPresent
Set-VMFirmware -VM $VM -EnableSecureBoot $( if ($SecureBoot.IsPresent) { 'On' } else { 'Off' } )
#endregion
#region Disk and booting
switch ($PSCmdlet.ParameterSetName) {
{
$_ -in @('ISO', 'VHDName')
} {
$VHDPath = Join-Path -Path (Get-VMHost).VirtualHardDiskPath -ChildPath $VHDName
Write-Debug "${VHDName} resolves to ${VHDPath}"
}
'ISO' {
New-VHD -Dynamic -Path $VHDPath -SizeBytes $NewDiskSizeBytes -ErrorAction 'Stop' | Out-Null
Add-VMHardDiskDrive -VM $VM -ControllerLocation 0 -Path $VHDPath
Add-VMDvdDrive -VM $VM -ControllerLocation 1 -Path $ISOImagePath
Set-VMFirmware -VM $VM -BootOrder (
Get-VMFirmware -VM $VM | Select-Object -ExpandProperty BootOrder
)[2, 1] # The DVD then the VHD; no NIC
}
{
$_ -in @('VHDName', 'VHDPath')
} {
Test-VHD -Path $VHDPath -ErrorAction 'Stop' | Out-Null
Add-VMHardDiskDrive -VM $VM -ControllerLocation 0 -Path $VHDPath
Set-VMFirmware -VM $VM -BootOrder (
Get-VMFirmware -VM $VM | Select-Object -ExpandProperty BootOrder
)[1] # The VHD; no NIC
}
}
$VM
#endregio
# SIG # Begin signature block
# MIIVswYJKoZIhvcNAQcCoIIVpDCCFaACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAwfFanb62NGO2H
# lMlQHOKn/HxK0RQnj3NDAcBh/9PRv6CCEe8wggVvMIIEV6ADAgECAhBI/JO0YFWU
# jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM
# EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy
# dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG
# EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv
# IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s
# hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD
# J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7
# P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme
# me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz
# T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q
# RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz
# mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc
# QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T
# OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/
# AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID
# AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD
# VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV
# HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE
# VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v
# ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE
# KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI
# hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF
# OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC
# J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ
# pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl
# d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH
# +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggYaMIIEAqADAgECAhBiHW0M
# UgGeO5B5FSCJIRwKMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYTAkdCMRgwFgYD
# VQQKEw9TZWN0aWdvIExpbWl0ZWQxLTArBgNVBAMTJFNlY3RpZ28gUHVibGljIENv
# ZGUgU2lnbmluZyBSb290IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5
# NTlaMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzAp
# BgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwggGiMA0G
# CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCbK51T+jU/jmAGQ2rAz/V/9shTUxjI
# ztNsfvxYB5UXeWUzCxEeAEZGbEN4QMgCsJLZUKhWThj/yPqy0iSZhXkZ6Pg2A2NV
# DgFigOMYzB2OKhdqfWGVoYW3haT29PSTahYkwmMv0b/83nbeECbiMXhSOtbam+/3
# 6F09fy1tsB8je/RV0mIk8XL/tfCK6cPuYHE215wzrK0h1SWHTxPbPuYkRdkP05Zw
# mRmTnAO5/arnY83jeNzhP06ShdnRqtZlV59+8yv+KIhE5ILMqgOZYAENHNX9SJDm
# +qxp4VqpB3MV/h53yl41aHU5pledi9lCBbH9JeIkNFICiVHNkRmq4TpxtwfvjsUe
# dyz8rNyfQJy/aOs5b4s+ac7IH60B+Ja7TVM+EKv1WuTGwcLmoU3FpOFMbmPj8pz4
# 4MPZ1f9+YEQIQty/NQd/2yGgW+ufflcZ/ZE9o1M7a5Jnqf2i2/uMSWymR8r2oQBM
# dlyh2n5HirY4jKnFH/9gRvd+QOfdRrJZb1sCAwEAAaOCAWQwggFgMB8GA1UdIwQY
# MBaAFDLrkpr/NZZILyhAQnAgNpFcF4XmMB0GA1UdDgQWBBQPKssghyi47G9IritU
# pimqF6TNDDAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNV
# HSUEDDAKBggrBgEFBQcDAzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEsG
# A1UdHwREMEIwQKA+oDyGOmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1
# YmxpY0NvZGVTaWduaW5nUm9vdFI0Ni5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsG
# AQUFBzAChjpodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2Rl
# U2lnbmluZ1Jvb3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0
# aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEABv+C4XdjNm57oRUgmxP/BP6YdURh
# w1aVcdGRP4Wh60BAscjW4HL9hcpkOTz5jUug2oeunbYAowbFC2AKK+cMcXIBD0Zd
# OaWTsyNyBBsMLHqafvIhrCymlaS98+QpoBCyKppP0OcxYEdU0hpsaqBBIZOtBajj
# cw5+w/KeFvPYfLF/ldYpmlG+vd0xqlqd099iChnyIMvY5HexjO2AmtsbpVn0OhNc
# WbWDRF/3sBp6fWXhz7DcML4iTAWS+MVXeNLj1lJziVKEoroGs9Mlizg0bUMbOalO
# hOfCipnx8CaLZeVme5yELg09Jlo8BMe80jO37PU8ejfkP9/uPak7VLwELKxAMcJs
# zkyeiaerlphwoKx1uHRzNyE6bxuSKcutisqmKL5OTunAvtONEoteSiabkPVSZ2z7
# 6mKnzAfZxCl/3dq3dUNw4rg3sTCggkHSRqTqlLMS7gjrhTqBmzu1L90Y1KWN/Y5J
# KdGvspbOrTfOXyXvmPL6E52z1NZJ6ctuMFBQZH3pwWvqURR8AgQdULUvrxjUYbHH
# j95Ejza63zdrEcxWLDX6xWls/GDnVNueKjWUH3fTv1Y8Wdho698YADR7TNx8X8z2
# Bev6SivBBOHY+uqiirZtg0y9ShQoPzmCcn63Syatatvx157YK9hlcPmVoa1oDE5/
# L9Uo2bC5a4CH2RwwggZaMIIEwqADAgECAhA/t2IdfwMBRYVAcCilY4r8MA0GCSqG
# SIb3DQEBDAUAMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0
# ZWQxKzApBgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYw
# HhcNMjEwOTIwMDAwMDAwWhcNMjQwOTE5MjM1OTU5WjBeMQswCQYDVQQGEwJVUzER
# MA8GA1UECAwIRGVsYXdhcmUxHTAbBgNVBAoMFFRoZSBNaWd1cyBHcm91cCwgTExD
# MR0wGwYDVQQDDBRUaGUgTWlndXMgR3JvdXAsIExMQzCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAMvjKurDy+SoqOZHCQRmPMTE2vJZa/0UEL23aj3ib2Hu
# qvgftKqxYW0Xj0GuWeE5663ZPkuBiBbOVT17qmZSBJIt/nE7HwJw392oBkwz9Yzf
# QH7Q407NuZlbqm6tsYZkF0Ejlj927j9deivAKg9SgBTirnvHb9e3fu16gYi8ile1
# k0PZsjj9SqyM+k95rfnYbEA0Hrq3L8msO4gTX1VHSyYRJ2taT04Vqmm8rFur+oN0
# k7ioS38/XQ6xynGRgpRlrkT2XvQXHt5VfR8yBSydBgp6YrEwRKjd5FGcWzUWwWnF
# 8U5h0OwgosMh/TYowMKqCa3khKITw8nlZz7uhbODsGkJLQJjFkO9oz0Ix1bWcdkP
# HOts34PPvTVPEyZxcjdzD68FyifhDPdL988rWNF5jfss7hWRCy5Wh0n8zfZLdEsn
# K2bXvArKgFKvKIH9qyK4nyhR6Z+Xj71n5ceWJImY1piuTwQaOxywVnLOshcVbrJO
# 9mdWlToyxEwejMw8DR/OiOrVhPX1HjFVCmFTZoRvrRT1bMVGOa94m1oYsEixg+wK
# UJ0J7Hus9uE3LJr+l4gfwoy0Feffj4cA8X2ZAF1U3UvJ5HaJNkkdxej0lkwz0+NC
# j5xSQ3CDXtA2FYlLrzRb2Al96UlJVmlMH551gQRzQv5foblGfo/UZc+a2n/veq2h
# AgMBAAGjggGcMIIBmDAfBgNVHSMEGDAWgBQPKssghyi47G9IritUpimqF6TNDDAd
# BgNVHQ4EFgQUKb+GambINobdBbRQHFORjX+g5sIwDgYDVR0PAQH/BAQDAgeAMAwG
# A1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEQYJYIZIAYb4QgEBBAQD
# AgQQMEoGA1UdIARDMEEwNQYMKwYBBAGyMQECAQMCMCUwIwYIKwYBBQUHAgEWF2h0
# dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAEEATBJBgNVHR8EQjBAMD6gPKA6
# hjhodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmlu
# Z0NBUjM2LmNybDB5BggrBgEFBQcBAQRtMGswRAYIKwYBBQUHMAKGOGh0dHA6Ly9j
# cnQuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVTaWduaW5nQ0FSMzYuY3J0
# MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0B
# AQwFAAOCAYEACScMGtWQDOwCKMahfzmu+6vqk5UL9Z/Dndzw8fMVCpim0A1DliGC
# yD+Xp+dkQjE4BcX1+dLD8nt338l4YrqiekiAFhaMvvM6iyzSiVj6+hKs4j2jHp6f
# TzgeDsZkBICBJQZywknM/ZqEOgaWsHZuPFSYRuRb0/OKqN5adla9h3OkmMlUVBoY
# ilRtxbMpU1MzDYicN2IM2Ti5KvMpy6V9EUlDpfmdG3vW6wXd46D7D3lYzS5jG01j
# edhDLdm2XxEoKxY1Sap4Ax5tr4bjsEHeeKK9wSqoe3IODdvUnFlwBllVakGgCOxv
# MODEnWdVX0Ks+OgVYTZ8xWVoU29FHCibUBesTY1DJCyWz1G+2ZoJOBFLHs8KXnXa
# S5sa4LrINcuZR3OUZmy2n4XT/Fumw1l3qrrCJctqpQsf+OKHWfX6hoTzYHKO9XhS
# CMWP0Um4NWH8eOAjSzEYn362vvLxSp8tD1G4DF6JXcjiuiQ8Q9nciId0HJA7R6u6
# W0xAn28OTNNUMYIDGjCCAxYCAQEwaDBUMQswCQYDVQQGEwJHQjEYMBYGA1UEChMP
# U2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDEyJTZWN0aWdvIFB1YmxpYyBDb2RlIFNp
# Z25pbmcgQ0EgUjM2AhA/t2IdfwMBRYVAcCilY4r8MA0GCWCGSAFlAwQCAQUAoIGE
# MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQB
# gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkE
# MSIEICUxbs/dsgu4J1W+eh4pcwoQS3wf9ElV1cuaf87LsX3NMA0GCSqGSIb3DQEB
# AQUABIICALC7Zb4nrBWLXOxfc8kByyPx6AX3I3HAm6qpGlloiNcVYaMsm3NWREvv
# pbJQfDBwqT0tRdZuATsAjrb8SPQ4rVfjr7yEI/Q0gr8RcRHKSyz9wvAZqziM7gJS
# CFcf8P/81hqpq8EUIAsxJArwsOfQvpREbd+2FHUNoroQanJr7f+2M3To5U+gNp8e
# ZobPIrz/UpkWBKW4oYVK0VlFrL5k6slWRMRCAT1IoONrF//i8DRb4SM/Wc/MiAFM
# nJHnPBDM0yKx7QnsnXc2QRZXbmg/KuIqUpY9OC5PXeMCu1Tn/fEqFCoyBUiFbqAA
# Qx1pFFNx3Uzb6AnK7lddcz0DYdx2ftLJErmxPqg7kkTFvOFxPC2d+uDvUMZ10UMh
# mK0tirPMGL9aVuHmpln+JNw+nWs1mKuyOO1af8MSWE/+pzIVq8Jci7r9xr1k5zFH
# yVmnf/rhDRwMrUQ6fbac6GU3zgV3aIfnYpBY+acrp+2siV5XeOw71GQD+reUTnIC
# oMNWgrDZiE7EjXN5uTB1YHPHjZnHT+mQybRmr4vIPNOuoUFElnMsgH7wpDuv1ALY
# O8qa+Ubod0H7Tzodab47k/lMVo4/EEQLghAssEjQiI+wIVuHYYBOI/ueaJVGitPG
# L8IO1zLeBEiC/RHZMcOP/OyMATymCvssUcOwlTwjL32ZscesyQ/S
# SIG # End signature block
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment