public/New-Lably.ps1
Function New-Lably { <# .SYNOPSIS Creates a new empty lab in the current directory or defined path. .DESCRIPTION This function is used to create a new Lably (lab or lab scaffold) that will be used to store the meta data for this specific lab. Function will create a scaffold.lably.json in the defined path that other Lably functions will use to store the metadata. .PARAMETER Path Optional parameter to define where the lably will be created. The scaffold, template cache, and (optionally) virtual disks folders will be created within this folder. If this parameter is not defined, it will default to the path from which the function was called. .PARAMETER Name Optional name of the Lably. Used as a description when using and describing the lab, as well as the default prefix for the display name of VMs created in Hyper-V. If this parameter is not defined, it will default to the name of the folder it's being created it. .PARAMETER CreateSwitch Name of switch to be created in Hyper-V for this lab. Either CreateSwitch or SwitchName are required. If neither parameter is defined, it will default creating a new switch using the name of the folder it's being created it. .PARAMETER Switch Name of exiting Hyper-V switch to be used for this lab. Either CreateSwitch or SwitchName are required. If neither parameter is defined, it will default creating a new switch using the name of the folder it's being created it. .PARAMETER NatIPAddress Optional parameter to be used when using CreateSwitch to define the IP Address to create for this network to use to access the host's network. .PARAMETER NatIPCIDRRange Optional parameter to be used to define the CIDR range of the VMs that will use the switch. Required if NatIPAddress is defined. .PARAMETER VirtualDiskPath Optional parameter to define the folder in which new differencing disks for this lab will be created when New-LablyVM is called. Defaults to a 'Virtual Disks' subfolder of the 'Path' parameter. .PARAMETER SecretKeyFile Switch used to indicate that instead of using the computer/user accounts to encrypt secrets that an AES key file should be created. When using keyfiles, ensure that appropriate NTFS permissions are set on your Lably folder to ensure that keys cannot be read by other users. .INPUTS None. You cannot pipe objects to New-Lably. .OUTPUTS None. The function will either complete successfully or throw an error. .EXAMPLE New-Lably -Name "Chris' Lab" .EXAMPLE New-Lably -Name "Chris' Lab" -CreateSwitch "Test Switch #1" .EXAMPLE New-Lably -Name "Chris' Lab" -CreateSwitch "Test Switch #2" -NATIPAddress 10.0.0.1 -NATIPCIDRRange 10.0.0.0/24 .EXAMPLE New-Lably -Name "Chris' Lab" -Switch "Existing Switch #3" .EXAMPLE New-Lably -Name "Chris' Lab" -VirtualDiskPath "D:\LabVMs" -SecretKeyFile #> [CmdLetBinding(DefaultParameterSetName='NewSwitch')] Param( [Parameter(Mandatory=$False)] [String]$Path = $PWD, [Parameter(Mandatory=$False)] [String]$Name = (Split-Path $Path -Leaf), [Parameter(Mandatory=$False,ParameterSetName='NewSwitch')] [Parameter(Mandatory=$False,ParameterSetName='NewSwitchNAT')] [String]$CreateSwitch = (Split-Path $Path -Leaf), [Parameter(Mandatory=$True,ParameterSetName='NewSwitchNAT')] [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')] [String]$NATIPAddress, [Parameter(Mandatory=$True,ParameterSetName='NewSwitchNAT')] [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/\d{1,2}$')] [String]$NATRangeCIDR, [Parameter(Mandatory=$False,ParameterSetName='Switch')] [String]$Switch, [Parameter(Mandatory=$False)] [String]$VirtualDiskPath, [Parameter(Mandatory=$False)] [Switch]$SecretKeyFile ) ValidateModuleRun -RequiresAdministrator If(Get-ChildItem -Path $Path -ErrorAction SilentlyContinue) { Throw "Cannot create lably in $Path as it contains other files/folders. A Lably should be created in clean folders." } Try { If(-Not(Test-Path $Path)) { Write-Verbose "Creating $Path" New-Item -ItemType Directory -Path $Path | Out-Null } } Catch { Throw "Cannot Create $Path. $($_.Exception.Message)" } If($Switch) { Write-Host "Should use switch $switch" Try { $VMSwitch = Get-VMSwitch -Name $Switch -ErrorAction Stop } Catch { Throw "Cannot get switch by Name '$Switch'. $($_.Exception.Message)" } } If(-Not($Switch)) { $CreateSwitch = $CreateSwitch -replace "[^A-Za-z0-9 ]","" If(Get-VMSwitch -Name $CreateSwitch -ErrorAction SilentlyContinue) { Throw "Virtual Adapter '$CreateSwitch' already exists." } Try { Write-Verbose "Creating Switch '$CreateSwitch'." $VMSwitch = New-VMSwitch -Name $CreateSwitch -SwitchType Internal } Catch { Throw "Cannot create '$CreateSwitch'. $($_.Exception.Message)" } If($NATIPAddress) { If(Get-NetIPAddress -IPAddress $NATIPAddress -ErrorAction SilentlyContinue) { Throw "NAT IP Address $NATIPAddress Already Exists on System. This must be unique." } Write-Verbose "Setting up NAT for Switch" $SwitchMAC = Get-VMNetworkAdapter -ManagementOS | Where-Object { $_.Name -eq $CreateSwitch } | Select-Object -ExpandProperty MacAddress Write-Verbose "Virtual Switch MAC Address is $SwitchMAC" If(-Not($SwitchMAC)) { Write-Warning "Could not find MAC Address of Virtual Switch. Aborting NAT setup." $VirtualAdapter = "" } Else { $VirtualAdapter = Get-NetAdapter | Where-Object { $($_.MacAddress -Replace '-','') -eq $SwitchMAC } Write-Verbose "Virtual Adapter is '$($VirtualAdapter.Name)'" } If(-Not($VirtualAdapter)) { Write-Warning "Could not find virtual adapter for MAC Address. Aborting NAT setup." } Else { Try { Write-Verbose "Creating New NetIPAddress Bound to $($VirtualAdapter.Name)" $PrefixLength = $($NATRangeCIDR -split '/')[1] $NATIP = New-NetIPAddress -IPAddress $NATIPAddress -PrefixLength $PrefixLength -ifIndex ($VirtualAdapter.IfIndex) -ErrorAction Stop } Catch { Throw "Could not create NetIPAddress. Aborting NAT Setup. $($_.Exception.Message)" } } If($NATIP) { Write-Verbose "Configuring New NAT Rule" Try { $NewNAT = New-NetNat -Name "LablyNAT ($CreateSwitch)" -InternalIPInterfaceAddressPrefix $NATRangeCIDR -ErrorAction Stop } Catch { Write-Warning "Unable to create new NAT rule. Aborting NAT setup. $($_.Exception.Message)" } } } } If(-Not($VirtualDiskPath)) { $VirtualDiskPath = Join-Path $Path -ChildPath "Virtual Disks" } If($SecretKeyFile) { $SecretType = "KeyFile" Try { $KeyFilePath = Join-Path $env:USERPROFILE -ChildPath "Lably\Keys" If(-Not(Test-Path $KeyFilePath -ErrorAction SilentlyContinue)) { New-Item -ItemType Directory -Path $KeyFilePath -ErrorAction Stop | Out-Null } } Catch { Write-Warning "Could not create key file path. $($_.Exception.Message). Please manually create this folder." } Try { $KeyFile = Join-Path $KeyFilePath -ChildPath "$Name.$((New-TimeSpan -Start (Get-Date "01/01/1970") -End (Get-Date)).TotalSeconds).key" $KeyAES = New-Object Byte[] 32 [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($KeyAES) $KeyAES | Out-File $KeyFile } Catch { Throw "Unable to create secure key. $($_.Exception.Message)" } } else { $SecretType = "PowerShell" $KeyFile = $null } Try { Write-Verbose "Writing Scaffolding File" $ScaffoldFile = Join-Path $Path -ChildPath "scaffold.lably.json" [PSCustomObject]@{ Meta = @{ Name = $Name SwitchId = $VMSwitch.Id SwitchCreated = $(If($Switch) { $false } else { $true }) NATName = $(If($NewNAT) { $NewNAT.Name } else { $null }) NATIPCIDR = $NATRangeCIDR VirtualDiskPath = $VirtualDiskPath CreatedUTC = $(Get-DateUTC) ModifiedUTC = $(Get-DateUTC) } Secrets = @{ SecretType = $SecretType KeyFile = $KeyFile } } | ConvertTo-Json | Out-File $ScaffoldFile -Force } Catch { Throw "Could not create Lably $Name. $($_.Exception.Message)" } If(-Not(Test-Path $VirtualDiskPath -ErrorAction SilentlyContinue)) { Try { Write-Verbose "Creating Virtual Disk Path $VirtualDiskPath" New-Item -ItemType Directory -Path $VirtualDiskPath -ErrorAction Stop | Out-Null } Catch { Write-Warning "Could not create $VirtualDiskPath. $($_.Exception.Message). Please manually create this folder." } } $TemplateCachePath = Join-Path $Path -ChildPath "Template Cache" If(-Not(Test-Path $TemplateCachePath -ErrorAction SilentlyContinue)) { Try { Write-Verbose "Creating Template Cache Path $TemplateCachePath" New-Item -ItemType Directory -Path $TemplateCachePath -ErrorAction Stop | Out-Null } Catch { Write-Warning "Could not create $TemplateCachePath. $($_.Exception.Message). Please manually create this folder." } } Write-Host "Congratulations! Your Lably '$Name'." -ForegroundColor Green If($SecretKeyFile) { Write-Host "Warning! Your key files are stored in $($KeyFilePath)." -ForegroundColor Yellow Write-Host "You're encouraged to **secure this folder** to ensure your lab secrets are kept safe." -ForegroundColor Yellow } Else { Write-Host "Your secrets are secured by your computer and user account." -ForegroundColor Yellow Write-Host "You may not be able to recover your secrets if your computer or user account changes." -ForegroundColor Yellow } } |