Src/LabVM.ps1
function ResolveLabVMProperties { <# .SYNOPSIS Resolve lab VM properties. .DESCRIPTION Resolves a lab virtual machine properties, using VM-specific properties over-and-above the AllNodes.NodeName '*' and lab defaults. #> [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( ## Lab VM/Node name [Parameter(Mandatory, ValueFromPipeline)] [System.String] $NodeName, ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, ## Do not enumerate the AllNodes.'*' node [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $NoEnumerateWildcardNode ) process { $node = @{ }; $moduleName = $labDefaults.ModuleName; ## Set the node's display name, if defined. if ($ConfigurationData.NonNodeData.$moduleName.EnvironmentPrefix) { $node["$($moduleName)_EnvironmentPrefix"] = $ConfigurationData.NonNodeData.$moduleName.EnvironmentPrefix; } if ($ConfigurationData.NonNodeData.$moduleName.EnvironmentSuffix) { $node["$($moduleName)_EnvironmentSuffix"] = $ConfigurationData.NonNodeData.$moduleName.EnvironmentSuffix; } if (-not $NoEnumerateWildcardNode) { ## Retrieve the AllNodes.* properties $ConfigurationData.AllNodes.Where({ $_.NodeName -eq '*' }) | ForEach-Object { foreach ($key in $_.Keys) { $node[$key] = $_.$key; } } } ## Retrieve the AllNodes.$NodeName properties $ConfigurationData.AllNodes.Where({ $_.NodeName -eq $NodeName }) | ForEach-Object { foreach ($key in $_.Keys) { $node[$key] = $_.$key; } } ## Check VM defaults $labDefaultProperties = GetConfigurationData -Configuration VM; $properties = Get-Member -InputObject $labDefaultProperties -MemberType NoteProperty; foreach ($propertyName in $properties.Name) { ## Int32 values of 0 get coerced into $false! if (($node.$propertyName -isnot [System.Int32]) -and (-not $node.ContainsKey($propertyName))) { $node[$propertyName] = $labDefaultProperties.$propertyName; } } ## Set the node's friendly/display name $nodeDisplayName = $node.NodeName; $environmentPrefix = '{0}_EnvironmentPrefix' -f $moduleName; $environmentSuffix = '{0}_EnvironmentSuffix' -f $moduleName; if (-not [System.String]::IsNullOrEmpty($node[$environmentPrefix])) { $nodeDisplayName = '{0}{1}' -f $node[$environmentPrefix], $nodeDisplayName; } if (-not [System.String]::IsNullOrEmpty($node[$environmentSuffix])) { $nodeDisplayName = '{0}{1}' -f $nodeDisplayName, $node[$environmentSuffix]; } $node["$($moduleName)_NodeDisplayName"] = $nodeDisplayName; ## Rename/overwrite existing parameter values where $moduleName-specific parameters exist foreach ($key in @($node.Keys)) { if ($key.StartsWith("$($moduleName)_")) { $node[($key.Replace("$($moduleName)_",''))] = $node.$key; $node.Remove($key); } } return $node; } #end process } #end function Resolve-LabProperty function Get-LabVM { <# .SYNOPSIS Retrieves the current configuration of a VM. .DESCRIPTION Gets a virtual machine's configuration using the xVMHyperV DSC resource. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( ## Specifies the lab virtual machine/node name. [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData ) process { if (-not $Name) { # Return all nodes defined in the configuration $Name = $ConfigurationData.AllNodes | Where-Object NodeName -ne '*' | ForEach-Object { $_.NodeName; } } foreach ($nodeName in $Name) { $node = ResolveLabVMProperties -NodeName $nodeName -ConfigurationData $ConfigurationData; $xVMParams = @{ Name = $node.NodeDisplayName; VhdPath = ResolveLabVMDiskPath -Name $node.NodeDisplayName;; } try { ImportDscResource -ModuleName xHyper-V -ResourceName MSFT_xVMHyperV -Prefix VM; $vm = GetDscResource -ResourceName VM -Parameters $xVMParams; Write-Output -InputObject ([PSCustomObject] $vm); } catch { Write-Error -Message ($localized.CannotLocateNodeError -f $nodeName); } } #end foreach node } #end process } #end function Get-LabVM function Test-LabVM { <# .SYNOPSIS Checks whether the (external) lab virtual machine is configured as required. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( ## Specifies the lab virtual machine/node name. [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData ) process { if (-not $Name) { $Name = $ConfigurationData.AllNodes | Where-Object NodeName -ne '*' | ForEach-Object { $_.NodeName } } foreach ($vmName in $Name) { $isNodeCompliant = $true; $node = ResolveLabVMProperties -NodeName $vmName -ConfigurationData $ConfigurationData; WriteVerbose ($localized.TestingNodeConfiguration -f $node.NodeDisplayName); WriteVerbose ($localized.TestingVMConfiguration -f 'Image', $node.Media); if (-not (Test-LabImage -Id $node.Media)) { $isNodeCompliant = $false; } WriteVerbose ($localized.TestingVMConfiguration -f 'Virtual Switch', $node.SwitchName); if (-not (TestLabSwitch -Name $node.SwitchName -ConfigurationData $ConfigurationData)) { $isNodeCompliant = $false; } WriteVerbose ($localized.TestingVMConfiguration -f 'VHDX', $node.Media); if (-not (TestLabVMDisk -Name $node.NodeDisplayName -Media $node.Media -ErrorAction SilentlyContinue)) { $isNodeCompliant = $false; } WriteVerbose ($localized.TestingVMConfiguration -f 'VM', $vmName); $testLabVirtualMachineParams = @{ Name = $node.NodeDisplayName; SwitchName = $node.SwitchName; Media = $node.Media; StartupMemory = $node.StartupMemory; MinimumMemory = $node.MinimumMemory; MaximumMemory = $node.MaximumMemory; ProcessorCount = $node.ProcessorCount; MACAddress = $node.MACAddress; SecureBoot = $node.SecureBoot; GuestIntegrationServices = $node.GuestIntegrationServices; } if (-not (TestLabVirtualMachine @testLabVirtualMachineParams)) { $isNodeCompliant = $false; } Write-Output -InputObject $isNodeCompliant; } } #end process } #end function Test-LabVM function NewLabVM { <# .SYNOPSIS Creates a new lab virtual machine is configured as required. #> [CmdletBinding(DefaultParameterSetName = 'PSCredential')] param ( ## Specifies the lab virtual machine/node name. [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, ## Local administrator password of the VM. The username is NOT used. [Parameter(ParameterSetName = 'PSCredential', ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential = (& $credentialCheckScriptBlock), ## Local administrator password of the VM. [Parameter(Mandatory, ParameterSetName = 'Password', ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.Security.SecureString] $Password, ## Virtual machine DSC .mof and .meta.mof location [Parameter(ValueFromPipelineByPropertyName)] [System.String] $Path = (GetLabHostDSCConfigurationPath), ## Skip creating baseline snapshots [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $NoSnapshot, ## Is a quick VM [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $IsQuickVM ) begin { ## If we have only a secure string, create a PSCredential if ($PSCmdlet.ParameterSetName -eq 'Password') { $Credential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList 'LocalAdministrator', $Password; } if (-not $Credential) {throw ($localized.CannotProcessCommandError -f 'Credential'); } elseif ($Credential.Password.Length -eq 0) { throw ($localized.CannotBindArgumentError -f 'Password'); } } process { $node = ResolveLabVMProperties -NodeName $Name -ConfigurationData $ConfigurationData -ErrorAction Stop; $NodeName = $node.NodeName; ## Display name includes any environment prefix/suffix $DisplayName = $node.NodeDisplayName; ## Don't attempt to check certificates for 'Quick VMs' if (-not $IsQuickVM) { ## Check for certificate before we (re)create the VM if (-not [System.String]::IsNullOrWhitespace($node.ClientCertificatePath)) { $expandedClientCertificatePath = [System.Environment]::ExpandEnvironmentVariables($node.ClientCertificatePath); if (-not (Test-Path -Path $expandedClientCertificatePath -PathType Leaf)) { throw ($localized.CannotFindCertificateError -f 'Client', $node.ClientCertificatePath); } } else { WriteWarning ($localized.NoCertificateFoundWarning -f 'Client'); } if (-not [System.String]::IsNullOrWhitespace($node.RootCertificatePath)) { $expandedRootCertificatePath = [System.Environment]::ExpandEnvironmentVariables($node.RootCertificatePath); if (-not (Test-Path -Path $expandedRootCertificatePath -PathType Leaf)) { throw ($localized.CannotFindCertificateError -f 'Root', $node.RootCertificatePath); } } else { WriteWarning ($localized.NoCertificateFoundWarning -f 'Root'); } } #end if not quick VM foreach ($switchName in $node.SwitchName) { WriteVerbose ($localized.SettingVMConfiguration -f 'Virtual Switch', $switchName); SetLabSwitch -Name $switchName -ConfigurationData $ConfigurationData; } if (-not (Test-LabImage -Id $node.Media)) { [ref] $null = New-LabImage -Id $node.Media -ConfigurationData $ConfigurationData; } WriteVerbose ($localized.ResettingVMConfiguration -f 'VHDX', "$DisplayName.vhdx"); ResetLabVMDisk -Name $DisplayName -Media $node.Media -ErrorAction Stop; WriteVerbose ($localized.SettingVMConfiguration -f 'VM', $DisplayName); $setLabVirtualMachineParams = @{ Name = $DisplayName; SwitchName = $node.SwitchName; Media = $node.Media; StartupMemory = $node.StartupMemory; MinimumMemory = $node.MinimumMemory; MaximumMemory = $node.MaximumMemory; ProcessorCount = $node.ProcessorCount; MACAddress = $node.MACAddress; SecureBoot = $node.SecureBoot; GuestIntegrationServices = $node.GuestIntegrationServices; } SetLabVirtualMachine @setLabVirtualMachineParams; $media = ResolveLabMedia -Id $node.Media -ConfigurationData $ConfigurationData; if ($media.OperatingSystem -eq 'Linux') { ## Skip injecting files for Linux VMs.. } else { ## Only mount the VHDX to copy resources if needed! if ($node.Resource) { WriteVerbose ($localized.AddingVMResource -f 'VM'); $setLabVMDiskResourceParams = @{ ConfigurationData = $ConfigurationData; NodeName = $NodeName; DisplayName = $DisplayName; } SetLabVMDiskResource @setLabVMDiskResourceParams; } WriteVerbose ($localized.AddingVMCustomization -f 'VM'); ## DSC resources and unattend.xml $setLabVMDiskFileParams = @{ Name = $NodeName; NodeData = $node; Path = $Path; Credential = $Credential; CoreCLR = $media.CustomData.SetupComplete -eq 'CoreCLR'; } $resolveCustomBootStrapParams = @{ CustomBootstrapOrder = $node.CustomBootstrapOrder; ConfigurationCustomBootstrap = $node.CustomBootstrap; MediaCustomBootStrap = $media.CustomData.CustomBootstrap; } $customBootstrap = ResolveCustomBootStrap @resolveCustomBootStrapParams; if ($customBootstrap) { $setLabVMDiskFileParams['CustomBootstrap'] = $customBootstrap; } SetLabVMDiskFile @setLabVMDiskFileParams; } #end Windows VMs if (-not $NoSnapshot) { $snapshotName = $localized.BaselineSnapshotName -f $labDefaults.ModuleName; WriteVerbose ($localized.CreatingBaselineSnapshot -f $snapshotName); Checkpoint-VM -Name $DisplayName -SnapshotName $snapshotName; } if ($node.WarningMessage) { if ($node.WarningMessage -is [System.String]) { WriteWarning ($localized.NodeCustomMessageWarning -f $NodeName, $node.WarningMessage); } else { WriteWarning ($localized.IncorrectPropertyTypeError -f 'WarningMessage', '[System.String]') } } Write-Output -InputObject (Get-VM -Name $DisplayName); } #end process } #end function NewLabVM function RemoveLabVM { <# .SYNOPSIS Deletes a lab virtual machine. #> [CmdletBinding()] param ( ## Specifies the lab virtual machine/node name. [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, ## Include removal of virtual switch(es). By default virtual switches are not removed. [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $RemoveSwitch ) process { $node = ResolveLabVMProperties -NodeName $Name -ConfigurationData $ConfigurationData -NoEnumerateWildcardNode -ErrorAction Stop; if (-not $node.NodeName) { throw ($localized.CannotLocateNodeError -f $Name); } $Name = $node.NodeDisplayName; # Revert to oldest snapshot prior to VM removal to speed things up Get-VMSnapshot -VMName $Name -ErrorAction SilentlyContinue | Sort-Object -Property CreationTime | Select-Object -First 1 | Restore-VMSnapshot -Confirm:$false; RemoveLabVMSnapshot -Name $Name; WriteVerbose ($localized.RemovingNodeConfiguration -f 'VM', $Name); $removeLabVirtualMachineParams = @{ Name = $Name; SwitchName = $node.SwitchName; Media = $node.Media; StartupMemory = $node.StartupMemory; MinimumMemory = $node.MinimumMemory; MaximumMemory = $node.MaximumMemory; MACAddress = $node.MACAddress; ProcessorCount = $node.ProcessorCount; } RemoveLabVirtualMachine @removeLabVirtualMachineParams; WriteVerbose ($localized.RemovingNodeConfiguration -f 'VHDX', "$Name.vhdx"); RemoveLabVMDisk -Name $Name -Media $node.Media -ErrorAction Stop; if ($RemoveSwitch) { WriteVerbose ($localized.RemovingNodeConfiguration -f 'Virtual Switch', $node.SwitchName); RemoveLabSwitch -Name $node.SwitchName -ConfigurationData $ConfigurationData; } } #end process } #end function RemoveLabVM function Reset-LabVM { <# .SYNOPSIS Recreates a lab virtual machine. .DESCRIPTION The Reset-LabVM cmdlet deletes and recreates a lab virtual machine, reapplying the MOF. To revert a single VM to a previous state, use the Restore-VMSnapshot cmdlet. To revert an entire lab environment, use the Restore-Lab cmdlet. #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'PSCredential')] param ( ## Specifies the lab virtual machine/node name. [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, ## Local administrator password of the virtual machine. The username is NOT used. [Parameter(ParameterSetName = 'PSCredential', ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential = (& $credentialCheckScriptBlock), ## Local administrator password of the virtual machine. [Parameter(Mandatory, ParameterSetName = 'Password', ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.Security.SecureString] $Password, ## Directory path containing the virtual machines' .mof file(s). [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Path = (GetLabHostDSCConfigurationPath), ## Skip creation of the initial baseline snapshot. [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $NoSnapshot ) begin { ## If we have only a secure string, create a PSCredential if ($PSCmdlet.ParameterSetName -eq 'Password') { $Credential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList 'LocalAdministrator', $Password; } if (-not $Credential) { throw ($localized.CannotProcessCommandError -f 'Credential'); } elseif ($Credential.Password.Length -eq 0) { throw ($localized.CannotBindArgumentError -f 'Password'); } } process { $currentNodeCount = 0; foreach ($vmName in $Name) { $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'Reset-LabVM', $vmName; $verboseProcessMessage = GetFormattedMessage -Message ($localized.ResettingVM -f $vmName); if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { $currentNodeCount++; [System.Int32] $percentComplete = (($currentNodeCount / $Name.Count) * 100) - 1; $activity = $localized.ConfiguringNode -f $vmName; Write-Progress -Id 42 -Activity $activity -PercentComplete $percentComplete; RemoveLabVM -Name $vmName -ConfigurationData $ConfigurationData; NewLabVM -Name $vmName -ConfigurationData $ConfigurationData -Path $Path -NoSnapshot:$NoSnapshot -Credential $Credential; } #end if should process } #end foreach VMd if (-not [System.String]::IsNullOrEmpty($activity)) { Write-Progress -Id 42 -Activity $activity -PercentComplete $percentComplete; } } #end process } #end function Reset-LabVM function New-LabVM { <# .SYNOPSIS Creates a simple bare-metal virtual machine. .DESCRIPTION The New-LabVM cmdlet creates a bare virtual machine using the specified media. No bootstrap or DSC configuration is applied. NOTE: The mandatory -MediaId parameter is dynamic and is not displayed in the help syntax output. If optional values are not specified, the virtual machine default settings are applied. To list the current default settings run the `Get-LabVMDefault` command. NOTE: If a specified virtual switch cannot be found, an Internal virtual switch will automatically be created. To use any other virtual switch configuration, ensure the virtual switch is created in advance. .LINK Register-LabMedia Unregister-LabMedia Get-LabVMDefault Set-LabVMDefault #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'PSCredential')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams','')] param ( ## Specifies the virtual machine name. [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, ## Default virtual machine startup memory (bytes). [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $StartupMemory, ## Default virtual machine miniumum dynamic memory allocation (bytes). [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $MinimumMemory, ## Default virtual machine maximum dynamic memory allocation (bytes). [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $MaximumMemory, ## Default virtual machine processor count. [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(1, 4)] [System.Int32] $ProcessorCount, # Input Locale [Parameter(ValueFromPipelineByPropertyName)] [ValidatePattern('^([a-z]{2,2}-[a-z]{2,2})|(\d{4,4}:\d{8,8})$')] [System.String] $InputLocale, # System Locale [Parameter(ValueFromPipelineByPropertyName)] [ValidatePattern('^[a-z]{2,2}-[a-z]{2,2}$')] [System.String] $SystemLocale, # User Locale [Parameter(ValueFromPipelineByPropertyName)] [ValidatePattern('^[a-z]{2,2}-[a-z]{2,2}$')] [System.String] $UserLocale, # UI Language [Parameter(ValueFromPipelineByPropertyName)] [ValidatePattern('^[a-z]{2,2}-[a-z]{2,2}$')] [System.String] $UILanguage, # Timezone [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Timezone, # Registered Owner [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $RegisteredOwner, # Registered Organization [Parameter(ValueFromPipelineByPropertyName)] [Alias('RegisteredOrganisation')] [ValidateNotNullOrEmpty()] [System.String] $RegisteredOrganization, ## Local administrator password of the VM. The username is NOT used. [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'PSCredential')] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential = (& $credentialCheckScriptBlock), ## Local administrator password of the VM. [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Password')] [ValidateNotNullOrEmpty()] [System.Security.SecureString] $Password, ## Virtual machine switch name(s). [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String[]] $SwitchName, ## Virtual machine MAC address(es). [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String[]] $MACAddress, ## Enable Secure boot status [Parameter(ValueFromPipelineByPropertyName)] [System.Boolean] $SecureBoot, ## Enable Guest Integration Services [Parameter(ValueFromPipelineByPropertyName)] [System.Boolean] $GuestIntegrationServices, ## Custom data [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Collections.Hashtable] $CustomData, ## Skip creating baseline snapshots [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $NoSnapshot ) DynamicParam { ## Adds a dynamic -MediaId parameter that returns the available media Ids $parameterAttribute = New-Object -TypeName 'System.Management.Automation.ParameterAttribute'; $parameterAttribute.ParameterSetName = '__AllParameterSets'; $parameterAttribute.Mandatory = $true; $attributeCollection = New-Object -TypeName 'System.Collections.ObjectModel.Collection[System.Attribute]'; $attributeCollection.Add($parameterAttribute); $mediaIds = (Get-LabMedia).Id; $validateSetAttribute = New-Object -TypeName 'System.Management.Automation.ValidateSetAttribute' -ArgumentList $mediaIds; $attributeCollection.Add($validateSetAttribute); $runtimeParameter = New-Object -TypeName 'System.Management.Automation.RuntimeDefinedParameter' -ArgumentList @('MediaId', [System.String], $attributeCollection); $runtimeParameterDictionary = New-Object -TypeName 'System.Management.Automation.RuntimeDefinedParameterDictionary'; $runtimeParameterDictionary.Add('MediaId', $runtimeParameter); return $runtimeParameterDictionary; } begin { ## If we have only a secure string, create a PSCredential if ($PSCmdlet.ParameterSetName -eq 'Password') { $Credential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList 'LocalAdministrator', $Password; } if (-not $Credential) { throw ($localized.CannotProcessCommandError -f 'Credential'); } elseif ($Credential.Password.Length -eq 0) { throw ($localized.CannotBindArgumentError -f 'Password'); } } #end begin process { ## Skeleton configuration node $configurationNode = @{ } if ($CustomData) { ## Add all -CustomData keys/values to the skeleton configuration foreach ($key in $CustomData.Keys) { $configurationNode[$key] = $CustomData.$key; } } ## Explicitly defined parameters override any -CustomData $parameterNames = @('StartupMemory','MinimumMemory','MaximumMemory','SwitchName','Timezone','UILanguage','MACAddress', 'ProcessorCount','InputLocale','SystemLocale','UserLocale','RegisteredOwner','RegisteredOrganization','SecureBoot') foreach ($key in $parameterNames) { if ($PSBoundParameters.ContainsKey($key)) { $configurationNode[$key] = $PSBoundParameters.$key; } } ## Ensure the specified MediaId is applied after any CustomData media entry! $configurationNode['Media'] = $PSBoundParameters.MediaId; $currentNodeCount = 0; foreach ($vmName in $Name) { ## Update the node name before creating the VM $configurationNode['NodeName'] = $vmName; $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'New-LabVM', $vmName; $verboseProcessMessage = GetFormattedMessage -Message ($localized.CreatingQuickVM -f $vmName, $PSBoundParameters.MediaId); if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { $currentNodeCount++; [System.Int32] $percentComplete = (($currentNodeCount / $Name.Count) * 100) - 1; $activity = $localized.ConfiguringNode -f $vmName; Write-Progress -Id 42 -Activity $activity -PercentComplete $percentComplete; $configurationData = @{ AllNodes = @( $configurationNode ) }; NewLabVM -Name $vmName -ConfigurationData $configurationData -Credential $Credential -NoSnapshot:$NoSnapshot -IsQuickVM; } } #end foreach name if (-not [System.String]::IsNullOrEmpty($activity)) { Write-Progress -Id 42 -Activity $activity -PercentComplete $percentComplete; } } #end process } #end function New-LabVM function Remove-LabVM { <# .SYNOPSIS Removes a bare-metal virtual machine and differencing VHD(X). .DESCRIPTION The Remove-LabVM cmdlet removes a virtual machine and it's VHD(X) file. #> [CmdletBinding(SupportsShouldProcess)] param ( ## Virtual machine name [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(ValueFromPipelineByPropertyName)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData ) process { $currentNodeCount = 0; foreach ($vmName in $Name) { $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'Remove-LabVM', $vmName; $verboseProcessMessage = GetFormattedMessage -Message ($localized.RemovingVM -f $vmName); if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { $currentNodeCount++; [System.Int32] $percentComplete = (($currentNodeCount / $Name.Count) * 100) - 1; $activity = $localized.ConfiguringNode -f $vmName; Write-Progress -Id 42 -Activity $activity -PercentComplete $percentComplete; ## Create a skeleton config data if one wasn't supplied if (-not $PSBoundParameters.ContainsKey('ConfigurationData')) { $configurationData = @{ AllNodes = @( @{ NodeName = $vmName; } ) }; } RemoveLabVM -Name $vmName -ConfigurationData $configurationData; } #end if should process } #end foreach VM if (-not [System.String]::IsNullOrEmpty($activity)) { Write-Progress -Id 42 -Activity $activity -PercentComplete $percentComplete; } } #end process } #end function Remove-LabVM # SIG # Begin signature block # MIIXtwYJKoZIhvcNAQcCoIIXqDCCF6QCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUX+YxsJqGLtTJkng8byCzqJL/ # JsegghLqMIID7jCCA1egAwIBAgIQfpPr+3zGTlnqS5p31Ab8OzANBgkqhkiG9w0B # AQUFADCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIG # A1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhh # d3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcg # Q0EwHhcNMTIxMjIxMDAwMDAwWhcNMjAxMjMwMjM1OTU5WjBeMQswCQYDVQQGEwJV # UzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xMDAuBgNVBAMTJ1N5bWFu # dGVjIFRpbWUgU3RhbXBpbmcgU2VydmljZXMgQ0EgLSBHMjCCASIwDQYJKoZIhvcN # AQEBBQADggEPADCCAQoCggEBALGss0lUS5ccEgrYJXmRIlcqb9y4JsRDc2vCvy5Q # WvsUwnaOQwElQ7Sh4kX06Ld7w3TMIte0lAAC903tv7S3RCRrzV9FO9FEzkMScxeC # i2m0K8uZHqxyGyZNcR+xMd37UWECU6aq9UksBXhFpS+JzueZ5/6M4lc/PcaS3Er4 # ezPkeQr78HWIQZz/xQNRmarXbJ+TaYdlKYOFwmAUxMjJOxTawIHwHw103pIiq8r3 # +3R8J+b3Sht/p8OeLa6K6qbmqicWfWH3mHERvOJQoUvlXfrlDqcsn6plINPYlujI # fKVOSET/GeJEB5IL12iEgF1qeGRFzWBGflTBE3zFefHJwXECAwEAAaOB+jCB9zAd # BgNVHQ4EFgQUX5r1blzMzHSa1N197z/b7EyALt0wMgYIKwYBBQUHAQEEJjAkMCIG # CCsGAQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMBIGA1UdEwEB/wQIMAYB # Af8CAQAwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC50aGF3dGUuY29tL1Ro # YXd0ZVRpbWVzdGFtcGluZ0NBLmNybDATBgNVHSUEDDAKBggrBgEFBQcDCDAOBgNV # HQ8BAf8EBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFtcC0y # MDQ4LTEwDQYJKoZIhvcNAQEFBQADgYEAAwmbj3nvf1kwqu9otfrjCR27T4IGXTdf # plKfFo3qHJIJRG71betYfDDo+WmNI3MLEm9Hqa45EfgqsZuwGsOO61mWAK3ODE2y # 0DGmCFwqevzieh1XTKhlGOl5QGIllm7HxzdqgyEIjkHq3dlXPx13SYcqFgZepjhq # IhKjURmDfrYwggSjMIIDi6ADAgECAhAOz/Q4yP6/NW4E2GqYGxpQMA0GCSqGSIb3 # DQEBBQUAMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3Jh # dGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2aWNlcyBD # QSAtIEcyMB4XDTEyMTAxODAwMDAwMFoXDTIwMTIyOTIzNTk1OVowYjELMAkGA1UE # BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMTQwMgYDVQQDEytT # eW1hbnRlYyBUaW1lIFN0YW1waW5nIFNlcnZpY2VzIFNpZ25lciAtIEc0MIIBIjAN # BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomMLOUS4uyOnREm7Dv+h8GEKU5Ow # mNutLA9KxW7/hjxTVQ8VzgQ/K/2plpbZvmF5C1vJTIZ25eBDSyKV7sIrQ8Gf2Gi0 # jkBP7oU4uRHFI/JkWPAVMm9OV6GuiKQC1yoezUvh3WPVF4kyW7BemVqonShQDhfu # ltthO0VRHc8SVguSR/yrrvZmPUescHLnkudfzRC5xINklBm9JYDh6NIipdC6Anqh # d5NbZcPuF3S8QYYq3AhMjJKMkS2ed0QfaNaodHfbDlsyi1aLM73ZY8hJnTrFxeoz # C9Lxoxv0i77Zs1eLO94Ep3oisiSuLsdwxb5OgyYI+wu9qU+ZCOEQKHKqzQIDAQAB # o4IBVzCCAVMwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAO # BgNVHQ8BAf8EBAMCB4AwcwYIKwYBBQUHAQEEZzBlMCoGCCsGAQUFBzABhh5odHRw # Oi8vdHMtb2NzcC53cy5zeW1hbnRlYy5jb20wNwYIKwYBBQUHMAKGK2h0dHA6Ly90 # cy1haWEud3Muc3ltYW50ZWMuY29tL3Rzcy1jYS1nMi5jZXIwPAYDVR0fBDUwMzAx # oC+gLYYraHR0cDovL3RzLWNybC53cy5zeW1hbnRlYy5jb20vdHNzLWNhLWcyLmNy # bDAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVGltZVN0YW1wLTIwNDgtMjAdBgNV # HQ4EFgQURsZpow5KFB7VTNpSYxc/Xja8DeYwHwYDVR0jBBgwFoAUX5r1blzMzHSa # 1N197z/b7EyALt0wDQYJKoZIhvcNAQEFBQADggEBAHg7tJEqAEzwj2IwN3ijhCcH # bxiy3iXcoNSUA6qGTiWfmkADHN3O43nLIWgG2rYytG2/9CwmYzPkSWRtDebDZw73 # BaQ1bHyJFsbpst+y6d0gxnEPzZV03LZc3r03H0N45ni1zSgEIKOq8UvEiCmRDoDR # EfzdXHZuT14ORUZBbg2w6jiasTraCXEQ/Bx5tIB7rGn0/Zy2DBYr8X9bCT2bW+IW # yhOBbQAuOA2oKY8s4bL0WqkBrxWcLC9JG9siu8P+eJRRw4axgohd8D20UaF5Mysu # e7ncIAkTcetqGVvP6KUwVyyJST+5z3/Jvz4iaGNTmr1pdKzFHTx/kuDDvBzYBHUw # ggUZMIIEAaADAgECAhADViTO4HBjoJNSwH9//cwJMA0GCSqGSIb3DQEBCwUAMHIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJ # RCBDb2RlIFNpZ25pbmcgQ0EwHhcNMTUwNTE5MDAwMDAwWhcNMTcwODIzMTIwMDAw # WjBgMQswCQYDVQQGEwJHQjEPMA0GA1UEBxMGT3hmb3JkMR8wHQYDVQQKExZWaXJ0 # dWFsIEVuZ2luZSBMaW1pdGVkMR8wHQYDVQQDExZWaXJ0dWFsIEVuZ2luZSBMaW1p # dGVkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqLQmabdimcQtYPTQ # 9RSjv3ThEmFTRJt/MzseYYtZpBTcR6BnSfj8RfkC4aGZvspFgH0cGP/SNJh1w67b # iX9oT5NFL9sUJHUsVdyPBA1LhpWcF09PP28mGGKO3oQHI4hTLD8etiIlF9qFantd # 1Pmo0jdqT4uErSmx0m4kYGUUTa5ZPAK0UZSuAiNX6iNIL+rj/BPbI3nuPJzzx438 # oHYkZGRtsx11+pLA6hIKyUzRuIDoI7JQ0nZ0MkCziVyc6xGfS54JVLaVCEteTKPz # Gc4yyvCqp6Tfe9gs8UuxJiEMdH5fvllTU4aoXbm+W8tonkE7i/19rv8S1A2VPiVV # xNLbpwIDAQABo4IBuzCCAbcwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1 # DlgwHQYDVR0OBBYEFP2RNOWYipdNCSRVb5jIcyRp9tUDMA4GA1UdDwEB/wQEAwIH # gDATBgNVHSUEDDAKBggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYv # aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmww # QgYDVR0gBDswOTA3BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93 # d3cuZGlnaWNlcnQuY29tL0NQUzCBhAYIKwYBBQUHAQEEeDB2MCQGCCsGAQUFBzAB # hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTgYIKwYBBQUHMAKGQmh0dHA6Ly9j # YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURDb2RlU2ln # bmluZ0NBLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCclXHR # DhDyJr81eiD0x+AL04ryDwdKT+PooKYgOxc7EhRn59ogxNO7jApQPSVo0I11Zfm6 # zQ6K6RPWhxDenflf2vMx7a0tIZlpHhq2F8praAMykK7THA9F3AUxIb/lWHGZCock # yD/GQvJek3LSC5NjkwQbnubWYF/XZTDzX/mJGU2DcG1OGameffR1V3xODHcUE/K3 # PWy1bzixwbQCQA96GKNCWow4/mEW31cupHHSo+XVxmjTAoC93yllE9f4Kdv6F29H # bRk0Go8Yn8WjWeLE/htxW/8ruIj0KnWkG+YwmZD+nTegYU6RvAV9HbJJYUEIfhVy # 3DeK5OlY9ima2sdtMIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1b5VQCDANBgkq # hkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j # MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBB # c3N1cmVkIElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgxMDIyMTIwMDAw # WjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL # ExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3Vy # ZWQgSUQgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB # CgKCAQEA+NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLXcep2nQUut4/6 # kkPApfmJ1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSRI5aQd4L5oYQj # ZhJUM1B0sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXiTWAYvqrEsq5w # MWYzcT6scKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5Ng2Q7+S1TqSp # 6moKq4TzrGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8vYWxYoNzQYIH # 5DiLanMg0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYDVR0TAQH/BAgw # BgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMweQYI # KwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6 # Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmww # OqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ # RFJvb3RDQS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUH # AgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZIAYb9bAMwHQYD # VR0OBBYEFFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaAFEXroq/0ksuC # MS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPzItEVyCx8JSl2 # qB1dHC06GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRupY5a4l4kgU4Q # pO4/cY5jDhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKNJK4kxscnKqEp # KBo6cSgCPC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7qPjFEmifz0DLQESlE/Dm # ZAwlCEIysjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN3fYBIM6ZMWM9 # CBoYs4GbT8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKyZqHnGKSaZFHv # MYIENzCCBDMCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0 # IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNl # cnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBDQQIQA1YkzuBwY6CTUsB/ # f/3MCTAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkq # hkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGC # NwIBFTAjBgkqhkiG9w0BCQQxFgQUljHzbvpZeS0wweFX1tJuasll1cUwDQYJKoZI # hvcNAQEBBQAEggEAYIeD2yioHKLAEPy9mWfLLqNads540qFtWLN81Fqvi1LuYtjO # J5fIPm6b5mXBad73m/PKZ6mzEGmDU4J+wZ0+BsQM/n7ImUvk6987iHv2vRby5w3R # fHfVCx+tvq//TrJEM++JHVJmQAWlpIAkdNjhP4aukbniOLTW5ADx4urx9qsGDa5X # f71lMACLKNC4sHQ2xzEEBCtBK9g2coFJ0IglWiwPJY7dXeWzbnZLVF/BalApIUB1 # AyZqlFQbkgMDkWz+ouFZhwM7PS3QDrgmdU83/LWML8avNq0/MEulXhXsYaCaLerU # k917glYyudQVf1ez+dS+uAq3zVDcpDaAnZqm6aGCAgswggIHBgkqhkiG9w0BCQYx # ggH4MIIB9AIBATByMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBD # b3Jwb3JhdGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2 # aWNlcyBDQSAtIEcyAhAOz/Q4yP6/NW4E2GqYGxpQMAkGBSsOAwIaBQCgXTAYBgkq # hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MTMxMzMz # MjNaMCMGCSqGSIb3DQEJBDEWBBSrPnxJ7XwG2KR7I6ai+yppXvG48zANBgkqhkiG # 9w0BAQEFAASCAQANg9c+bJiDVq6yfOZxmiEZRLgKuKnNsDSJrrU3oO0sCyPZ3lg/ # SNgDtnDJjfCiFaRxEd07KT4Ztbt5x8sAI5/JCxd32/tsu0AkttEMhSzP39pPq1Mf # JB4FNNCkPos2f5IP48BJ0XlIkexh8IBDc7b/H1OVTk5D+mghLb2s7nl5dlWcmunZ # a7ZE3n3VFBldKb3GAMytZO3mdKqSde5Pl0PVb4nIPtUHkPxB+8cZfx6tSSz/khfd # GXUwvzR4Oi413/2zq1NBJFg33ojn/dr+t4bnt6aebDddNENpmMMm5KoihM2r1AEa # /o3r/JiMayAN+SgUGgyc+5zYKMPSpK3EsJVb # SIG # End signature block |