From 6042e0fb1030d8f257f9b9e33afcc013415b1950 Mon Sep 17 00:00:00 2001 From: Iain Brighton Date: Fri, 27 Nov 2015 15:14:04 +0000 Subject: [PATCH 01/23] Initial quick VM implementation --- Resources.psd1 | 1 + Src/LabVM.ps1 | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/Resources.psd1 b/Resources.psd1 index b7fb13f7..462a5378 100644 --- a/Resources.psd1 +++ b/Resources.psd1 @@ -99,6 +99,7 @@ ConvertFrom-StringData @' LocatingWimImageName = Locating WIM image '{0}' name. LocatingWimImageIndex = Locating WIM image '{0}' index. MediaFileCachingDisabled = Caching of file-based media is disabled. Skipping media '{0}' download. + CreatingQuickVM = Creating quick VM '{0}' using media '{1}'. NoCertificateFoundWarning = No '{0}' certificate was found. CannotLocateLcmFileWarning = Cannot locate LCM configuration file '{0}'. No DSC Local Configuration Manager configuration will be applied. diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index f658c2ab..10621dbf 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -371,3 +371,81 @@ function Reset-LabVM { } } #end process } #end function Reset-LabVM + +function New-LabQuickVM { +<# + .SYNOPSIS + Creates a quick virtual machine. + .DESCRIPTION + The New-LabQuickVM creates a bare virtual machine using the specified media. No DSC configuration is applied, although DSC resources are still copied in to the VM's VHD(X). + + NOTE: The -Id parameter is dynamic and is not displayed in the help output. + + The resulting virtual machine is created using the default values. You can find the default values with the 'Get-LabVMDefaults' cmdlet. + .LINK + Register-LabMedia + Unregister-LabMedia + Get-LabVMDefaults + Set-LabVMDefaults +#> + [CmdletBinding(DefaultParameterSetName = 'PSCredential')] + param ( + ## Lab VM/Node name + [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, + + ## Local administrator password of the VM. The username is NOT used. + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'PSCredential')] [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCredential] $Credential = (& $credentialCheckScriptBlock), + + ## Local administrator password of the VM. + [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Password')] [ValidateNotNullOrEmpty()] + [System.Security.SecureString] $Password, + + ## Virtual machine switch name. NOTE: the virtual switch must already exist - it won't be created! + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $SwitchName, + + ## Skip creating baseline snapshots + [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $NoSnapshot + ) + DynamicParam { + ## Adds a dynamic -Id parameter that returns the registered media Ids + $parameterAttribute = New-Object -TypeName 'System.Management.Automation.ParameterAttribute'; + $parameterAttribute.ParameterSetName = '__AllParameterSets'; + $parameterAttribute.Mandatory = $true; + $aliasAttribute = New-Object -TypeName 'System.Management.Automation.AliasAttribute' -ArgumentList @('MediaId'); + $attributeCollection = New-Object -TypeName 'System.Collections.ObjectModel.Collection[System.Attribute]'; + $attributeCollection.Add($parameterAttribute); + $attributeCollection.Add($aliasAttribute); + $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 @('Id', [System.String], $attributeCollection); + $runtimeParameterDictionary = New-Object -TypeName 'System.Management.Automation.RuntimeDefinedParameterDictionary'; + $runtimeParameterDictionary.Add('Id', $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'); } + } + process { + foreach ($vmName in $Name) { + ## Create a skelton config data + $skeletonConfigurationData = @{ + AllNodes = @( + @{ NodeName = $vmName; VirtualEngineLab_Media = $Id; } + ) + }; + if ($SwitchName) { + $switchKeyName = '{0}_SwitchName' -f $labDefaults.ModuleName; + [ref] $null = $skeletonConfigurationData.AllNodes[0].Add($switchKeyName, $SwitchName); + } + WriteVerbose ($localized.CreatingQuickVM -f $vmName, $MediaId); + NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot + } + } #end process +} #end function New-LabVM From e9c7351d4e6caa7fb2c5205053faed4d199e8c93 Mon Sep 17 00:00:00 2001 From: Iain Brighton Date: Wed, 2 Dec 2015 22:35:43 +0000 Subject: [PATCH 02/23] Removes hard-coded module name --- Src/LabVM.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index 10621dbf..1aa64723 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -437,7 +437,7 @@ function New-LabQuickVM { ## Create a skelton config data $skeletonConfigurationData = @{ AllNodes = @( - @{ NodeName = $vmName; VirtualEngineLab_Media = $Id; } + @{ NodeName = $vmName; "$($labDefaults.ModuleName)_Media" = $PSBoundParameters.Id; } ) }; if ($SwitchName) { From 5d02fef6eb25614670dde4b14daf44c70ac02adb Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Wed, 9 Dec 2015 20:19:27 +0000 Subject: [PATCH 03/23] Adds LabVMDefaults parameters to New-LabQuickVM Adds localisation in NewLabVM --- Resources.psd1 | 2 ++ Src/LabVM.ps1 | 61 +++++++++++++++++++++++++++++++++++++++---- Src/LabVMDefaults.ps1 | 39 ++++++++++++++++++--------- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/Resources.psd1 b/Resources.psd1 index 16cba155..7675f3a2 100644 --- a/Resources.psd1 +++ b/Resources.psd1 @@ -138,4 +138,6 @@ ConvertFrom-StringData -StringData @' MediaAlreadyRegisteredError = Media Id '{0}' is already registered. Use {1} to override the existing media entry. CannotProcessCommandError = Cannot process command because of one or more missing mandatory parameters: {0}. CannotBindArgumentError = Cannot bind argument to parameter '{0}' because it is an empty string. + StartMemLessThanMinMemError = Startup memory '{0}' cannot be less than minimum memory '{1}'. + StartMemGreaterThanMaxMemError = Startup memory '{0}' cannot be greater than maximum memory '{1}'. '@ \ No newline at end of file diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index e17194aa..2a6c3ac5 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -391,7 +391,52 @@ function New-LabQuickVM { [CmdletBinding(DefaultParameterSetName = 'PSCredential')] param ( ## Lab VM/Node name - [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String[]] $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()] @@ -440,11 +485,17 @@ function New-LabQuickVM { @{ NodeName = $vmName; "$($labDefaults.ModuleName)_Media" = $PSBoundParameters.Id; } ) }; - if ($SwitchName) { - $switchKeyName = '{0}_SwitchName' -f $labDefaults.ModuleName; - [ref] $null = $skeletonConfigurationData.AllNodes[0].Add($switchKeyName, $SwitchName); + + $parameterNames = @('StartupMemory','MinimumMemory','MaximumMemory','SwitchName','Timezone','UILanguage', + 'ProcessorCount','InputLocale','SystemLocale','UserLocale','RegisteredOwner','RegisteredOrganization') + foreach ($key in $parameterNames) { + if ($PSBoundParameters.ContainsKey($key)) { + $configurationName = '{0}_{1}' -f $labDefaults.ModuleName, $key; + [ref] $null = $skeletonConfigurationData.AllNodes[0].Add($configurationname, $PSBoundParameters.$key); + } } - WriteVerbose ($localized.CreatingQuickVM -f $vmName, $MediaId); + + WriteVerbose -Message ($localized.CreatingQuickVM -f $vmName, $MediaId); NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot } } #end process diff --git a/Src/LabVMDefaults.ps1 b/Src/LabVMDefaults.ps1 index e9fb50ce..9784b1e9 100644 --- a/Src/LabVMDefaults.ps1 +++ b/Src/LabVMDefaults.ps1 @@ -90,13 +90,7 @@ function Set-LabVMDefaults { $vmDefaults.SwitchName = $SwitchName; } if ($PSBoundParameters.ContainsKey('Timezone')) { - try { - $TZ = [TimeZoneInfo]::FindSystemTimeZoneById($TimeZone) - $vmDefaults.Timezone = $TZ.StandardName; - } - catch [System.TimeZoneNotFoundException] { - throw $_; - } + $vmDefaults.Timezone = ValidateTimeZone -TimeZone $Timezone; } if ($PSBoundParameters.ContainsKey('UILanguage')) { $vmDefaults.UILanguage = $UILanguage; @@ -120,7 +114,7 @@ function Set-LabVMDefaults { if (-not [System.String]::IsNullOrWhitespace($ClientCertificatePath)) { $ClientCertificatePath = [System.Environment]::ExpandEnvironmentVariables($ClientCertificatePath); if (-not (Test-Path -Path $ClientCertificatePath -Type Leaf)) { - throw ('Cannot resolve certificate path ''{0}''.' -f $ClientCertificatePath); + throw ($localized.CannotFindCertificateError -f 'Client', $ClientCertificatePath); } } $vmDefaults.ClientCertificatePath = $ClientCertificatePath; @@ -129,7 +123,7 @@ function Set-LabVMDefaults { if (-not [System.String]::IsNullOrWhitespace($RootCertificatePath)) { $RootCertificatePath = [System.Environment]::ExpandEnvironmentVariables($RootCertificatePath); if (-not (Test-Path -Path $RootCertificatePath -Type Leaf)) { - throw ('Cannot resolve certificate path ''{0}''.' -f $RootCertificatePath); + throw ($localized.CannotFindCertificateError -f 'Root', $RootCertificatePath); } } $vmDefaults.RootCertificatePath = $RootCertificatePath; @@ -139,10 +133,10 @@ function Set-LabVMDefaults { } if ($vmDefaults.StartupMemory -lt $vmDefaults.MinimumMemory) { - throw ('Startup memory ''{0}'' cannot be less than minimum memory ''{1}''.' -f $vmDefaults.StartupMemory, $vmDefaults.MinimumMemory); + throw ($localized.StartMemLessThanMinMemError -f $vmDefaults.StartupMemory, $vmDefaults.MinimumMemory); } elseif ($vmDefaults.StartupMemory -gt $vmDefaults.MaximumMemory) { - throw ('Startup memory ''{0}'' cannot be greater than maximum memory ''{1}''.' -f $vmDefaults.StartupMemory, $vmDefaults.MaximumMemory); + throw ($localized.StartMemGreaterThanMaxMemError -f $vmDefaults.StartupMemory, $vmDefaults.MaximumMemory); } SetConfigurationData -Configuration VM -InputObject $vmDefaults; @@ -151,4 +145,25 @@ function Set-LabVMDefaults { return $vmDefaults; } } #end function Set-LabVMDefaults - \ No newline at end of file + +function ValidateTimeZone { +<# + .SYNOPSIS + Validates a timezone string. +#> + [CmdletBinding()] + [OutputType([System.String])] + param ( + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $TimeZone + ) + process { + try { + $TZ = [TimeZoneInfo]::FindSystemTimeZoneById($TimeZone) + return $TZ.StandardName; + } + catch [System.TimeZoneNotFoundException] { + throw $_; + } + } #end process +} #end function ValidateTimeZone From 9bf328a87d6d89344851b765d751f2e6783d2f6f Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Sun, 13 Dec 2015 22:36:49 +0000 Subject: [PATCH 04/23] Renames Set/Get-LabVMDefaults to Set/Get-LabVMDefault --- Src/LabVM.ps1 | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index 1011fbb6..400a79bf 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -383,13 +383,11 @@ function New-LabQuickVM { The New-LabQuickVM creates a bare virtual machine using the specified media. No DSC configuration is applied, although DSC resources are still copied in to the VM's VHD(X). NOTE: The -Id parameter is dynamic and is not displayed in the help output. - - The resulting virtual machine is created using the default values. You can find the default values with the 'Get-LabVMDefaults' cmdlet. .LINK Register-LabMedia Unregister-LabMedia - Get-LabVMDefaults - Set-LabVMDefaults + Get-LabVMDefault + Set-LabVMDefault #> [CmdletBinding(DefaultParameterSetName = 'PSCredential')] param ( @@ -499,7 +497,7 @@ function New-LabQuickVM { } WriteVerbose -Message ($localized.CreatingQuickVM -f $vmName, $MediaId); - NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot + NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot; } } #end process } #end function New-LabVM From fa25b2cd4a3563ae2a777c7ab07701f9a21933d9 Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Sun, 13 Dec 2015 22:41:22 +0000 Subject: [PATCH 05/23] Renames New-LabQuickVM to New-LabVM --- Src/LabVM.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index 400a79bf..b02ac2ee 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -375,12 +375,12 @@ function Reset-LabVM { } #end process } #end function Reset-LabVM -function New-LabQuickVM { +function New-LabVM { <# .SYNOPSIS - Creates a quick virtual machine. + Creates a simple bare-metal virtual machine. .DESCRIPTION - The New-LabQuickVM creates a bare virtual machine using the specified media. No DSC configuration is applied, although DSC resources are still copied in to the VM's VHD(X). + The New-LabVM cmdlet creates a bare virtual machine using the specified media. No DSC configuration is applied, although DSC resources are still copied in to the VM's VHD(X). NOTE: The -Id parameter is dynamic and is not displayed in the help output. .LINK From 46431d27542a9dbdcf0ed56eed8ad03b1b62ce14 Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Mon, 14 Dec 2015 21:58:05 +0000 Subject: [PATCH 06/23] Converts to space indentation --- Lib/ConfigurationData.ps1 | 38 ++++++------ Lib/DscModule.ps1 | 54 ++++++++--------- Lib/Resource.ps1 | 18 +++--- Lib/UnattendXml.ps1 | 16 ++--- Src/LabHostConfiguration.ps1 | 6 +- Src/LabHostDefaults.ps1 | 66 ++++++++++---------- Src/LabMedia.ps1 | 14 ++--- Src/LabVM.ps1 | 8 +-- Src/LabVMDefaults.ps1 | 114 +++++++++++++++++------------------ 9 files changed, 167 insertions(+), 167 deletions(-) diff --git a/Lib/ConfigurationData.ps1 b/Lib/ConfigurationData.ps1 index ac0d5ca8..4d7a7cde 100644 --- a/Lib/ConfigurationData.ps1 +++ b/Lib/ConfigurationData.ps1 @@ -30,17 +30,17 @@ function ConvertToConfigurationData { function ResolveConfigurationDataPath { <# - .SYNOPSIS - Resolves the lab configuration data path. - .NOTES - When -IncludeDefaultPath is specified, if the configuration data file is not found, the default - module configuration path is returned. + .SYNOPSIS + Resolves the lab configuration data path. + .NOTES + When -IncludeDefaultPath is specified, if the configuration data file is not found, the default + module configuration path is returned. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Mandatory)] [ValidateSet('Host','VM','Media','CustomMedia')] [System.String] $Configuration, - [Parameter()] [System.Management.Automation.SwitchParameter] $IncludeDefaultPath + [Parameter()] [System.Management.Automation.SwitchParameter] $IncludeDefaultPath ) process { switch ($Configuration) { @@ -51,11 +51,11 @@ function ResolveConfigurationDataPath { } $configPath = Join-Path -Path $labDefaults.ConfigurationData -ChildPath $configPath; $resolvedPath = Join-Path -Path "$env:ALLUSERSPROFILE\$($labDefaults.ModuleName)" -ChildPath $configPath; - if ($IncludeDefaultPath) { - if (-not (Test-Path -Path $resolvedPath)) { - $resolvedPath = Join-Path -Path $labDefaults.ModuleRoot -ChildPath $configPath; - } - } + if ($IncludeDefaultPath) { + if (-not (Test-Path -Path $resolvedPath)) { + $resolvedPath = Join-Path -Path $labDefaults.ModuleRoot -ChildPath $configPath; + } + } Write-Debug -Message ('Resolved ''{0}'' configuration file to ''{1}''.' -f $Configuration, $resolvedPath); return $resolvedPath; } #end process @@ -63,8 +63,8 @@ function ResolveConfigurationDataPath { function GetConfigurationData { <# - .SYNOPSIS - Gets lab configuration data. + .SYNOPSIS + Gets lab configuration data. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] @@ -82,20 +82,20 @@ function GetConfigurationData { function SetConfigurationData { <# - .SYNOPSIS - Saves lab configuration data. + .SYNOPSIS + Saves lab configuration data. #> - [CmdletBinding()] + [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Mandatory)] [ValidateSet('Host','VM','Media','CustomMedia')] [System.String] $Configuration, - [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $InputObject + [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $InputObject ) process { $configurationPath = ResolveConfigurationDataPath -Configuration $Configuration; $expandedPath = [System.Environment]::ExpandEnvironmentVariables($configurationPath); - [ref] $null = NewDirectory -Path (Split-Path -Path $expandedPath -Parent); - Set-Content -Path $expandedPath -Value (ConvertTo-Json -InputObject $InputObject) -Force; + [ref] $null = NewDirectory -Path (Split-Path -Path $expandedPath -Parent); + Set-Content -Path $expandedPath -Value (ConvertTo-Json -InputObject $InputObject) -Force; } } #end function SetConfigurationData diff --git a/Lib/DscModule.ps1 b/Lib/DscModule.ps1 index 6e909975..f5183fe9 100644 --- a/Lib/DscModule.ps1 +++ b/Lib/DscModule.ps1 @@ -1,34 +1,34 @@ function ExpandDscModule { <# - .SYNOPSIS - Extracts a DSC resource .zip archive using Windows Explorer and removes -master or -dev directory suffixes. + .SYNOPSIS + Extracts a DSC resource .zip archive using Windows Explorer and removes -master or -dev directory suffixes. #> [CmdletBinding()] - param ( - [Parameter(Mandatory)] [System.String] $ModuleName, - [Parameter(Mandatory)] [System.String] $Path, - [Parameter(Mandatory)] [System.String] $DestinationPath, - [Parameter()] [System.Management.Automation.SwitchParameter] $Force - ) - process { - $targetPath = Join-Path -Path $DestinationPath -ChildPath $ModuleName; - if (-not (Test-Path -Path $targetPath) -or $Force) { - if (Test-Path -Path $targetPath) { - WriteVerbose ($localized.RemovingDirectory -f $targetPath); - Remove-Item -Path $targetPath -Recurse -Force -ErrorAction Stop; - } - WriteVerbose ($localized.ExpandingArchive -f $Path, $DestinationPath); - $shellApplication = New-Object -ComObject Shell.Application; - $archiveItems = $shellApplication.Namespace($Path).Items(); - $shellApplication.NameSpace($DestinationPath).CopyHere($archiveItems); - ## Rename any -master branch folder where no GitHub release available - Get-ChildItem -Path $DestinationPath -Directory | Where-Object { $_.Name -like '*-dev' -or $_.Name -like '*-master' } | ForEach-Object { - $destinationFilename = $_.Name -replace '-dev','' -replace '-master',''; - WriteVerbose ($localized.RenamingPath -f $_.FullName, $destinationFilename); - Rename-Item -Path $_.FullName -NewName $destinationFilename -ErrorAction Stop; - } - } - } #end process + param ( + [Parameter(Mandatory)] [System.String] $ModuleName, + [Parameter(Mandatory)] [System.String] $Path, + [Parameter(Mandatory)] [System.String] $DestinationPath, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force + ) + process { + $targetPath = Join-Path -Path $DestinationPath -ChildPath $ModuleName; + if (-not (Test-Path -Path $targetPath) -or $Force) { + if (Test-Path -Path $targetPath) { + WriteVerbose ($localized.RemovingDirectory -f $targetPath); + Remove-Item -Path $targetPath -Recurse -Force -ErrorAction Stop; + } + WriteVerbose ($localized.ExpandingArchive -f $Path, $DestinationPath); + $shellApplication = New-Object -ComObject Shell.Application; + $archiveItems = $shellApplication.Namespace($Path).Items(); + $shellApplication.NameSpace($DestinationPath).CopyHere($archiveItems); + ## Rename any -master branch folder where no GitHub release available + Get-ChildItem -Path $DestinationPath -Directory | Where-Object { $_.Name -like '*-dev' -or $_.Name -like '*-master' } | ForEach-Object { + $destinationFilename = $_.Name -replace '-dev','' -replace '-master',''; + WriteVerbose ($localized.RenamingPath -f $_.FullName, $destinationFilename); + Rename-Item -Path $_.FullName -NewName $destinationFilename -ErrorAction Stop; + } + } + } #end process } #end function ExpandDscModule function TestDscModule { diff --git a/Lib/Resource.ps1 b/Lib/Resource.ps1 index 8957c14d..ecc0cdf7 100644 --- a/Lib/Resource.ps1 +++ b/Lib/Resource.ps1 @@ -85,16 +85,16 @@ function GetResourceDownload { [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, + [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter()] [AllowNull()] [System.String] $Checksum, [Parameter()] [System.UInt32] $BufferSize = 64KB ##TODO: Support Headers and UserAgent ) process { $checksumPath = '{0}.checksum' -f $DestinationPath; - if (-not (Test-Path -Path $DestinationPath)) { - WriteVerbose ($localized.MissingResourceFile -f $DestinationPath); - } + if (-not (Test-Path -Path $DestinationPath)) { + WriteVerbose ($localized.MissingResourceFile -f $DestinationPath); + } elseif (-not (Test-Path -Path $checksumPath)) { [ref] $null = SetResourceChecksum -Path $DestinationPath; } @@ -126,7 +126,7 @@ function TestResourceDownload { [OutputType([System.Boolean])] param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, + [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter()] [AllowNull()] [System.String] $Checksum, [Parameter()] [System.UInt32] $BufferSize = 64KB ##TODO: Support Headers and UserAgent @@ -156,7 +156,7 @@ function SetResourceDownload { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, + [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter()] [AllowNull()] [System.String] $Checksum, [Parameter()] [System.UInt32] $BufferSize = 64KB ##TODO: Support Headers and UserAgent @@ -253,14 +253,14 @@ function InvokeResourceDownload { [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, + [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter()] [AllowNull()] [System.String] $Checksum, - [Parameter()] [System.Management.Automation.SwitchParameter] $Force, + [Parameter()] [System.Management.Automation.SwitchParameter] $Force, [Parameter()] [System.UInt32] $BufferSize = 64KB ##TODO: Support Headers and UserAgent ) process { - [ref] $null = $PSBoundParameters.Remove('Force'); + [ref] $null = $PSBoundParameters.Remove('Force'); if (-not (TestResourceDownload @PSBoundParameters) -or $Force) { SetResourceDownload @PSBoundParameters -Verbose:$false; } diff --git a/Lib/UnattendXml.ps1 b/Lib/UnattendXml.ps1 index 3f066993..c3147bbc 100644 --- a/Lib/UnattendXml.ps1 +++ b/Lib/UnattendXml.ps1 @@ -41,25 +41,25 @@ function NewUnattendXml { - - - + + + - + en-US en-US en-US en-US - + en-US en-US en-US en-US - + true true Work @@ -80,8 +80,8 @@ function NewUnattendXml { true false - - + + true true Work diff --git a/Src/LabHostConfiguration.ps1 b/Src/LabHostConfiguration.ps1 index 72f1c24a..99a87864 100644 --- a/Src/LabHostConfiguration.ps1 +++ b/Src/LabHostConfiguration.ps1 @@ -147,12 +147,12 @@ function Start-LabHostConfiguration { [CmdletBinding()] [OutputType([System.Boolean])] param ( - [Parameter()] [System.Management.Automation.SwitchParameter] $Force + [Parameter()] [System.Management.Automation.SwitchParameter] $Force ) process { WriteVerbose $localized.StartedHostConfiguration; - ## Create required directory structure - $hostDefaults = GetConfigurationData -Configuration Host; + ## Create required directory structure + $hostDefaults = GetConfigurationData -Configuration Host; foreach ($property in $hostDefaults.PSObject.Properties) { if (($property.Name.EndsWith('Path')) -and (-not [System.String]::IsNullOrEmpty($property.Value))) { [ref] $null = NewDirectory -Path $(ResolvePathEx -Path $Property.Value) -ErrorAction Stop; diff --git a/Src/LabHostDefaults.ps1 b/Src/LabHostDefaults.ps1 index aa67512e..97babbac 100644 --- a/Src/LabHostDefaults.ps1 +++ b/Src/LabHostDefaults.ps1 @@ -1,11 +1,11 @@ function Reset-LabHostDefault { <# - .SYNOPSIS - Reset the current Hyper-V host default settings back to defaults. + .SYNOPSIS + Reset the current Hyper-V host default settings back to defaults. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Management.Automation.PSCustomObject])] - param ( ) + param ( ) process { RemoveConfigurationData -Configuration Host; Get-LabHostDefault; @@ -15,12 +15,12 @@ New-Alias -Name Reset-LabHostDefaults -Value Reset-LabHostDefault function Get-LabHostDefault { <# - .SYNOPSIS - Gets the current Hyper-V host default settings. + .SYNOPSIS + Gets the current Hyper-V host default settings. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] - param ( ) + param ( ) process { GetConfigurationData -Configuration Host; } @@ -29,8 +29,8 @@ New-Alias -Name Get-LabHostDefaults -Value Get-LabHostDefault function GetLabHostDSCConfigurationPath { <# - .SYNOPSIS - Shortcut function to resolve the $labHostDefaults.ConfigurationPath property + .SYNOPSIS + Shortcut function to resolve the $labHostDefaults.ConfigurationPath property #> [CmdletBinding()] [OutputType([System.String])] @@ -43,19 +43,19 @@ function GetLabHostDSCConfigurationPath { function Set-LabHostDefault { <# - .SYNOPSIS - Sets the current Hyper-V host default settings. + .SYNOPSIS + Sets the current Hyper-V host default settings. #> - [CmdletBinding()] + [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] - param ( + param ( ## Lab host default configuration document path. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $ConfigurationPath, - ## Lab host Media/ISO path. + ## Lab host Media/ISO path. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $IsoPath, ## Lab host parent/master VHD(X) path. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $ParentVhdPath, - ## Lab host virtual machine differencing VHD(X) path. + ## Lab host virtual machine differencing VHD(X) path. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $DifferencingVhdPath, ## Lab host DSC resource path. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $ResourcePath, @@ -69,9 +69,9 @@ function Set-LabHostDefault { [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $DisableLocalFileCaching, ## Enable call stack logging in verbose output [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $EnableCallStackLogging - ) - process { - $hostDefaults = GetConfigurationData -Configuration Host; + ) + process { + $hostDefaults = GetConfigurationData -Configuration Host; ## This property may not be present in the original machine configuration file if ($hostDefaults.PSObject.Properties.Name -notcontains 'DisableLocalFileCaching') { @@ -82,8 +82,8 @@ function Set-LabHostDefault { [ref] $null = Add-Member -InputObject $hostDefaults -MemberType NoteProperty -Name 'EnableCallStackLogging' -Value $DisableLocalFileCaching; } - foreach ($path in @('IsoPath','ParentVhdPath','DifferencingVhdPath','ResourcePath','HotfixPath','UpdatePath','ConfigurationPath')) { - if ($PSBoundParameters.ContainsKey($path)) { + foreach ($path in @('IsoPath','ParentVhdPath','DifferencingVhdPath','ResourcePath','HotfixPath','UpdatePath','ConfigurationPath')) { + if ($PSBoundParameters.ContainsKey($path)) { $resolvedPath = ResolvePathEx -Path $PSBoundParameters[$path]; if (-not ((Test-Path -Path $resolvedPath -PathType Container -IsValid) -and (Test-Path -Path (Split-Path -Path $resolvedPath -Qualifier))) ) { throw ($localized.InvalidPathError -f $resolvedPath, $PSBoundParameters[$path]); @@ -91,23 +91,23 @@ function Set-LabHostDefault { else { $hostDefaults.$path = $resolvedPath.Trim('\'); } - } - } + } + } - if ($PSBoundParameters.ContainsKey('ResourceShareName')) { - $hostDefaults.ResourceShareName = $ResourceShareName; - } - if ($PSBoundParameters.ContainsKey('DisableLocalFileCaching')) { - $hostDefaults.DisableLocalFileCaching = $DisableLocalFileCaching.ToBool(); - } + if ($PSBoundParameters.ContainsKey('ResourceShareName')) { + $hostDefaults.ResourceShareName = $ResourceShareName; + } + if ($PSBoundParameters.ContainsKey('DisableLocalFileCaching')) { + $hostDefaults.DisableLocalFileCaching = $DisableLocalFileCaching.ToBool(); + } if ($PSBoundParameters.ContainsKey('EnableCallStackLogging')) { - ## Set the global script variable read by WriteVerbose + ## Set the global script variable read by WriteVerbose $script:labDefaults.CallStackLogging = $EnableCallStackLogging; $hostDefaults.EnableCallStackLogging = $EnableCallStackLogging.ToBool(); - } - - SetConfigurationData -Configuration Host -InputObject $hostDefaults; - return $hostDefaults; - } + } + + SetConfigurationData -Configuration Host -InputObject $hostDefaults; + return $hostDefaults; + } } #end function Set-LabHostDefault New-Alias -Name Set-LabHostDefaults -Value Set-LabHostDefault diff --git a/Src/LabMedia.ps1 b/Src/LabMedia.ps1 index 64fdac0e..abbe4743 100644 --- a/Src/LabMedia.ps1 +++ b/Src/LabMedia.ps1 @@ -101,12 +101,12 @@ function ResolveLabMedia { function Get-LabMedia { <# - .SYNOPSIS - Gets the currently registered built-in and custom lab media. + .SYNOPSIS + Gets the currently registered built-in and custom lab media. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] - param ( + param ( ## Media ID [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $Id, ## Only return custom media @@ -258,7 +258,7 @@ function InvokeLabMediaHotfixDownload { [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Id, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Checksum, - [Parameter()] [System.Management.Automation.SwitchParameter] $Force + [Parameter()] [System.Management.Automation.SwitchParameter] $Force ) process { $hostDefaults = GetConfigurationData -Configuration Host; @@ -409,12 +409,12 @@ function Unregister-LabMedia { function Reset-LabMedia { <# - .SYNOPSIS - Reset the lab media back to default settings. + .SYNOPSIS + Reset the lab media back to default settings. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Management.Automation.PSCustomObject])] - param ( ) + param ( ) process { RemoveConfigurationData -Configuration CustomMedia; Get-Labmedia; diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index b02ac2ee..8e45972c 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -399,16 +399,16 @@ function New-LabVM { [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $StartupMemory, - ## Default virtual machine miniumum dynamic memory allocation (bytes). + ## 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)] + [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $MaximumMemory, ## Default virtual machine processor count. - [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(1, 4)] + [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(1, 4)] [System.Int32] $ProcessorCount, # Input Locale @@ -492,7 +492,7 @@ function New-LabVM { foreach ($key in $parameterNames) { if ($PSBoundParameters.ContainsKey($key)) { $configurationName = '{0}_{1}' -f $labDefaults.ModuleName, $key; - [ref] $null = $skeletonConfigurationData.AllNodes[0].Add($configurationname, $PSBoundParameters.$key); + [ref] $null = $skeletonConfigurationData.AllNodes[0].Add($configurationname, $PSBoundParameters.$key); } } diff --git a/Src/LabVMDefaults.ps1 b/Src/LabVMDefaults.ps1 index 591b0c72..e397d3c3 100644 --- a/Src/LabVMDefaults.ps1 +++ b/Src/LabVMDefaults.ps1 @@ -1,11 +1,11 @@ function Reset-LabVMDefault { <# - .SYNOPSIS - Reset the current lab virtual machine default settings back to defaults. + .SYNOPSIS + Reset the current lab virtual machine default settings back to defaults. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Management.Automation.PSCustomObject])] - param ( ) + param ( ) process { RemoveConfigurationData -Configuration VM; Get-LabVMDefault; @@ -15,12 +15,12 @@ New-Alias -Name Reset-LabVMDefaults -Value Reset-LabVMDefault function Get-LabVMDefault { <# - .SYNOPSIS - Gets the current lab virtual machine default settings. + .SYNOPSIS + Gets the current lab virtual machine default settings. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] - param ( ) + param ( ) process { $labDefaults = GetConfigurationData -Configuration VM; ## BootOrder property should not be exposed via the Get-LabVMDefault/Set-LabVMDefault @@ -32,22 +32,22 @@ New-Alias -Name Get-LabVMDefaults -Value Get-LabVMDefault function Set-LabVMDefault { <# - .SYNOPSIS - Sets the lab virtual machine default settings. + .SYNOPSIS + Sets the lab virtual machine default settings. #> - [CmdletBinding()] + [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] - param ( - ## Default virtual machine startup memory (bytes). + param ( + ## Default virtual machine startup memory (bytes). [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $StartupMemory, - ## Default virtual machine miniumum dynamic memory allocation (bytes). + ## 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, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $MaximumMemory, ## Default virtual machine processor count. - [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(1, 4)] [System.Int32] $ProcessorCount, - ## Default virtual machine media Id. - [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Media, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(1, 4)] [System.Int32] $ProcessorCount, + ## Default virtual machine media Id. + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Media, ## Lab host internal switch name. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $SwitchName, # Input Locale @@ -72,46 +72,46 @@ function Set-LabVMDefault { [Parameter()] [System.UInt16] $BootDelay ) process { - $vmDefaults = GetConfigurationData -Configuration VM; + $vmDefaults = GetConfigurationData -Configuration VM; if ($PSBoundParameters.ContainsKey('StartupMemory')) { - $vmDefaults.StartupMemory = $StartupMemory; - } - if ($PSBoundParameters.ContainsKey('MinimumMemory')) { - $vmDefaults.MinimumMemory = $MinimumMemory; - } - if ($PSBoundParameters.ContainsKey('MaximumMemory')) { - $vmDefaults.MaximumMemory = $MaximumMemory; - } - if ($PSBoundParameters.ContainsKey('ProcessorCount')) { - $vmDefaults.ProcessorCount = $ProcessorCount; - } + $vmDefaults.StartupMemory = $StartupMemory; + } + if ($PSBoundParameters.ContainsKey('MinimumMemory')) { + $vmDefaults.MinimumMemory = $MinimumMemory; + } + if ($PSBoundParameters.ContainsKey('MaximumMemory')) { + $vmDefaults.MaximumMemory = $MaximumMemory; + } + if ($PSBoundParameters.ContainsKey('ProcessorCount')) { + $vmDefaults.ProcessorCount = $ProcessorCount; + } if ($PSBoundParameters.ContainsKey('Media')) { - $vmDefaults.Media = $Media; - } + $vmDefaults.Media = $Media; + } if ($PSBoundParameters.ContainsKey('SwitchName')) { - $vmDefaults.SwitchName = $SwitchName; - } + $vmDefaults.SwitchName = $SwitchName; + } if ($PSBoundParameters.ContainsKey('Timezone')) { $vmDefaults.Timezone = ValidateTimeZone -TimeZone $Timezone; - } + } if ($PSBoundParameters.ContainsKey('UILanguage')) { - $vmDefaults.UILanguage = $UILanguage; - } + $vmDefaults.UILanguage = $UILanguage; + } if ($PSBoundParameters.ContainsKey('InputLocale')) { - $vmDefaults.InputLocale = $InputLocale; - } + $vmDefaults.InputLocale = $InputLocale; + } if ($PSBoundParameters.ContainsKey('SystemLocale')) { - $vmDefaults.SystemLocale = $SystemLocale; - } + $vmDefaults.SystemLocale = $SystemLocale; + } if ($PSBoundParameters.ContainsKey('UserLocale')) { - $vmDefaults.UserLocale = $UserLocale; - } + $vmDefaults.UserLocale = $UserLocale; + } if ($PSBoundParameters.ContainsKey('RegisteredOwner')) { - $vmDefaults.RegisteredOwner = $RegisteredOwner; - } + $vmDefaults.RegisteredOwner = $RegisteredOwner; + } if ($PSBoundParameters.ContainsKey('RegisteredOrganization')) { - $vmDefaults.RegisteredOrganization = $RegisteredOrganization; - } + $vmDefaults.RegisteredOrganization = $RegisteredOrganization; + } if ($PSBoundParameters.ContainsKey('ClientCertificatePath')) { if (-not [System.String]::IsNullOrWhitespace($ClientCertificatePath)) { $ClientCertificatePath = [System.Environment]::ExpandEnvironmentVariables($ClientCertificatePath); @@ -120,7 +120,7 @@ function Set-LabVMDefault { } } $vmDefaults.ClientCertificatePath = $ClientCertificatePath; - } + } if ($PSBoundParameters.ContainsKey('RootCertificatePath')) { if (-not [System.String]::IsNullOrWhitespace($RootCertificatePath)) { $RootCertificatePath = [System.Environment]::ExpandEnvironmentVariables($RootCertificatePath); @@ -129,21 +129,21 @@ function Set-LabVMDefault { } } $vmDefaults.RootCertificatePath = $RootCertificatePath; - } + } if ($PSBoundParameters.ContainsKey('BootDelay')) { $vmDefaults.BootDelay = $BootDelay; } - if ($vmDefaults.StartupMemory -lt $vmDefaults.MinimumMemory) { - throw ($localized.StartMemLessThanMinMemError -f $vmDefaults.StartupMemory, $vmDefaults.MinimumMemory); - } - elseif ($vmDefaults.StartupMemory -gt $vmDefaults.MaximumMemory) { - throw ($localized.StartMemGreaterThanMaxMemError -f $vmDefaults.StartupMemory, $vmDefaults.MaximumMemory); - } - - SetConfigurationData -Configuration VM -InputObject $vmDefaults; + if ($vmDefaults.StartupMemory -lt $vmDefaults.MinimumMemory) { + throw ($localized.StartMemLessThanMinMemError -f $vmDefaults.StartupMemory, $vmDefaults.MinimumMemory); + } + elseif ($vmDefaults.StartupMemory -gt $vmDefaults.MaximumMemory) { + throw ($localized.StartMemGreaterThanMaxMemError -f $vmDefaults.StartupMemory, $vmDefaults.MaximumMemory); + } + + SetConfigurationData -Configuration VM -InputObject $vmDefaults; ## BootOrder property should not be exposed via the Get-LabVMDefault/Set-LabVMDefault - $vmDefaults.PSObject.Properties.Remove('BootOrder'); + $vmDefaults.PSObject.Properties.Remove('BootOrder'); return $vmDefaults; } } #end function Set-LabVMDefault @@ -163,7 +163,7 @@ function ValidateTimeZone { process { try { $TZ = [TimeZoneInfo]::FindSystemTimeZoneById($TimeZone) - return $TZ.StandardName; + return $TZ.StandardName; } catch [System.TimeZoneNotFoundException] { throw $_; From 0c75a12101e0c1412e61a38552d5c0bf73168bc0 Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Tue, 15 Dec 2015 22:39:35 +0000 Subject: [PATCH 07/23] Adds additional debugging information Renames the media -Id parameter to -MediaId --- Lib/VirtualMachine.ps1 | 8 ++++++++ Src/LabVM.ps1 | 15 ++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Lib/VirtualMachine.ps1 b/Lib/VirtualMachine.ps1 index 2fce5f1f..b94f8d68 100644 --- a/Lib/VirtualMachine.ps1 +++ b/Lib/VirtualMachine.ps1 @@ -87,6 +87,14 @@ function SetLabVirtualMachine { ## Resolve the xVMHyperV resource parameters $vmHyperVParams = GetVirtualMachineProperties @PSBoundParameters; ImportDscResource -ModuleName xHyper-V -ResourceName MSFT_xVMHyperV -Prefix VM; + foreach ($key in $vmHyperVParams.Keys) { + Write-Host ("CS! '{0}' : '{1}'" -f $key, $vmHyperVParams.$key -join ',') -ForegroundColor Yellow; + if ($vmHyperVParams.$key -is [System.String[]]) { + foreach ($s in $vmHyperVParams.$key) { + Write-Host ("CS! '{0}[]' : '{1}'" -f $key, $s) -ForegroundColor Yellow; + } + } + } InvokeDscResource -ResourceName VM -Parameters $vmHyperVParams # -ErrorAction SilentlyContinue; } #end process } #end function SetLabVirtualMachine diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index 8e45972c..a213bc6a 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -382,7 +382,7 @@ function New-LabVM { .DESCRIPTION The New-LabVM cmdlet creates a bare virtual machine using the specified media. No DSC configuration is applied, although DSC resources are still copied in to the VM's VHD(X). - NOTE: The -Id parameter is dynamic and is not displayed in the help output. + NOTE: The -MediaId parameter is dynamic and is not displayed in the help output. .LINK Register-LabMedia Unregister-LabMedia @@ -458,16 +458,14 @@ function New-LabVM { $parameterAttribute = New-Object -TypeName 'System.Management.Automation.ParameterAttribute'; $parameterAttribute.ParameterSetName = '__AllParameterSets'; $parameterAttribute.Mandatory = $true; - $aliasAttribute = New-Object -TypeName 'System.Management.Automation.AliasAttribute' -ArgumentList @('MediaId'); $attributeCollection = New-Object -TypeName 'System.Collections.ObjectModel.Collection[System.Attribute]'; $attributeCollection.Add($parameterAttribute); - $attributeCollection.Add($aliasAttribute); $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 @('Id', [System.String], $attributeCollection); + $runtimeParameter = New-Object -TypeName 'System.Management.Automation.RuntimeDefinedParameter' -ArgumentList @('MediaId', [System.String], $attributeCollection); $runtimeParameterDictionary = New-Object -TypeName 'System.Management.Automation.RuntimeDefinedParameterDictionary'; - $runtimeParameterDictionary.Add('Id', $runtimeParameter); + $runtimeParameterDictionary.Add('MediaId', $runtimeParameter); return $runtimeParameterDictionary; } begin { @@ -483,7 +481,7 @@ function New-LabVM { ## Create a skelton config data $skeletonConfigurationData = @{ AllNodes = @( - @{ NodeName = $vmName; "$($labDefaults.ModuleName)_Media" = $PSBoundParameters.Id; } + @{ NodeName = $vmName; "$($labDefaults.ModuleName)_Media" = $PSBoundParameters.MediaId; } ) }; @@ -495,9 +493,8 @@ function New-LabVM { [ref] $null = $skeletonConfigurationData.AllNodes[0].Add($configurationname, $PSBoundParameters.$key); } } - - WriteVerbose -Message ($localized.CreatingQuickVM -f $vmName, $MediaId); + WriteVerbose -Message ($localized.CreatingQuickVM -f $vmName, $PSBoundParameters.MediaId); NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot; - } + } #end foreach name } #end process } #end function New-LabVM From ed27751c99006982d0cc0df842adf8050329319c Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Wed, 16 Dec 2015 10:04:45 +0000 Subject: [PATCH 08/23] Updates New-LabVM help and adds multiple switch support --- Src/LabVM.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index a213bc6a..ff0f8280 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -382,7 +382,9 @@ function New-LabVM { .DESCRIPTION The New-LabVM cmdlet creates a bare virtual machine using the specified media. No DSC configuration is applied, although DSC resources are still copied in to the VM's VHD(X). - NOTE: The -MediaId parameter is dynamic and is not displayed in the help output. + 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. .LINK Register-LabMedia Unregister-LabMedia @@ -447,8 +449,8 @@ function New-LabVM { [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Password')] [ValidateNotNullOrEmpty()] [System.Security.SecureString] $Password, - ## Virtual machine switch name. NOTE: the virtual switch must already exist - it won't be created! - [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $SwitchName, + ## Virtual machine switch name. + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String[]] $SwitchName, ## Skip creating baseline snapshots [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $NoSnapshot From 867a7769c1316a69542f309cfa722da699c8b3ed Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Wed, 16 Dec 2015 21:13:58 +0000 Subject: [PATCH 09/23] Adds Remove-LabVM function Removes VM switch creation from New-LabVM Removed VM resource injection for quick VMs Removes temporary debugging information Misc formatting changes --- Lib/VirtualMachine.ps1 | 8 -- Resources.psd1 | 21 +++-- Src/LabVM.ps1 | 186 +++++++++++++++++++++++++++++------------ Src/LabVMDiskFile.ps1 | 84 ++++++++++--------- 4 files changed, 190 insertions(+), 109 deletions(-) diff --git a/Lib/VirtualMachine.ps1 b/Lib/VirtualMachine.ps1 index b94f8d68..2fce5f1f 100644 --- a/Lib/VirtualMachine.ps1 +++ b/Lib/VirtualMachine.ps1 @@ -87,14 +87,6 @@ function SetLabVirtualMachine { ## Resolve the xVMHyperV resource parameters $vmHyperVParams = GetVirtualMachineProperties @PSBoundParameters; ImportDscResource -ModuleName xHyper-V -ResourceName MSFT_xVMHyperV -Prefix VM; - foreach ($key in $vmHyperVParams.Keys) { - Write-Host ("CS! '{0}' : '{1}'" -f $key, $vmHyperVParams.$key -join ',') -ForegroundColor Yellow; - if ($vmHyperVParams.$key -is [System.String[]]) { - foreach ($s in $vmHyperVParams.$key) { - Write-Host ("CS! '{0}[]' : '{1}'" -f $key, $s) -ForegroundColor Yellow; - } - } - } InvokeDscResource -ResourceName VM -Parameters $vmHyperVParams # -ErrorAction SilentlyContinue; } #end process } #end function SetLabVirtualMachine diff --git a/Resources.psd1 b/Resources.psd1 index 7675f3a2..5b55c2fc 100644 --- a/Resources.psd1 +++ b/Resources.psd1 @@ -1,21 +1,21 @@ ConvertFrom-StringData -StringData @' - DownloadingResource = Downloading resource '{0}' to '{1}'. + DownloadingResource = Downloading resource '{0}' to '{1}'. DownloadingActivity = Downloading '{0}'. DownloadStatus = {0:N0} of {1:N0} bytes ({2} %). UsingProxyServer = Using proxy server '{0}'. CopyingResource = Copying resource '{0}' to '{1}'. MissingResourceFile = Resource '{0}' does not exist. - ResourceChecksumNotSpecified = Resource '{0}' checksum was not specified. + ResourceChecksumNotSpecified = Resource '{0}' checksum was not specified. ResourceChecksumMatch = Resource '{0}' checksum matches '{1}'. - ResourceChecksumMismatch = Resource '{0}' checksum does not match '{1}'. + ResourceChecksumMismatch = Resource '{0}' checksum does not match '{1}'. CalculatingResourceChecksum = Calculating resource '{0}' checksum. - WritingResourceChecksum = Writing checksum '{0}' to resource '{1}'. - CreatingDirectory = Creating directory '{0}'. - RemovingDirectory = Removing directory '{0}'. - DirectoryExists = Directory '{0}' already exists. - RenamingPath = Renaming '{0}' to '{1}'. + WritingResourceChecksum = Writing checksum '{0}' to resource '{1}'. + CreatingDirectory = Creating directory '{0}'. + RemovingDirectory = Removing directory '{0}'. + DirectoryExists = Directory '{0}' already exists. + RenamingPath = Renaming '{0}' to '{1}'. TestingPathExists = Testing directory '{0}' exists. - ExpandingArchive = Expanding archive '{0}' to '{1}'. + ExpandingArchive = Expanding archive '{0}' to '{1}'. PendingRebootWarning = A pending reboot is required. Please reboot the system and re-run the configuration. CheckingDscResource = Checking DSC Resource '{0}\\{1}'. ImportingDscResource = Importing DSC Resource '{0}\\{1}'. @@ -100,6 +100,8 @@ ConvertFrom-StringData -StringData @' LocatingWimImageIndex = Locating WIM image '{0}' index. MediaFileCachingDisabled = Caching of file-based media is disabled. Skipping media '{0}' download. CreatingQuickVM = Creating quick VM '{0}' using media '{1}'. + RemovingQuickVM = Removing quick VM '{0}'. + ResettingVM = Resetting VM '{0}'. NoCertificateFoundWarning = No '{0}' certificate was found. CannotLocateLcmFileWarning = Cannot locate LCM configuration file '{0}'. No DSC Local Configuration Manager configuration will be applied. @@ -140,4 +142,5 @@ ConvertFrom-StringData -StringData @' CannotBindArgumentError = Cannot bind argument to parameter '{0}' because it is an empty string. StartMemLessThanMinMemError = Startup memory '{0}' cannot be less than minimum memory '{1}'. StartMemGreaterThanMaxMemError = Startup memory '{0}' cannot be greater than maximum memory '{1}'. + SwitchDoesNotExistError = Virtual switch '{0}' cannot be found. '@ \ No newline at end of file diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index ff0f8280..92eb10dc 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -10,12 +10,17 @@ function ResolveLabVMProperties { [OutputType([System.Collections.Hashtable])] param ( ## Lab VM/Node name - [Parameter(Mandatory)] [System.String] $NodeName, - [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.String] $NodeName, + ## Lab DSC configuration data - [Parameter(Mandatory)] [System.Collections.Hashtable] $ConfigurationData, + [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.Collections.Hashtable] $ConfigurationData, + ## Do not enumerate the AllNode.'*' - [Parameter()] [System.Management.Automation.SwitchParameter] $NoEnumerateWildcardNode + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $NoEnumerateWildcardNode ) process { $node = @{ }; @@ -73,9 +78,12 @@ function Get-LabVM { param ( ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory)] [System.Object] $ConfigurationData, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.Object] $ConfigurationData, + ## Lab VM/Node name - [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String[]] $Name + [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String[]] $Name ) begin { $ConfigurationData = ConvertToConfigurationData -ConfigurationData $ConfigurationData; @@ -115,9 +123,12 @@ function Test-LabVM { param ( ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory)] [System.Object] $ConfigurationData, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.Object] $ConfigurationData, + ## Lab VM/Node name - [Parameter()] [ValidateNotNullOrEmpty()] [System.String[]] $Name + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String[]] $Name ) begin { $ConfigurationData = ConvertToConfigurationData -ConfigurationData $ConfigurationData; @@ -171,23 +182,35 @@ function NewLabVM { [CmdletBinding(DefaultParameterSetName = 'PSCredential')] param ( ## Lab VM/Node name - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Name, + [Parameter(Mandatory,ValueFromPipeline)] + [ValidateNotNullOrEmpty()] [System.String] $Name, + ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory)] [System.Collections.Hashtable] $ConfigurationData, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.Collections.Hashtable] $ConfigurationData, ## Local administrator password of the VM. The username is NOT used. - [Parameter(ParameterSetName = 'PSCredential')] [ValidateNotNullOrEmpty()] + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'PSCredential')] + [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] $Credential = (& $credentialCheckScriptBlock), ## Local administrator password of the VM. - [Parameter(Mandatory, ParameterSetName = 'Password')] [ValidateNotNullOrEmpty()] + [Parameter(Mandatory, ParameterSetName = 'Password', ValueFromPipelineByPropertyName)] + [ValidateNotNullOrEmpty()] [System.Security.SecureString] $Password, ## Virtual machine DSC .mof and .meta.mof location - [Parameter()] [System.String] $Path = (GetLabHostDSCConfigurationPath), + [Parameter(ValueFromPipelineByPropertyName)] + [System.String] $Path = (GetLabHostDSCConfigurationPath), + ## Skip creating baseline snapshots - [Parameter()] [System.Management.Automation.SwitchParameter] $NoSnapshot + [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 @@ -202,34 +225,36 @@ function NewLabVM { $Name = $node.NodeName; [ref] $null = $node.Remove('NodeName'); - ## 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); + 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 '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'); } - } - else { - WriteWarning ($localized.NoCertificateFoundWarning -f 'Root'); - } - if (-not (Test-LabImage -Id $node.Media)) { - [ref] $null = New-LabImage -Id $node.Media -ConfigurationData $ConfigurationData; - } + if (-not (Test-LabImage -Id $node.Media)) { + [ref] $null = New-LabImage -Id $node.Media -ConfigurationData $ConfigurationData; + } - WriteVerbose ($localized.SettingVMConfiguration -f 'Virtual Switch', $node.SwitchName); - SetLabSwitch -Name $node.SwitchName -ConfigurationData $ConfigurationData; + WriteVerbose ($localized.SettingVMConfiguration -f 'Virtual Switch', $node.SwitchName); + SetLabSwitch -Name $node.SwitchName -ConfigurationData $ConfigurationData; + } #end if not quick VM - WriteVerbose ($localized.ResettingVMConfiguration -f 'VHDX', $node.Media); + WriteVerbose ($localized.ResettingVMConfiguration -f 'VHDX', "$Name.vhdx"); ResetLabVMDisk -Name $Name -Media $node.Media -ErrorAction Stop; WriteVerbose ($localized.SettingVMConfiguration -f 'VM', $Name); @@ -261,7 +286,7 @@ function NewLabVM { if ($node.CustomBootStrap) { $setLabVMDiskFileParams['CustomBootStrap'] = ($node.CustomBootStrap).ToString(); } - SetLabVMDiskFile @setLabVMDiskFileParams; + SetLabVMDiskFile @setLabVMDiskFileParams -IsQuickVM:$IsQuickVM; if (-not $NoSnapshot) { $snapshotName = $localized.BaselineSnapshotName -f $labDefaults.ModuleName; @@ -290,12 +315,17 @@ function RemoveLabVM { [CmdletBinding()] param ( ## Lab VM/Node name - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Name, + [Parameter(Mandatory, ValueFromPipeline)] + [ValidateNotNullOrEmpty()] [System.String] $Name, + ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory)] [Object] $ConfigurationData, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.Object] $ConfigurationData, + ## Include removal of virtual switch(es). By default virtual switches are not removed. - [Parameter()] [System.Management.Automation.SwitchParameter] $RemoveSwitch + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $RemoveSwitch ) process { $node = ResolveLabVMProperties -NodeName $Name -ConfigurationData $ConfigurationData -NoEnumerateWildcardNode -ErrorAction Stop; @@ -305,7 +335,10 @@ function RemoveLabVM { $Name = $node.NodeName; # 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 + Get-VMSnapshot -VMName $Name -ErrorAction SilentlyContinue | + Sort-Object -Property CreationTime | + Select-Object -First 1 | + Restore-VMSnapshot -Confirm:$false; RemoveLabVMSnapshot -Name $Name; @@ -336,26 +369,32 @@ function Reset-LabVM { .SYNOPSIS Deletes and recreates a lab virtual machine, reapplying the MOF #> - [CmdletBinding(DefaultParameterSetName = 'PSCredential')] + [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'PSCredential')] param ( ## Lab VM/Node name - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, + [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String[]] $Name, + ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory)] [System.Object] $ConfigurationData, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.Object] $ConfigurationData, ## Local administrator password of the VM. The username is NOT used. - [Parameter(ParameterSetName = 'PSCredential')] [ValidateNotNullOrEmpty()] + [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'PSCredential')] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] $Credential = (& $credentialCheckScriptBlock), ## Local administrator password of the VM. - [Parameter(Mandatory, ParameterSetName = 'Password')] [ValidateNotNullOrEmpty()] + [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Password')] [ValidateNotNullOrEmpty()] [System.Security.SecureString] $Password, ## Directory path containing the VM .mof file(s) - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Path = (GetLabHostDSCConfigurationPath), + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Path = (GetLabHostDSCConfigurationPath), + ## Skip creating baseline snapshots - [Parameter()] [System.Management.Automation.SwitchParameter] $NoSnapshot + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $NoSnapshot ) begin { ## If we have only a secure string, create a PSCredential @@ -369,9 +408,13 @@ function Reset-LabVM { } process { foreach ($vmName in $Name) { - RemoveLabVM -Name $vmName -ConfigurationData $ConfigurationData; - NewLabVM -Name $vmName -ConfigurationData $ConfigurationData -Path $Path -NoSnapshot:$NoSnapshot -Credential $Credential; - } + $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'Reset-LabVM', $vmName; + $verboseProcessMessage = $localized.ResettingVM -f $vmName; + if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { + RemoveLabVM -Name $vmName -ConfigurationData $ConfigurationData; + NewLabVM -Name $vmName -ConfigurationData $ConfigurationData -Path $Path -NoSnapshot:$NoSnapshot -Credential $Credential; + } #end if should process + } #end foreach VM } #end process } #end function Reset-LabVM @@ -380,7 +423,7 @@ 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 DSC configuration is applied, although DSC resources are still copied in to the VM's VHD(X). + 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. @@ -477,6 +520,13 @@ function New-LabVM { } if (-not $Credential) { throw ($localized.CannotProcessCommandError -f 'Credential'); } elseif ($Credential.Password.Length -eq 0) { throw ($localized.CannotBindArgumentError -f 'Password'); } + + ## Test VM Switch exists + foreach ($switch in $SwitchName) { + if (-not (Get-VMSwitch -Name $switch -ErrorAction SilentlyContinue)) { + throw ($localized.SwitchDoesNotExistError -f $switch); + } + } } process { foreach ($vmName in $Name) { @@ -496,7 +546,35 @@ function New-LabVM { } } WriteVerbose -Message ($localized.CreatingQuickVM -f $vmName, $PSBoundParameters.MediaId); - NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot; + NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot -IsQuickVM; } #end foreach name } #end process } #end function New-LabVM + +function Remove-LabVM { +<# + .SYNOPSIS + Removes one or more lab virtual machines and differencing VHD(X)s. +#> + [CmdletBinding(SupportsShouldProcess)] + param ( + ## Lab VM/Node name + [Parameter(Mandatory, ValueFromPipeline)] + [ValidateNotNullOrEmpty()] [System.String[]] $Name + ) + process { + foreach ($vmName in $Name) { + $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'Remove-LabVM', $vmName; + $verboseProcessMessage = $localized.RemovingQuickVM -f $vmName; + if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { + ## Create a skelton config data + $skeletonConfigurationData = @{ + AllNodes = @( + @{ NodeName = $vmName; } + ) + }; + RemoveLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData; + } #end if should process + } #end foreach VM + } #end process +} #end function Remove-LabVM diff --git a/Src/LabVMDiskFile.ps1 b/Src/LabVMDiskFile.ps1 index f578504d..b1f0f68a 100644 --- a/Src/LabVMDiskFile.ps1 +++ b/Src/LabVMDiskFile.ps1 @@ -70,7 +70,11 @@ function SetLabVMDiskFile { ## Lab VM/Node DSC .mof and .meta.mof configuration files [Parameter()] [System.String] $Path, ## Custom bootstrap script - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $CustomBootStrap + [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $CustomBootStrap, + + ## Is a quick VM + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $IsQuickVM ) process { ## Temporarily disable Windows Explorer popup disk initialization and format notifications @@ -84,10 +88,6 @@ function SetLabVMDiskFile { $vhdDriveLetter = Get-Partition -DiskNumber $vhd.DiskNumber | Where-Object DriveLetter | Select-Object -Last 1 -ExpandProperty DriveLetter; Start-Service -Name 'ShellHWDetection'; - $destinationPath = '{0}:\Program Files\WindowsPowershell\Modules' -f $vhdDriveLetter; - WriteVerbose ($localized.AddingDSCResourceModules -f $destinationPath); - SetLabVMDiskDscResource -DestinationPath $destinationPath; - ## Create Unattend.xml $newUnattendXmlParams = @{ ComputerName = $Name; @@ -108,44 +108,52 @@ function SetLabVMDiskFile { WriteVerbose ($localized.AddingUnattendXmlFile -f $unattendXmlPath); $unattendXml = SetUnattendXml @newUnattendXmlParams -Path $unattendXmlPath; - $bootStrapPath = '{0}:\BootStrap' -f $vhdDriveLetter; - WriteVerbose ($localized.AddingBootStrapFile -f $bootStrapPath); - if ($CustomBootStrap) { SetBootStrap -Path $bootStrapPath -CustomBootStrap $CustomBootStrap; } - else { SetBootStrap -Path $bootStrapPath; } + ## Don't copy DSC resources, bootstrap or the certificates for a quick VM + if (-not $IsQuickVM) { + + $destinationPath = '{0}:\Program Files\WindowsPowershell\Modules' -f $vhdDriveLetter; + WriteVerbose ($localized.AddingDSCResourceModules -f $destinationPath); + SetLabVMDiskDscResource -DestinationPath $destinationPath; + + $bootStrapPath = '{0}:\BootStrap' -f $vhdDriveLetter; + WriteVerbose ($localized.AddingBootStrapFile -f $bootStrapPath); + if ($CustomBootStrap) { SetBootStrap -Path $bootStrapPath -CustomBootStrap $CustomBootStrap; } + else { SetBootStrap -Path $bootStrapPath; } - $setupCompleteCmdPath = '{0}:\Windows\Setup\Scripts' -f $vhdDriveLetter; - WriteVerbose ($localized.AddingSetupCompleteCmdFile -f $setupCompleteCmdPath); - SetSetupCompleteCmd -Path $setupCompleteCmdPath; + $setupCompleteCmdPath = '{0}:\Windows\Setup\Scripts' -f $vhdDriveLetter; + WriteVerbose ($localized.AddingSetupCompleteCmdFile -f $setupCompleteCmdPath); + SetSetupCompleteCmd -Path $setupCompleteCmdPath; - ## Copy MOF files to \BootStrap\localhost.mof and \BootStrap\localhost.meta.mof - if ($Path) { - $mofPath = Join-Path -Path $Path -ChildPath ('{0}.mof' -f $Name); - $destinationMofPath = Join-Path -Path $bootStrapPath -ChildPath 'localhost.mof'; - WriteVerbose ($localized.AddingDscConfiguration -f $destinationMofPath); - if (-not (Test-Path -Path $mofPath)) { WriteWarning ($localized.CannotLocateMofFileError -f $mofPath); } - else { Copy-Item -Path $mofPath -Destination $destinationMofPath -Force -ErrorAction Stop; } + ## Copy MOF files to \BootStrap\localhost.mof and \BootStrap\localhost.meta.mof + if ($Path) { + $mofPath = Join-Path -Path $Path -ChildPath ('{0}.mof' -f $Name); + $destinationMofPath = Join-Path -Path $bootStrapPath -ChildPath 'localhost.mof'; + WriteVerbose ($localized.AddingDscConfiguration -f $destinationMofPath); + if (-not (Test-Path -Path $mofPath)) { WriteWarning ($localized.CannotLocateMofFileError -f $mofPath); } + else { Copy-Item -Path $mofPath -Destination $destinationMofPath -Force -ErrorAction Stop; } - $metaMofPath = Join-Path -Path $Path -ChildPath ('{0}.meta.mof' -f $Name); - if (Test-Path -Path $metaMofPath -PathType Leaf) { - $destinationMetaMofPath = Join-Path -Path $bootStrapPath -ChildPath 'localhost.meta.mof'; - WriteVerbose ($localized.AddingDscConfiguration -f $destinationMetaMofPath); - Copy-Item -Path $metaMofPath -Destination $destinationMetaMofPath -Force; + $metaMofPath = Join-Path -Path $Path -ChildPath ('{0}.meta.mof' -f $Name); + if (Test-Path -Path $metaMofPath -PathType Leaf) { + $destinationMetaMofPath = Join-Path -Path $bootStrapPath -ChildPath 'localhost.meta.mof'; + WriteVerbose ($localized.AddingDscConfiguration -f $destinationMetaMofPath); + Copy-Item -Path $metaMofPath -Destination $destinationMetaMofPath -Force; + } } - } - ## Copy certificates - if (-not [System.String]::IsNullOrWhitespace($NodeData.ClientCertificatePath)) { - $destinationCertificatePath = Join-Path -Path $bootStrapPath -ChildPath 'LabClient.pfx'; - $expandedClientCertificatePath = [System.Environment]::ExpandEnvironmentVariables($NodeData.ClientCertificatePath); - WriteVerbose ($localized.AddingCertificate -f 'Client', $destinationCertificatePath); - Copy-Item -Path $expandedClientCertificatePath -Destination $destinationCertificatePath -Force; - } - if (-not [System.String]::IsNullOrWhitespace($NodeData.RootCertificatePath)) { - $destinationCertificatePath = Join-Path -Path $bootStrapPath -ChildPath 'LabRoot.cer'; - $expandedRootCertificatePath = [System.Environment]::ExpandEnvironmentVariables($NodeData.RootCertificatePath); - WriteVerbose ($localized.AddingCertificate -f 'Root', $destinationCertificatePath); - Copy-Item -Path $expandedRootCertificatePath -Destination $destinationCertificatePath -Force; - } + ## Copy certificates + if (-not [System.String]::IsNullOrWhitespace($NodeData.ClientCertificatePath)) { + $destinationCertificatePath = Join-Path -Path $bootStrapPath -ChildPath 'LabClient.pfx'; + $expandedClientCertificatePath = [System.Environment]::ExpandEnvironmentVariables($NodeData.ClientCertificatePath); + WriteVerbose ($localized.AddingCertificate -f 'Client', $destinationCertificatePath); + Copy-Item -Path $expandedClientCertificatePath -Destination $destinationCertificatePath -Force; + } + if (-not [System.String]::IsNullOrWhitespace($NodeData.RootCertificatePath)) { + $destinationCertificatePath = Join-Path -Path $bootStrapPath -ChildPath 'LabRoot.cer'; + $expandedRootCertificatePath = [System.Environment]::ExpandEnvironmentVariables($NodeData.RootCertificatePath); + WriteVerbose ($localized.AddingCertificate -f 'Root', $destinationCertificatePath); + Copy-Item -Path $expandedRootCertificatePath -Destination $destinationCertificatePath -Force; + } + } #end if not Quick VM WriteVerbose ($localized.DismountingDiskImage -f $VhdPath); Dismount-Vhd -Path $VhdPath; From 0a20e1e637ef49489f9e4a8a530717810aa946c9 Mon Sep 17 00:00:00 2001 From: Iain Brighton Date: Thu, 17 Dec 2015 15:15:10 +0000 Subject: [PATCH 10/23] Adds bootstrapping back into the Quick VM functionality to enable administrator accounts on client OSes Adds WhatIf and Confirm support to New-LabVM --- Src/LabVM.ps1 | 25 +++++----- Src/LabVMDiskFile.ps1 | 111 ++++++++++++++++++++++-------------------- 2 files changed, 72 insertions(+), 64 deletions(-) diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index 92eb10dc..27a25a5c 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -225,6 +225,7 @@ function NewLabVM { $Name = $node.NodeName; [ref] $null = $node.Remove('NodeName'); + ## Don't attempt to check certificates or create virtual switch for quick VMs if (-not $IsQuickVM) { ## Check for certificate before we (re)create the VM if (-not [System.String]::IsNullOrWhitespace($node.ClientCertificatePath)) { @@ -245,15 +246,14 @@ function NewLabVM { else { WriteWarning ($localized.NoCertificateFoundWarning -f 'Root'); } - - if (-not (Test-LabImage -Id $node.Media)) { - [ref] $null = New-LabImage -Id $node.Media -ConfigurationData $ConfigurationData; - } - WriteVerbose ($localized.SettingVMConfiguration -f 'Virtual Switch', $node.SwitchName); SetLabSwitch -Name $node.SwitchName -ConfigurationData $ConfigurationData; } #end if not quick VM + if (-not (Test-LabImage -Id $node.Media)) { + [ref] $null = New-LabImage -Id $node.Media -ConfigurationData $ConfigurationData; + } + WriteVerbose ($localized.ResettingVMConfiguration -f 'VHDX', "$Name.vhdx"); ResetLabVMDisk -Name $Name -Media $node.Media -ErrorAction Stop; @@ -286,7 +286,7 @@ function NewLabVM { if ($node.CustomBootStrap) { $setLabVMDiskFileParams['CustomBootStrap'] = ($node.CustomBootStrap).ToString(); } - SetLabVMDiskFile @setLabVMDiskFileParams -IsQuickVM:$IsQuickVM; + SetLabVMDiskFile @setLabVMDiskFileParams; if (-not $NoSnapshot) { $snapshotName = $localized.BaselineSnapshotName -f $labDefaults.ModuleName; @@ -434,7 +434,7 @@ function New-LabVM { Get-LabVMDefault Set-LabVMDefault #> - [CmdletBinding(DefaultParameterSetName = 'PSCredential')] + [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'PSCredential')] param ( ## Lab VM/Node name [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] @@ -526,8 +526,8 @@ function New-LabVM { if (-not (Get-VMSwitch -Name $switch -ErrorAction SilentlyContinue)) { throw ($localized.SwitchDoesNotExistError -f $switch); } - } - } + } #end foreach switch + } #end begin process { foreach ($vmName in $Name) { ## Create a skelton config data @@ -545,8 +545,11 @@ function New-LabVM { [ref] $null = $skeletonConfigurationData.AllNodes[0].Add($configurationname, $PSBoundParameters.$key); } } - WriteVerbose -Message ($localized.CreatingQuickVM -f $vmName, $PSBoundParameters.MediaId); - NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot -IsQuickVM; + $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'New-LabVM', $vmName; + $verboseProcessMessage = $localized.CreatingQuickVM -f $vmName, $PSBoundParameters.MediaId; + if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { + NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot -IsQuickVM; + } } #end foreach name } #end process } #end function New-LabVM diff --git a/Src/LabVMDiskFile.ps1 b/Src/LabVMDiskFile.ps1 index b1f0f68a..3b00ec30 100644 --- a/Src/LabVMDiskFile.ps1 +++ b/Src/LabVMDiskFile.ps1 @@ -6,7 +6,8 @@ function SetLabVMDiskDscResource { [CmdletBinding()] param ( ## The target VHDX modules path - [Parameter(Mandatory, ValueFromPipeline)] [System.String] $DestinationPath + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $DestinationPath ) process { $dscResourceModules = GetDscResourceModule -Path "$env:ProgramFiles\WindowsPowershell\Modules"; @@ -27,9 +28,12 @@ function SetLabVMDiskResource { param ( ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $ConfigurationData, + [Parameter(Mandatory, ValueFromPipeline)] + [System.Object] $ConfigurationData, + ## Lab VM/Node name - [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Name + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name ) begin { $hostDefaults = GetConfigurationData -Configuration Host; @@ -62,19 +66,24 @@ function SetLabVMDiskFile { [CmdletBinding()] param ( ## Lab VM/Node name - [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Name, + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + ## Lab VM/Node configuration data - [Parameter(Mandatory)] [System.Collections.Hashtable] $NodeData, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.Collections.Hashtable] $NodeData, + ## Local administrator password of the VM - [Parameter(Mandatory)] [System.Management.Automation.PSCredential] $Credential, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.Management.Automation.PSCredential] $Credential, + ## Lab VM/Node DSC .mof and .meta.mof configuration files - [Parameter()] [System.String] $Path, - ## Custom bootstrap script - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $CustomBootStrap, - - ## Is a quick VM [Parameter(ValueFromPipelineByPropertyName)] - [System.Management.Automation.SwitchParameter] $IsQuickVM + [System.String] $Path, + + ## Custom bootstrap script + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $CustomBootStrap ) process { ## Temporarily disable Windows Explorer popup disk initialization and format notifications @@ -106,54 +115,50 @@ function SetLabVMDiskFile { } $unattendXmlPath = '{0}:\Windows\System32\Sysprep\Unattend.xml' -f $vhdDriveLetter; WriteVerbose ($localized.AddingUnattendXmlFile -f $unattendXmlPath); - $unattendXml = SetUnattendXml @newUnattendXmlParams -Path $unattendXmlPath; - - ## Don't copy DSC resources, bootstrap or the certificates for a quick VM - if (-not $IsQuickVM) { + [ref] $null = SetUnattendXml @newUnattendXmlParams -Path $unattendXmlPath; - $destinationPath = '{0}:\Program Files\WindowsPowershell\Modules' -f $vhdDriveLetter; - WriteVerbose ($localized.AddingDSCResourceModules -f $destinationPath); - SetLabVMDiskDscResource -DestinationPath $destinationPath; + $destinationPath = '{0}:\Program Files\WindowsPowershell\Modules' -f $vhdDriveLetter; + WriteVerbose ($localized.AddingDSCResourceModules -f $destinationPath); + SetLabVMDiskDscResource -DestinationPath $destinationPath; - $bootStrapPath = '{0}:\BootStrap' -f $vhdDriveLetter; - WriteVerbose ($localized.AddingBootStrapFile -f $bootStrapPath); - if ($CustomBootStrap) { SetBootStrap -Path $bootStrapPath -CustomBootStrap $CustomBootStrap; } - else { SetBootStrap -Path $bootStrapPath; } + $bootStrapPath = '{0}:\BootStrap' -f $vhdDriveLetter; + WriteVerbose ($localized.AddingBootStrapFile -f $bootStrapPath); + if ($CustomBootStrap) { SetBootStrap -Path $bootStrapPath -CustomBootStrap $CustomBootStrap; } + else { SetBootStrap -Path $bootStrapPath; } - $setupCompleteCmdPath = '{0}:\Windows\Setup\Scripts' -f $vhdDriveLetter; - WriteVerbose ($localized.AddingSetupCompleteCmdFile -f $setupCompleteCmdPath); - SetSetupCompleteCmd -Path $setupCompleteCmdPath; + $setupCompleteCmdPath = '{0}:\Windows\Setup\Scripts' -f $vhdDriveLetter; + WriteVerbose ($localized.AddingSetupCompleteCmdFile -f $setupCompleteCmdPath); + SetSetupCompleteCmd -Path $setupCompleteCmdPath; - ## Copy MOF files to \BootStrap\localhost.mof and \BootStrap\localhost.meta.mof - if ($Path) { - $mofPath = Join-Path -Path $Path -ChildPath ('{0}.mof' -f $Name); - $destinationMofPath = Join-Path -Path $bootStrapPath -ChildPath 'localhost.mof'; - WriteVerbose ($localized.AddingDscConfiguration -f $destinationMofPath); - if (-not (Test-Path -Path $mofPath)) { WriteWarning ($localized.CannotLocateMofFileError -f $mofPath); } - else { Copy-Item -Path $mofPath -Destination $destinationMofPath -Force -ErrorAction Stop; } + ## Copy MOF files to \BootStrap\localhost.mof and \BootStrap\localhost.meta.mof + if ($Path) { + $mofPath = Join-Path -Path $Path -ChildPath ('{0}.mof' -f $Name); + $destinationMofPath = Join-Path -Path $bootStrapPath -ChildPath 'localhost.mof'; + WriteVerbose ($localized.AddingDscConfiguration -f $destinationMofPath); + if (-not (Test-Path -Path $mofPath)) { WriteWarning ($localized.CannotLocateMofFileError -f $mofPath); } + else { Copy-Item -Path $mofPath -Destination $destinationMofPath -Force -ErrorAction Stop; } - $metaMofPath = Join-Path -Path $Path -ChildPath ('{0}.meta.mof' -f $Name); - if (Test-Path -Path $metaMofPath -PathType Leaf) { - $destinationMetaMofPath = Join-Path -Path $bootStrapPath -ChildPath 'localhost.meta.mof'; - WriteVerbose ($localized.AddingDscConfiguration -f $destinationMetaMofPath); - Copy-Item -Path $metaMofPath -Destination $destinationMetaMofPath -Force; - } + $metaMofPath = Join-Path -Path $Path -ChildPath ('{0}.meta.mof' -f $Name); + if (Test-Path -Path $metaMofPath -PathType Leaf) { + $destinationMetaMofPath = Join-Path -Path $bootStrapPath -ChildPath 'localhost.meta.mof'; + WriteVerbose ($localized.AddingDscConfiguration -f $destinationMetaMofPath); + Copy-Item -Path $metaMofPath -Destination $destinationMetaMofPath -Force; } + } - ## Copy certificates - if (-not [System.String]::IsNullOrWhitespace($NodeData.ClientCertificatePath)) { - $destinationCertificatePath = Join-Path -Path $bootStrapPath -ChildPath 'LabClient.pfx'; - $expandedClientCertificatePath = [System.Environment]::ExpandEnvironmentVariables($NodeData.ClientCertificatePath); - WriteVerbose ($localized.AddingCertificate -f 'Client', $destinationCertificatePath); - Copy-Item -Path $expandedClientCertificatePath -Destination $destinationCertificatePath -Force; - } - if (-not [System.String]::IsNullOrWhitespace($NodeData.RootCertificatePath)) { - $destinationCertificatePath = Join-Path -Path $bootStrapPath -ChildPath 'LabRoot.cer'; - $expandedRootCertificatePath = [System.Environment]::ExpandEnvironmentVariables($NodeData.RootCertificatePath); - WriteVerbose ($localized.AddingCertificate -f 'Root', $destinationCertificatePath); - Copy-Item -Path $expandedRootCertificatePath -Destination $destinationCertificatePath -Force; - } - } #end if not Quick VM + ## Copy certificates + if (-not [System.String]::IsNullOrWhitespace($NodeData.ClientCertificatePath)) { + $destinationCertificatePath = Join-Path -Path $bootStrapPath -ChildPath 'LabClient.pfx'; + $expandedClientCertificatePath = [System.Environment]::ExpandEnvironmentVariables($NodeData.ClientCertificatePath); + WriteVerbose ($localized.AddingCertificate -f 'Client', $destinationCertificatePath); + Copy-Item -Path $expandedClientCertificatePath -Destination $destinationCertificatePath -Force; + } + if (-not [System.String]::IsNullOrWhitespace($NodeData.RootCertificatePath)) { + $destinationCertificatePath = Join-Path -Path $bootStrapPath -ChildPath 'LabRoot.cer'; + $expandedRootCertificatePath = [System.Environment]::ExpandEnvironmentVariables($NodeData.RootCertificatePath); + WriteVerbose ($localized.AddingCertificate -f 'Root', $destinationCertificatePath); + Copy-Item -Path $expandedRootCertificatePath -Destination $destinationCertificatePath -Force; + } WriteVerbose ($localized.DismountingDiskImage -f $VhdPath); Dismount-Vhd -Path $VhdPath; From 1c5d9dbf5050c624c8acb74ef9849734b992b52a Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Tue, 22 Dec 2015 22:12:00 +0000 Subject: [PATCH 11/23] Formatting changes --- Src/LabMedia.ps1 | 132 +++++++++++++++++++++++++++++++----------- Src/LabResource.ps1 | 44 ++++++++++---- Src/LabSwitch.ps1 | 49 +++++++++++----- Src/LabVMDisk.ps1 | 38 ++++++++---- Src/LabVMSnapshot.ps1 | 37 ++++++++---- 5 files changed, 216 insertions(+), 84 deletions(-) diff --git a/Src/LabMedia.ps1 b/Src/LabMedia.ps1 index abbe4743..f702ede1 100644 --- a/Src/LabMedia.ps1 +++ b/Src/LabMedia.ps1 @@ -7,17 +7,38 @@ function NewLabMedia { #> [CmdletBinding()] param ( - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Id = $(throw ($localized.MissingParameterError -f 'Id')), - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Filename = $(throw ($localized.MissingParameterError -f 'Filename')), - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Description = '', - [Parameter()] [ValidateSet('x86','x64')] [System.String] $Architecture = $(throw ($localized.MissingParameterError -f 'Architecture')), - [Parameter()] [System.String] $ImageName = '', - [Parameter()] [ValidateSet('ISO','VHD')] [System.String] $MediaType = $(throw ($localized.MissingParameterError -f 'MediaType')), - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Uri = $(throw ($localized.MissingParameterError -f 'Uri')), - [Parameter()] [System.String] $Checksum = '', - [Parameter()] [System.String] $ProductKey = '', - [Parameter()] [ValidateNotNull()] [System.Collections.Hashtable] $CustomData = @{}, - [Parameter()] [AllowNull()] [System.Array] $Hotfixes + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Id = $(throw ($localized.MissingParameterError -f 'Id')), + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Filename = $(throw ($localized.MissingParameterError -f 'Filename')), + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Description = '', + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('x86','x64')] + [System.String] $Architecture = $(throw ($localized.MissingParameterError -f 'Architecture')), + + [Parameter(ValueFromPipelineByPropertyName)] + [System.String] $ImageName = '', + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('ISO','VHD')] + [System.String] $MediaType = $(throw ($localized.MissingParameterError -f 'MediaType')), + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Uri = $(throw ($localized.MissingParameterError -f 'Uri')), + + [Parameter(ValueFromPipelineByPropertyName)] + [System.String] $Checksum = '', + + [Parameter(ValueFromPipelineByPropertyName)] + [System.String] $ProductKey = '', + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] + [System.Collections.Hashtable] $CustomData = @{}, + + [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] + [System.Array] $Hotfixes ) begin { ## Confirm we have a valid Uri @@ -63,10 +84,13 @@ function ResolveLabMedia { [CmdletBinding()] param ( ## Media ID - [Parameter(Mandatory)] [System.String] $Id, + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Id, + ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter()] [System.Object] $ConfigurationData + [Parameter(ValueFromPipelineByPropertyName)] + [System.Object] $ConfigurationData ) process { ## If we have configuration data specific instance, return that @@ -108,9 +132,12 @@ function Get-LabMedia { [OutputType([System.Management.Automation.PSCustomObject])] param ( ## Media ID - [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $Id, + [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String] $Id, + ## Only return custom media - [Parameter()] [System.Management.Automation.SwitchParameter] $CustomOnly + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $CustomOnly ) process { ## Retrieve built-in media @@ -159,7 +186,8 @@ function Test-LabMedia { [CmdletBinding()] [OutputType([System.Boolean])] param ( - [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $Id + [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String] $Id ) process { $hostDefaults = GetConfigurationData -Configuration Host; @@ -199,9 +227,12 @@ function InvokeLabMediaImageDownload { [OutputType([System.IO.FileInfo])] param ( ## Lab media object - [Parameter(Mandatory)] [ValidateNotNull()] [System.Object] $Media, + [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] + [System.Object] $Media, + ## Force (re)download of the resource - [Parameter()] [System.Management.Automation.SwitchParameter] $Force + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $Force ) process { $hostDefaults = GetConfigurationData -Configuration Host; @@ -255,10 +286,17 @@ function InvokeLabMediaHotfixDownload { [CmdletBinding()] [OutputType([System.IO.FileInfo])] param ( - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Id, - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Uri, - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Checksum, - [Parameter()] [System.Management.Automation.SwitchParameter] $Force + [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String] $Id, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Uri, + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Checksum, + + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $Force ) process { $hostDefaults = GetConfigurationData -Configuration Host; @@ -287,27 +325,48 @@ function Register-LabMedia { [CmdletBinding()] param ( ## Unique media ID. You can override the built-in media if required. - [Parameter(Mandatory)] [System.String] $Id, + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Id, + ## Media type - [Parameter(Mandatory)] [ValidateSet('VHD','ISO','WIM')] [System.String] $MediaType, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateSet('VHD','ISO','WIM')] + [System.String] $MediaType, + ## The source http/https/file Uri of the source file - [Parameter(Mandatory)] [System.Uri] $Uri, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.Uri] $Uri, + ## Architecture of the source media - [Parameter(Mandatory)] [ValidateSet('x64','x86')] [System.String] $Architecture, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateSet('x64','x86')] + [System.String] $Architecture, + ## Media description - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Description, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Description, + ## ISO/WIM image name - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $ImageName, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $ImageName, + ## Target local filename for the locally cached resource - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Filename, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Filename, + ## MD5 checksum of the resource - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $Checksum, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Checksum, + ## Media custom data - [Parameter()] [ValidateNotNull()] [System.Collections.Hashtable] $CustomData, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] + [System.Collections.Hashtable] $CustomData, + ## Media custom data - [Parameter()] [ValidateNotNull()] [System.Collections.Hashtable[]] $Hotfixes, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] + [System.Collections.Hashtable[]] $Hotfixes, + ## Override existing media entries - [Parameter()] [System.Management.Automation.SwitchParameter] $Force + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $Force ) process { ## Validate ImageName when media type is ISO/WIM @@ -373,9 +432,12 @@ function Unregister-LabMedia { The Unregister-LabMedia cmdlet allows unregistering custom media entries. #> [CmdletBinding(SupportsShouldProcess)] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideDefaultParameterValue', '')] param ( ## Unique media ID. You can override the built-in media if required. - [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $Id + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.String] $Id ) process { ## Get the custom media list @@ -419,4 +481,4 @@ function Reset-LabMedia { RemoveConfigurationData -Configuration CustomMedia; Get-Labmedia; } -} #end function Reset-LabMedia \ No newline at end of file +} #end function Reset-LabMedia diff --git a/Src/LabResource.ps1 b/Src/LabResource.ps1 index 032d1ec8..0c84612d 100644 --- a/Src/LabResource.ps1 +++ b/Src/LabResource.ps1 @@ -6,9 +6,12 @@ function Test-LabResource { param ( ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $ConfigurationData, + [Parameter(Mandatory, ValueFromPipeline)] + [System.Object] $ConfigurationData, + ## Lab resource Id - [Parameter()] [System.String] $ResourceId + [Parameter(ValueFromPipelineByPropertyName)] + [System.String] $ResourceId ) begin { $ConfigurationData = ConvertToConfigurationData -ConfigurationData $ConfigurationData; @@ -45,12 +48,18 @@ function Invoke-LabResourceDownload { [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [System.Object] $ConfigurationData = @{ }, + ## Lab media Id - [Parameter(ValueFromPipelineByPropertyName)] [System.String[]] $MediaId, + [Parameter(ValueFromPipelineByPropertyName)] + [System.String[]] $MediaId, + ## Lab resource Id - [Parameter(ValueFromPipelineByPropertyName)] [System.String[]] $ResourceId, + [Parameter(ValueFromPipelineByPropertyName)] + [System.String[]] $ResourceId, + ## Forces a checksum recalculations and a download if necessary. - [Parameter()] [System.Management.Automation.SwitchParameter] $Force + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $Force ) begin { $ConfigurationData = ConvertToConfigurationData -ConfigurationData $ConfigurationData; @@ -115,9 +124,12 @@ function ResolveLabResource { param ( ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $ConfigurationData, + [Parameter(Mandatory, ValueFromPipeline)] + [System.Object] $ConfigurationData, + ## Lab resource ID - [Parameter(Mandatory)] [System.String] $ResourceId + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.String] $ResourceId ) process { $resource = $ConfigurationData.NonNodeData.($labDefaults.ModuleName).Resource | Where-Object Id -eq $ResourceId; @@ -133,9 +145,12 @@ function ExpandIsoResource { #> param ( ## Source ISO file path - [Parameter(Mandatory)] [System.String] $Path, + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Path, + ## Destination folder path - [Parameter(Mandatory)] [System.String] $DestinationPath + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.String] $DestinationPath ) process { WriteVerbose ($localized.MountingDiskImage -f $Path); @@ -162,11 +177,16 @@ function ExpandLabResource { param ( ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $ConfigurationData, + [Parameter(Mandatory, ValueFromPipeline)] + [System.Object] $ConfigurationData, + ## Lab VM name - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Name, + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Name, + ## Destination mounted VHDX path to expand resources into - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $DestinationPath ) begin { $ConfigurationData = ConvertToConfigurationData -ConfigurationData $ConfigurationData; diff --git a/Src/LabSwitch.ps1 b/Src/LabSwitch.ps1 index 20bf574f..6278e576 100644 --- a/Src/LabSwitch.ps1 +++ b/Src/LabSwitch.ps1 @@ -6,13 +6,22 @@ function NewLabSwitch { Permits validation of custom NonNodeData\VirtualEngineLab\Network entries. #> [CmdletBinding()] - [OutputType([System.Management.Automation.PSCustomObject])] + [OutputType([System.Collections.Hashtable])] param ( - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Name, - [Parameter(Mandatory)] [ValidateSet('Internal','External','Private')] [System.String] $Type, - [Parameter()] [ValidateNotNull()] [System.String] $NetAdapterName, - [Parameter()] [ValidateNotNull()] [System.Boolean] $AllowManagementOS = $false, - [Parameter()] [ValidateSet('Present','Absent')] [System.String] $Ensure = 'Present' + [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String] $Name, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateSet('Internal','External','Private')] + [System.String] $Type, + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] + [System.String] $NetAdapterName, + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] + [System.Boolean] $AllowManagementOS = $false, + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('Present','Absent')] + [System.String] $Ensure = 'Present' ) begin { if (($Type -eq 'External') -and (-not $NetAdapterName)) { @@ -44,10 +53,13 @@ function ResolveLabSwitch { [OutputType([System.Collections.Hashtable])] param ( ## Switch Id/Name - [Parameter(Mandatory)] [System.String] $Name, + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $ConfigurationData + [Parameter(Mandatory, ValueFromPipeline)] + [System.Object] $ConfigurationData ) begin { $ConfigurationData = ConvertToConfigurationData -ConfigurationData $ConfigurationData; @@ -81,10 +93,13 @@ function TestLabSwitch { [OutputType([System.Boolean])] param ( ## Switch Id/Name - [Parameter(Mandatory)] [System.String] $Name, + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $ConfigurationData + [Parameter(Mandatory, ValueFromPipeline)] + [System.Object] $ConfigurationData ) begin { $ConfigurationData = ConvertToConfigurationData -ConfigurationData $ConfigurationData; @@ -106,10 +121,13 @@ function SetLabSwitch { [CmdletBinding()] param ( ## Switch Id/Name - [Parameter(Mandatory)] [System.String] $Name, + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $ConfigurationData + [Parameter(Mandatory, ValueFromPipeline)] + [System.Object] $ConfigurationData ) begin { $ConfigurationData = ConvertToConfigurationData -ConfigurationData $ConfigurationData; @@ -131,10 +149,13 @@ function RemoveLabSwitch { [CmdletBinding()] param ( ## Switch Id/Name - [Parameter(Mandatory)] [System.String] $Name, + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + ## Lab DSC configuration data [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] - [Parameter(Mandatory, ValueFromPipeline)] [System.Object] $ConfigurationData + [Parameter(Mandatory, ValueFromPipeline)] + [System.Object] $ConfigurationData ) begin { $ConfigurationData = ConvertToConfigurationData -ConfigurationData $ConfigurationData; diff --git a/Src/LabVMDisk.ps1 b/Src/LabVMDisk.ps1 index 2622c622..be962d9a 100644 --- a/Src/LabVMDisk.ps1 +++ b/Src/LabVMDisk.ps1 @@ -4,7 +4,8 @@ function ResolveLabVMDiskPath { Resolves the specified VM name to it's target VHDX path. #> param ( - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $Name + [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String] $Name ) process { $hostDefaults = GetConfigurationData -Configuration Host; @@ -23,8 +24,11 @@ function GetLabVMDisk { #> [CmdletBinding()] param ( - [Parameter(Mandatory)] [System.String] $Name, - [Parameter(Mandatory)] [System.String] $Media + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.String] $Media ) process { $hostDefaults = GetConfigurationData -Configuration Host; @@ -47,8 +51,11 @@ function TestLabVMDisk { #> [CmdletBinding()] param ( - [Parameter(Mandatory)] [System.String] $Name, - [Parameter(Mandatory)] [System.String] $Media + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.String] $Media ) process { $hostDefaults = GetConfigurationData -Configuration Host; @@ -73,8 +80,11 @@ function SetLabVMDisk { #> [CmdletBinding()] param ( - [Parameter(Mandatory)] [System.String] $Name, - [Parameter(Mandatory)] [System.String] $Media + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.String] $Media ) process { $hostDefaults = GetConfigurationData -Configuration Host; @@ -99,8 +109,11 @@ function RemoveLabVMDisk { #> [CmdletBinding()] param ( - [Parameter(Mandatory)] [System.String] $Name, - [Parameter(Mandatory)] [System.String] $Media + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.String] $Media ) process { $hostDefaults = GetConfigurationData -Configuration Host; @@ -131,8 +144,11 @@ function ResetLabVMDisk { #> [CmdletBinding()] param ( - [Parameter(Mandatory)] [System.String] $Name, - [Parameter(Mandatory)] [System.String] $Media + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Name, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [System.String] $Media ) process { RemoveLabVMSnapshot -Name $Name; diff --git a/Src/LabVMSnapshot.ps1 b/Src/LabVMSnapshot.ps1 index c95ccb88..d558f5c2 100644 --- a/Src/LabVMSnapshot.ps1 +++ b/Src/LabVMSnapshot.ps1 @@ -5,8 +5,11 @@ function RemoveLabVMSnapshot { #> [CmdletBinding()] param ( - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, - [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $SnapshotName = '*' + [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String[]] $Name, + + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $SnapshotName = '*' ) process { <## TODO: Add the ability to force/wait for the snapshots to be removed. When removing snapshots it take a minute @@ -14,10 +17,14 @@ function RemoveLabVMSnapshot { foreach ($vmName in $Name) { # Sort by descending CreationTime to ensure we will not have to commit changes from one snapshot to another - Get-VMSnapshot -VMName $vmName -ErrorAction SilentlyContinue | Where-Object Name -like $SnapshotName | Sort-Object -Property CreationTime -Descending | ForEach-Object { - WriteVerbose ($localized.RemovingSnapshot -f $vmName, $_.Name); - Remove-VMSnapshot -VMName $_.VMName -Name $_.Name; - } + Get-VMSnapshot -VMName $vmName -ErrorAction SilentlyContinue | + Where-Object Name -like $SnapshotName | + Sort-Object -Property CreationTime -Descending | + ForEach-Object { + WriteVerbose -Message ($localized.RemovingSnapshot -f $vmName, $_.Name); + Remove-VMSnapshot -VMName $_.VMName -Name $_.Name -Confirm:$false; + } + } #end foreach VM } #end process } #end function RemoveVMSnapshot @@ -29,12 +36,15 @@ function NewLabVMSnapshot { #> [CmdletBinding()] param ( - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $SnapshotName + [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String[]] $Name, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $SnapshotName ) process { foreach ($vmName in $Name) { - WriteVerbose ($localized.SnapshottingVirtualMachine -f $vmName, $SnapshotName); + WriteVerbose -Message ($localized.SnapshottingVirtualMachine -f $vmName, $SnapshotName); Checkpoint-VM -VMName $vmName -SnapshotName $SnapshotName; } #end foreach VM } #end process @@ -47,14 +57,17 @@ function GetLabVMSnapshot { #> [CmdletBinding()] param ( - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String[]] $Name, - [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.String] $SnapshotName + [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] + [System.String[]] $Name, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $SnapshotName ) process { foreach ($vmName in $Name) { $snapshot = Get-VMSnapshot -VMName $vmName -Name $SnapshotName -ErrorAction SilentlyContinue; if (-not $snapshot) { - WriteWarning ($localized.SnapshotMissingWarning -f $SnapshotName, $vmName); + WriteWarning -Message ($localized.SnapshotMissingWarning -f $SnapshotName, $vmName); } else { Write-Output -InputObject $snapshot; From 2468effe6ef390554cf7fa853ec3276d2967ec49 Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Tue, 22 Dec 2015 22:15:07 +0000 Subject: [PATCH 12/23] Adds CustomBootstrapOrder and ShouldProcess attribute to Set-LabVMDefault --- Config/VMDefaults.json | 3 +- Src/LabVMDefaults.ps1 | 98 +++++++++++++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/Config/VMDefaults.json b/Config/VMDefaults.json index 2475fc18..5dcb9fcc 100644 --- a/Config/VMDefaults.json +++ b/Config/VMDefaults.json @@ -16,5 +16,6 @@ "ClientCertificatePath": "%ALLUSERSPROFILE%\\VirtualEngineLab\\Certificates\\LabClient.pfx", "RootCertificatePath": "%ALLUSERSPROFILE%\\VirtualEngineLab\\Certificates\\LabRoot.cer", "BootOrder": 99, - "BootDelay": 0 + "BootDelay": 0, + "CustomBootstrapOrder": "MediaFirst" } diff --git a/Src/LabVMDefaults.ps1 b/Src/LabVMDefaults.ps1 index e397d3c3..00982caf 100644 --- a/Src/LabVMDefaults.ps1 +++ b/Src/LabVMDefaults.ps1 @@ -22,10 +22,16 @@ function Get-LabVMDefault { [OutputType([System.Management.Automation.PSCustomObject])] param ( ) process { - $labDefaults = GetConfigurationData -Configuration VM; + $vmDefaults = GetConfigurationData -Configuration VM; + + ## This property may not be present in the original VM default file + if ($vmDefaults.PSObject.Properties.Name -notcontains 'CustomBootstrapOrder') { + [ref] $null = Add-Member -InputObject $vmDefaults -MemberType NoteProperty -Name 'CustomBootstrapOrder' -Value 'MediaFirst'; + } + ## BootOrder property should not be exposed via the Get-LabVMDefault/Set-LabVMDefault - $labDefaults.PSObject.Properties.Remove('BootOrder'); - return $labDefaults; + $vmDefaults.PSObject.Properties.Remove('BootOrder'); + return $vmDefaults; } } #end function Get-LabVMDefault New-Alias -Name Get-LabVMDefaults -Value Get-LabVMDefault @@ -35,44 +41,86 @@ function Set-LabVMDefault { .SYNOPSIS Sets the lab virtual machine default settings. #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Management.Automation.PSCustomObject])] param ( ## Default virtual machine startup memory (bytes). - [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $StartupMemory, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] + [System.Int64] $StartupMemory, + ## Default virtual machine miniumum dynamic memory allocation (bytes). - [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $MinimumMemory, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] + [System.Int64] $MinimumMemory, + ## Default virtual machine maximum dynamic memory allocation (bytes). - [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] [System.Int64] $MaximumMemory, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(536870912, 1099511627776)] + [System.Int64] $MaximumMemory, + ## Default virtual machine processor count. - [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(1, 4)] [System.Int32] $ProcessorCount, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(1, 4)] + [System.Int32] $ProcessorCount, + ## Default virtual machine media Id. - [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Media, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Media, + ## Lab host internal switch name. - [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $SwitchName, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $SwitchName, + # Input Locale - [Parameter(ValueFromPipelineByPropertyName)] [ValidatePattern('^([a-z]{2,2}-[a-z]{2,2})|(\d{4,4}:\d{8,8})$')] [System.String] $InputLocale, + [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, + [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, + [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, + [Parameter(ValueFromPipelineByPropertyName)] [ValidatePattern('^[a-z]{2,2}-[a-z]{2,2}$')] + [System.String] $UILanguage, + # Timezone - [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Timezone, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $Timezone, + # Registered Owner - [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $RegisteredOwner, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] $RegisteredOwner, + # Registered Organization - [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] [Alias('RegisteredOrganisation')] $RegisteredOrganization, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String] [Alias('RegisteredOrganisation')] $RegisteredOrganization, + ## Client PFX certificate bundle used to encrypt DSC credentials - [Parameter()] [ValidateNotNull()] [System.String] $ClientCertificatePath, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] + [System.String] $ClientCertificatePath, + ## Client certificate's issuing Root Certificate Authority (CA) certificate - [Parameter()] [ValidateNotNull()] [System.String] $RootCertificatePath, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] + [System.String] $RootCertificatePath, + ## Boot delay/pause between VM operations - [Parameter()] [System.UInt16] $BootDelay + [Parameter(ValueFromPipelineByPropertyName)] + [System.UInt16] $BootDelay, + + ## Custom bootstrap order + [Parameter(ValueFromPipelineByPropertyName)] + [ValidateSet('ConfigurationFirst','ConfigurationOnly','Disabled','MediaFirst','MediaOnly')] + [System.String] $CustomBootstrapOrder = 'MediaFirst' ) process { $vmDefaults = GetConfigurationData -Configuration VM; + + ## This property may not be present in the original VM default file + if ($vmDefaults.PSObject.Properties.Name -notcontains 'CustomBootstrapOrder') { + [ref] $null = Add-Member -InputObject $vmDefaults -MemberType NoteProperty -Name 'CustomBootstrapOrder' -Value $CustomBootstrapOrder; + } + if ($PSBoundParameters.ContainsKey('StartupMemory')) { $vmDefaults.StartupMemory = $StartupMemory; } @@ -133,6 +181,9 @@ function Set-LabVMDefault { if ($PSBoundParameters.ContainsKey('BootDelay')) { $vmDefaults.BootDelay = $BootDelay; } + if ($PSBoundParameters.ContainsKey('CustomBootstrapOrder')) { + $vmDefaults.CustomBootstrapOrder = $CustomBootstrapOrder; + } if ($vmDefaults.StartupMemory -lt $vmDefaults.MinimumMemory) { throw ($localized.StartMemLessThanMinMemError -f $vmDefaults.StartupMemory, $vmDefaults.MinimumMemory); @@ -141,7 +192,12 @@ function Set-LabVMDefault { throw ($localized.StartMemGreaterThanMaxMemError -f $vmDefaults.StartupMemory, $vmDefaults.MaximumMemory); } - SetConfigurationData -Configuration VM -InputObject $vmDefaults; + $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'Set-LabVMDefault', $vmName; + $verboseProcessMessage = $localized.SettingVMDefaults; + if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { + SetConfigurationData -Configuration VM -InputObject $vmDefaults; + } + ## BootOrder property should not be exposed via the Get-LabVMDefault/Set-LabVMDefault $vmDefaults.PSObject.Properties.Remove('BootOrder'); return $vmDefaults; From 4addd01e7fb93920eeacd7e4598309053b1b6b6b Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Tue, 22 Dec 2015 22:16:37 +0000 Subject: [PATCH 13/23] Adds CustomBootstrap for bundled client OS media --- Config/Media.json | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/Config/Media.json b/Config/Media.json index b10f7ba6..a4986793 100644 --- a/Config/Media.json +++ b/Config/Media.json @@ -236,7 +236,14 @@ "Uri": "http://download.microsoft.com/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X64FREE_EN-US_DV9.ISO", "Checksum": "EE63618E3BE220D86B993C1ABBCF32EB", "CustomData": { - "WindowsOptionalFeature": ["NetFx3"] + "WindowsOptionalFeature": ["NetFx3"], + "CustomBootstrap": [ + "## Unattend.xml will set the Administrator password, but it won't enable the account on client OSes", + "NET USER Administrator /active:yes;", + "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force;", + "## Kick-start PowerShell remoting on clients to permit applying DSC configurations", + "Enable-PSRemoting -SkipNetworkProfileCheck -Force;" + ] }, "Hotfixes": [ { @@ -279,7 +286,14 @@ "Uri": "http://download.microsoft.com/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X86FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X86FREE_EN-US_DV9.ISO", "Checksum": "B2ACCD5F135C3EEDE256D398856AEEAD", "CustomData": { - "WindowsOptionalFeature": ["NetFx3"] + "WindowsOptionalFeature": ["NetFx3"], + "CustomBootstrap": [ + "## Unattend.xml will set the Administrator password, but it won't enable the account on client OSes", + "NET USER Administrator /active:yes;", + "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force;", + "## Kick-start PowerShell remoting on clients to permit applying DSC configurations", + "Enable-PSRemoting -SkipNetworkProfileCheck -Force;" + ] }, "Hotfixes": [ { @@ -318,7 +332,14 @@ "Uri": "http://download.microsoft.com/download/C/3/9/C399EEA8-135D-4207-92C9-6AAB3259F6EF/10240.16384.150709-1700.TH1_CLIENTENTERPRISEEVAL_OEMRET_X64FRE_EN-US.ISO", "Checksum": "6CD2F47F2C32FAA7BE85F1DC81AF3220", "CustomData": { - "WindowsOptionalFeature": ["NetFx3"] + "WindowsOptionalFeature": ["NetFx3"], + "CustomBootstrap": [ + "## Unattend.xml will set the Administrator password, but it won't enable the account on client OSes", + "NET USER Administrator /active:yes;", + "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force;", + "## Kick-start PowerShell remoting on clients to permit applying DSC configurations", + "Enable-PSRemoting -SkipNetworkProfileCheck -Force;" + ] }, "Hotfixes": [ ] }, @@ -332,7 +353,14 @@ "Uri": "http://download.microsoft.com/download/C/3/9/C399EEA8-135D-4207-92C9-6AAB3259F6EF/10240.16384.150709-1700.TH1_CLIENTENTERPRISEEVAL_OEMRET_X86FRE_EN-US.ISO", "Checksum": "5531E6EE40A69E22A7386864A9087CDD", "CustomData": { - "WindowsOptionalFeature": ["NetFx3"] + "WindowsOptionalFeature": ["NetFx3"], + "CustomBootstrap": [ + "## Unattend.xml will set the Administrator password, but it won't enable the account on client OSes", + "NET USER Administrator /active:yes;", + "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force;", + "## Kick-start PowerShell remoting on clients to permit applying DSC configurations", + "Enable-PSRemoting -SkipNetworkProfileCheck -Force;" + ] }, "Hotfixes": [ ] }] From e99169dbb47c93b0e00539f3c27de108c9e76bc9 Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Tue, 22 Dec 2015 22:17:56 +0000 Subject: [PATCH 14/23] Adds ShouldProcess to New-LabImage Misc updates --- Lib/ConfigurationData.ps1 | 2 +- Resources.psd1 | 1 + Src/LabImage.ps1 | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/ConfigurationData.ps1 b/Lib/ConfigurationData.ps1 index 4d7a7cde..6729eb9b 100644 --- a/Lib/ConfigurationData.ps1 +++ b/Lib/ConfigurationData.ps1 @@ -95,7 +95,7 @@ function SetConfigurationData { $configurationPath = ResolveConfigurationDataPath -Configuration $Configuration; $expandedPath = [System.Environment]::ExpandEnvironmentVariables($configurationPath); [ref] $null = NewDirectory -Path (Split-Path -Path $expandedPath -Parent); - Set-Content -Path $expandedPath -Value (ConvertTo-Json -InputObject $InputObject) -Force; + Set-Content -Path $expandedPath -Value (ConvertTo-Json -InputObject $InputObject) -Force -Confirm:$false; } } #end function SetConfigurationData diff --git a/Resources.psd1 b/Resources.psd1 index 5b55c2fc..26700e2a 100644 --- a/Resources.psd1 +++ b/Resources.psd1 @@ -95,6 +95,7 @@ ConvertFrom-StringData -StringData @' RemovingCustomMediaEntry = Removing '{0}' media entry. SavingConfiguration = Saving configuration '{0}'. PerformingOperationOnTarget = Performing the operation '{0}' on target '{1}'. + SettingVMDefaults = Setting VM defaults. ResettingConfigurationDefaults = Resetting '{0}' configuration settings to default. LocatingWimImageName = Locating WIM image '{0}' name. LocatingWimImageIndex = Locating WIM image '{0}' index. diff --git a/Src/LabImage.ps1 b/Src/LabImage.ps1 index 602505d9..29300ebd 100644 --- a/Src/LabImage.ps1 +++ b/Src/LabImage.ps1 @@ -55,7 +55,7 @@ function New-LabImage { .SYNOPSIS Creates a new master/parent image. #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] [OutputType([System.IO.FileInfo])] param ( ## Lab media Id @@ -80,6 +80,8 @@ function New-LabImage { process { ## Download media if required.. [ref] $null = $PSBoundParameters.Remove('Force'); + [ref] $null = $PSBoundParameters.Remove('WhatIf'); + [ref] $null = $PSBoundParameters.Remove('Confirm'); $media = ResolveLabMedia @PSBoundParameters; $mediaFileInfo = InvokeLabMediaImageDownload -Media $media; @@ -105,7 +107,7 @@ function New-LabImage { } ## Create disk image and refresh PSDrives - $image = NewDiskImage -Path $imagePath -PartitionStyle $partitionStyle -Passthru -Force # -ErrorAction Stop; + $image = NewDiskImage -Path $imagePath -PartitionStyle $partitionStyle -Passthru -Force -ErrorAction Stop; [ref] $null = Get-PSDrive; ## Apply WIM (ExpandWindowsImage) and add specified features From 74c7f2bfa682f619bd16d194ef7a9f3ba572a5a5 Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Tue, 22 Dec 2015 22:24:01 +0000 Subject: [PATCH 15/23] Adds CustomData to New-LabVM --- Src/LabVM.ps1 | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index 27a25a5c..d5a66990 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -284,7 +284,10 @@ function NewLabVM { Credential = $Credential; } if ($node.CustomBootStrap) { - $setLabVMDiskFileParams['CustomBootStrap'] = ($node.CustomBootStrap).ToString(); + $setLabVMDiskFileParams['CustomBootstrap'] = ($node.CustomBootstrap).ToString(); + } + if ($node.CustomBootStrapOrder) { + $setLabVMDiskFileParams['CustomBootstrapOrder'] = ($node.CustomBootstrapOrder).ToString(); } SetLabVMDiskFile @setLabVMDiskFileParams; @@ -493,10 +496,16 @@ function New-LabVM { [System.Security.SecureString] $Password, ## Virtual machine switch name. - [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String[]] $SwitchName, + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] + [System.String[]] $SwitchName, + + ## Custom data + [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] + [System.Collections.Hashtable] $CustomData, ## Skip creating baseline snapshots - [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $NoSnapshot + [Parameter(ValueFromPipelineByPropertyName)] + [System.Management.Automation.SwitchParameter] $NoSnapshot ) DynamicParam { ## Adds a dynamic -Id parameter that returns the registered media Ids @@ -530,25 +539,33 @@ function New-LabVM { } #end begin process { foreach ($vmName in $Name) { - ## Create a skelton config data - $skeletonConfigurationData = @{ - AllNodes = @( - @{ NodeName = $vmName; "$($labDefaults.ModuleName)_Media" = $PSBoundParameters.MediaId; } - ) - }; + ## Create a skelton node configuration + $configurationNode = @{ Nodename = $vmName; }; + ## Add all -CustomData keys/values to the skeleton configuration + if ($CustomData) { + foreach ($key in $CustomData.Keys) { + $configurationNode[$key] = $CustomData.$key; + } + } + + ## Explicitly defined parameters override any -CustomData $parameterNames = @('StartupMemory','MinimumMemory','MaximumMemory','SwitchName','Timezone','UILanguage', 'ProcessorCount','InputLocale','SystemLocale','UserLocale','RegisteredOwner','RegisteredOrganization') foreach ($key in $parameterNames) { if ($PSBoundParameters.ContainsKey($key)) { - $configurationName = '{0}_{1}' -f $labDefaults.ModuleName, $key; - [ref] $null = $skeletonConfigurationData.AllNodes[0].Add($configurationname, $PSBoundParameters.$key); + $configurationNode[$key] = $PSBoundParameters.$key; } } + + ## Ensure the specified MediaId is applied after any CustomData media entry! + $configurationNode['Media'] = $PSBoundParameters.MediaId; + $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'New-LabVM', $vmName; $verboseProcessMessage = $localized.CreatingQuickVM -f $vmName, $PSBoundParameters.MediaId; if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { - NewLabVM -Name $vmName -ConfigurationData $skeletonConfigurationData -Credential $Credential -NoSnapshot:$NoSnapshot -IsQuickVM; + $configurationData = @{ AllNodes = @( $configurationNode ) }; + NewLabVM -Name $vmName -ConfigurationData $configurationData -Credential $Credential -NoSnapshot:$NoSnapshot -IsQuickVM; } } #end foreach name } #end process From d2a10cc202800617e41510757c8aaeef2d255532 Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Tue, 22 Dec 2015 22:24:39 +0000 Subject: [PATCH 16/23] Adds CustomBootstrapOrder property to SetLabVMDiskFile --- Src/LabVMDiskFile.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Src/LabVMDiskFile.ps1 b/Src/LabVMDiskFile.ps1 index 3b00ec30..5bbf7c70 100644 --- a/Src/LabVMDiskFile.ps1 +++ b/Src/LabVMDiskFile.ps1 @@ -83,7 +83,12 @@ function SetLabVMDiskFile { ## Custom bootstrap script [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] - [System.String] $CustomBootStrap + [System.String] $CustomBootstrap, + + ## Custom bootstrap order + [Parameter(ValueFromPipelineByPropertyName)] + [ValidateSet('ConfigurationFirst','ConfigurationOnly','Disabled','MediaFirst','MediaOnly')] + [System.String] $CustomBootstrapOrder = 'MediaFirst' ) process { ## Temporarily disable Windows Explorer popup disk initialization and format notifications From 142e9036da24defa06656d368b098773211cffe4 Mon Sep 17 00:00:00 2001 From: Iain Brighton Date: Wed, 30 Dec 2015 15:19:23 +0000 Subject: [PATCH 17/23] Adds ResolveCustomBootstrap function Adds automatic implementation of CustomBootstrapOrder in Get-LabVMDefault --- Lib/BootStrap.ps1 | 55 +++++++++++++++++++++++++++++++++++ Lib/ConfigurationData.ps1 | 17 +++++++++-- Src/LabVM.ps1 | 30 ++++++++++--------- Src/LabVMDefaults.ps1 | 5 ---- Src/LabVMDiskFile.ps1 | 5 ---- Tests/Lib/BootStrap.Tests.ps1 | 49 +++++++++++++++++++++++++++++++ Tests/Src/LabVM.Tests.ps1 | 4 +-- en-US/about_Media.help.txt | 1 + 8 files changed, 139 insertions(+), 27 deletions(-) diff --git a/Lib/BootStrap.ps1 b/Lib/BootStrap.ps1 index 5ff925d4..97e2544a 100644 --- a/Lib/BootStrap.ps1 +++ b/Lib/BootStrap.ps1 @@ -144,3 +144,58 @@ function SetBootStrap { Set-Content -Path $bootStrapPath -Value $bootStrap -Encoding UTF8 -Force; } #end process } #end function SetBootStrap + +function ResolveCustomBootStrap { +<# + .SYNOPSIS + Resolves the media and node custom bootstrap, using the specified CustomBootstrapOrder +#> + [CmdletBinding()] + [OutputType([System.String])] + param ( + ## Custom bootstrap order + [Parameter(Mandatory, ValueFromPipeline)] + [ValidateSet('ConfigurationFirst','ConfigurationOnly','Disabled','MediaFirst','MediaOnly')] + [System.String] $CustomBootstrapOrder, + + ## Node/configuration custom bootstrap script + [Parameter(ValueFromPipelineByPropertyName)] + [AllowNull()] [System.String] $ConfigurationCustomBootStrap, + + ## Media custom bootstrap script + [Parameter(ValueFromPipelineByPropertyName)] + [AllowNull()] [System.String[]] $MediaCustomBootStrap + ) + begin { + if ([System.String]::IsNullOrWhiteSpace($ConfigurationCustomBootStrap)) { + $ConfigurationCustomBootStrap = ""; + } + ## Convert the string[] into a multi-line string + if ($MediaCustomBootstrap) { + $mediaBootstrap = [System.String]::Join("`r`n", $MediaCustomBootStrap); + } + else { + $mediaBootstrap = ""; + } + } #end begin + process { + switch ($CustomBootstrapOrder) { + 'ConfigurationFirst' { + $bootStrap = "{0}`r`n{1}" -f $ConfigurationCustomBootStrap, $mediaBootstrap; + } + 'ConfigurationOnly' { + $bootStrap = $ConfigurationCustomBootStrap; + } + 'MediaFirst' { + $bootStrap = "{0}`r`n{1}" -f $mediaBootstrap, $ConfigurationCustomBootStrap; + } + 'MediaOnly' { + $bootStrap = $mediaBootstrap; + } + Default { + #Disabled + } + } #end switch + return $bootStrap; + } #end process +} #end function ResolveCustomBootStrap diff --git a/Lib/ConfigurationData.ps1 b/Lib/ConfigurationData.ps1 index 6729eb9b..e37cef8b 100644 --- a/Lib/ConfigurationData.ps1 +++ b/Lib/ConfigurationData.ps1 @@ -75,9 +75,22 @@ function GetConfigurationData { $configurationPath = ResolveConfigurationDataPath -Configuration $Configuration -IncludeDefaultPath; $expandedPath = [System.Environment]::ExpandEnvironmentVariables($configurationPath); if (Test-Path -Path $expandedPath) { - return Get-Content -Path $expandedPath -Raw | ConvertFrom-Json; + $configurationData = Get-Content -Path $expandedPath -Raw | ConvertFrom-Json; + + switch ($Configuration) { + 'VM' { + ## This property may not be present in the original VM default file TODO: Could be deprecated in the future + if ($configurationData.PSObject.Properties.Name -notcontains 'CustomBootstrapOrder') { + [ref] $null = Add-Member -InputObject $configurationData -MemberType NoteProperty -Name 'CustomBootstrapOrder' -Value 'MediaFirst'; + } + } + Default { + ## Do nothing + } + } #end switch + return $configurationData; } - } + } #end process } #end function GetConfigurationData function SetConfigurationData { diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index 2365c546..2f0d225c 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -249,11 +249,11 @@ function NewLabVM { WriteVerbose ($localized.SettingVMConfiguration -f 'Virtual Switch', $node.SwitchName); SetLabSwitch -Name $node.SwitchName -ConfigurationData $ConfigurationData; } #end if not quick VM - + if (-not (Test-LabImage -Id $node.Media)) { [ref] $null = New-LabImage -Id $node.Media -ConfigurationData $ConfigurationData; } - + WriteVerbose ($localized.ResettingVMConfiguration -f 'VHDX', "$Name.vhdx"); ResetLabVMDisk -Name $Name -Media $node.Media -ErrorAction Stop; @@ -285,11 +285,15 @@ function NewLabVM { Credential = $Credential; CoreCLR = $media.CustomData.SetupComplete -eq 'CoreCLR'; } - if ($node.CustomBootStrap) { - $setLabVMDiskFileParams['CustomBootstrap'] = ($node.CustomBootstrap).ToString(); + + $resolveCustomBootStrapParams = @{ + CustomBootstrapOrder = $node.CustomBootstrapOrder; + ConfigurationCustomBootstrap = $node.CustomBootstrap; + MediaCustomBootStrap = $media.CustomData.CustomBootstrap; } - if ($node.CustomBootStrapOrder) { - $setLabVMDiskFileParams['CustomBootstrapOrder'] = ($node.CustomBootstrapOrder).ToString(); + $customBootstrap = ResolveCustomBootStrap @resolveCustomBootStrapParams; + if ($customBootstrap) { + $setLabVMDiskFileParams['CustomBootstrap'] = $customBootstrap; } SetLabVMDiskFile @setLabVMDiskFileParams; @@ -313,10 +317,10 @@ function NewLabVM { } #end function NewLabVM function RemoveLabVM { - <# - .SYNOPSIS - Deletes a lab virtual machine. - #> +<# + .SYNOPSIS + Deletes a lab virtual machine. +#> [CmdletBinding()] param ( ## Lab VM/Node name @@ -419,7 +423,7 @@ function Reset-LabVM { RemoveLabVM -Name $vmName -ConfigurationData $ConfigurationData; NewLabVM -Name $vmName -ConfigurationData $ConfigurationData -Path $Path -NoSnapshot:$NoSnapshot -Credential $Credential; } #end if should process - } #end foreach VM + } #end foreach VMd } #end process } #end function Reset-LabVM @@ -510,7 +514,7 @@ function New-LabVM { [System.Management.Automation.SwitchParameter] $NoSnapshot ) DynamicParam { - ## Adds a dynamic -Id parameter that returns the registered media Ids + ## 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; @@ -589,7 +593,7 @@ function Remove-LabVM { $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'Remove-LabVM', $vmName; $verboseProcessMessage = $localized.RemovingQuickVM -f $vmName; if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { - ## Create a skelton config data + ## Create a skeleton config data $skeletonConfigurationData = @{ AllNodes = @( @{ NodeName = $vmName; } diff --git a/Src/LabVMDefaults.ps1 b/Src/LabVMDefaults.ps1 index 3cb42cdc..61785813 100644 --- a/Src/LabVMDefaults.ps1 +++ b/Src/LabVMDefaults.ps1 @@ -24,11 +24,6 @@ function Get-LabVMDefault { process { $vmDefaults = GetConfigurationData -Configuration VM; - ## This property may not be present in the original VM default file - if ($vmDefaults.PSObject.Properties.Name -notcontains 'CustomBootstrapOrder') { - [ref] $null = Add-Member -InputObject $vmDefaults -MemberType NoteProperty -Name 'CustomBootstrapOrder' -Value 'MediaFirst'; - } - ## BootOrder property should not be exposed via the Get-LabVMDefault/Set-LabVMDefault $vmDefaults.PSObject.Properties.Remove('BootOrder'); return $vmDefaults; diff --git a/Src/LabVMDiskFile.ps1 b/Src/LabVMDiskFile.ps1 index 721e08ad..5b9020dc 100644 --- a/Src/LabVMDiskFile.ps1 +++ b/Src/LabVMDiskFile.ps1 @@ -85,11 +85,6 @@ function SetLabVMDiskFile { [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $CustomBootstrap, - ## Custom bootstrap order - [Parameter(ValueFromPipelineByPropertyName)] - [ValidateSet('ConfigurationFirst','ConfigurationOnly','Disabled','MediaFirst','MediaOnly')] - [System.String] $CustomBootstrapOrder = 'MediaFirst', - ## CoreCLR [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $CoreCLR diff --git a/Tests/Lib/BootStrap.Tests.ps1 b/Tests/Lib/BootStrap.Tests.ps1 index 81fd645c..51bf774c 100644 --- a/Tests/Lib/BootStrap.Tests.ps1 +++ b/Tests/Lib/BootStrap.Tests.ps1 @@ -84,6 +84,55 @@ Describe 'BootStrap' { } #end context Validates "SetBootStrap" method + Context 'Validates "ResolveCustomBootStrap" method' { + + It 'Returns empty string when "CustomBootStrapOrder" = "Disabled"' { + $configurationBootstrap = 'Configuration'; + $mediaBootstrap = 'Media'; + + $bootstrap = ResolveCustomBootstrap -CustomBootstrapOrder Disabled -ConfigurationCustomBootstrap $configurationBootstrap -MediaCustomBootstrap $mediaBootstrap; + + $bootstrap | Should BeNullOrEmpty; + } + + It 'Returns configuration bootstrap when "CustomBootStrapOrder" = "ConfigurationOnly"' { + $configurationBootstrap = 'Configuration'; + $mediaBootstrap = 'Media'; + + $bootstrap = ResolveCustomBootstrap -CustomBootstrapOrder ConfigurationOnly -ConfigurationCustomBootstrap $configurationBootstrap -MediaCustomBootstrap $mediaBootstrap; + + $bootstrap | Should Be $configurationBootstrap; + } + + It 'Returns media bootstrap when "CustomBootStrapOrder" = "MediaOnly"' { + $configurationBootstrap = 'Configuration'; + $mediaBootstrap = 'Media'; + + $bootstrap = ResolveCustomBootstrap -CustomBootstrapOrder MediaOnly -ConfigurationCustomBootstrap $configurationBootstrap -MediaCustomBootstrap $mediaBootstrap; + + $bootstrap | Should Be $mediaBootstrap; + } + + It 'Returns configuration bootstrap first when "CustomBootStrapOrder" = "ConfigurationFirst"' { + $configurationBootstrap = 'Configuration'; + $mediaBootstrap = 'Media'; + + $bootstrap = ResolveCustomBootstrap -CustomBootstrapOrder ConfigurationFirst -ConfigurationCustomBootstrap $configurationBootstrap -MediaCustomBootstrap $mediaBootstrap; + + $bootstrap | Should Be "$configurationBootStrap`r`n$mediaBootstrap"; + } + + It 'Returns media bootstrap first when "CustomBootStrapOrder" = "MediaFirst"' { + $configurationBootstrap = 'Configuration'; + $mediaBootstrap = 'Media'; + + $bootstrap = ResolveCustomBootstrap -CustomBootstrapOrder MediaFirst -ConfigurationCustomBootstrap $configurationBootstrap -MediaCustomBootstrap $mediaBootstrap; + + $bootstrap | Should Be "$mediaBootstrap`r`n$configurationBootStrap"; + } + + } #end context Validates "ResolveCustomBootStrap" method + } #end InModuleScope } #end describe Bootstrap diff --git a/Tests/Src/LabVM.Tests.ps1 b/Tests/Src/LabVM.Tests.ps1 index dd68adcb..2e40deaa 100644 --- a/Tests/Src/LabVM.Tests.ps1 +++ b/Tests/Src/LabVM.Tests.ps1 @@ -465,11 +465,11 @@ Describe 'LabVM' { Mock ResetLabVMDisk -MockWith { } Mock SetLabVirtualMachine -MockWith { } Mock SetLabVMDiskResource -MockWith { } - Mock SetLabVMDiskFile -ParameterFilter { $CustomBootStrap -eq $null } -MockWith { } + Mock SetLabVMDiskFile -ParameterFilter { $Name -eq $testVMName } -MockWith { } $labVM = NewLabVM -ConfigurationData $configurationData -Name $testVMName -Path 'TestDrive:\' -Credential $testPassword; - Assert-MockCalled SetLabVMDiskFile -ParameterFilter { $CustomBootStrap -eq $null } -Scope It; + Assert-MockCalled SetLabVMDiskFile -ParameterFilter { $Name -eq $testVMName } -Scope It; } It 'Uses CoreCLR bootstrap when "SetupComplete" is specified' { diff --git a/en-US/about_Media.help.txt b/en-US/about_Media.help.txt index 755400b9..604b708d 100644 --- a/en-US/about_Media.help.txt +++ b/en-US/about_Media.help.txt @@ -21,6 +21,7 @@ LONG DESCRIPTION WindowsOptionalFeature Package PackagePath + SetupComplete { CoreCLR } } EXAMPLES From d49e9320970c58877db02924f02ba66fd463fa7c Mon Sep 17 00:00:00 2001 From: Iain Brighton Date: Wed, 30 Dec 2015 15:21:08 +0000 Subject: [PATCH 18/23] Fixes bug in Get-LabMedia where custom media was not always coerced into an array where only a single custom image is registered --- Src/LabMedia.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/LabMedia.ps1 b/Src/LabMedia.ps1 index f702ede1..407ed1e9 100644 --- a/Src/LabMedia.ps1 +++ b/Src/LabMedia.ps1 @@ -145,7 +145,7 @@ function Get-LabMedia { $defaultMedia = GetConfigurationData -Configuration Media; } ## Retrieve custom media - $customMedia = GetConfigurationData -Configuration CustomMedia; + $customMedia = @(GetConfigurationData -Configuration CustomMedia); if (-not $customMedia) { $customMedia = @(); } From b3e5a399a81c15a3bb5e68368a14f440b3958fec Mon Sep 17 00:00:00 2001 From: Iain Brighton Date: Wed, 30 Dec 2015 15:21:54 +0000 Subject: [PATCH 19/23] Adds GetFormattedMessage Adds call stack formatting to warning output --- Lib/Internal.ps1 | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Lib/Internal.ps1 b/Lib/Internal.ps1 index 55e61b77..eb4d2e81 100644 --- a/Lib/Internal.ps1 +++ b/Lib/Internal.ps1 @@ -60,10 +60,10 @@ function InvokeExecutable { } #end process } #end function InvokeExecutable -function WriteVerbose { +function GetFormattedMessage { <# .SYNOPSIS - Wrapper around Write-Verbose that adds a timestamp to the output. + Generates a formatted output message. #> [CmdletBinding()] param ( @@ -75,11 +75,26 @@ function WriteVerbose { $functionName = $parentCallStack.FunctionName; $lineNumber = $parentCallStack.ScriptLineNumber; $scriptName = ($parentCallStack.Location -split ':')[0]; - $verboseMessage = '[{0}] [Script:{1}] [Function:{2}] [Line:{3}] {4}' -f (Get-Date).ToLongTimeString(), $scriptName, $functionName, $lineNumber, $Message; + $formattedMessage = '[{0}] [Script:{1}] [Function:{2}] [Line:{3}] {4}' -f (Get-Date).ToLongTimeString(), $scriptName, $functionName, $lineNumber, $Message; } else { - $verboseMessage = '[{0}] {1}' -f (Get-Date).ToLongTimeString(), $Message; + $formattedMessage = '[{0}] {1}' -f (Get-Date).ToLongTimeString(), $Message; } + return $formattedMessage; + } #end process +} #end function GetFormattedMessage + +function WriteVerbose { +<# + .SYNOPSIS + Wrapper around Write-Verbose that adds a timestamp and/or call stack information to the output. +#> + [CmdletBinding()] + param ( + [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Message + ) + process { + $verboseMessage = GetFormattedMessage -Message $Message; Write-Verbose -Message $verboseMessage; } } #end function WriteVerbose @@ -87,13 +102,14 @@ function WriteVerbose { function WriteWarning { <# .SYNOPSIS - Wrapper around Write-Warning that adds a timestamp to the output. + Wrapper around Write-Warning that adds a timestamp and/or call stack information to the output. #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Message ) process { - Write-Warning -Message ('[{0}] {1}' -f (Get-Date).ToLongTimeString(), $Message); + $warningMessage = GetFormattedMessage -Message $Message; + Write-Warning -Message $warningMessage; } } #end function WriteWarning From d7670726467276fc4bc29233819db47afebfce1c Mon Sep 17 00:00:00 2001 From: Iain Brighton Date: Wed, 30 Dec 2015 15:46:31 +0000 Subject: [PATCH 20/23] Updates Example.psd1, removing the custom bootstrap as it's now implemented in the media .CustomData.CustomBootstrap property --- en-US/Example.psd1 | 16 ++-------------- en-US/about_Media.help.txt | 1 + 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/en-US/Example.psd1 b/en-US/Example.psd1 index 6f248c8b..856b2e06 100644 --- a/en-US/Example.psd1 +++ b/en-US/Example.psd1 @@ -51,25 +51,13 @@ NodeName = 'CLIENT1'; Role = 'CLIENT'; VirtualEngineLab_Media = 'Win81_x64_Enterprise_EN_Eval'; - VirtualEngineLab_CustomBootStrap = @' - ## Unattend.xml will set the Administrator password, but it won't enable the account on client OSes - NET USER Administrator /active:yes; - Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force; - ## Kick-start PowerShell remoting on clients to permit applying DSC configurations - Enable-PSRemoting -SkipNetworkProfileCheck -Force; -'@ + <# VirtualEngineLab_CustomBootStrap = 'Now implemented in the Media's CustomData.CustomBootstrap property' #> } @{ NodeName = 'CLIENT2'; Role = 'CLIENT'; VirtualEngineLab_Media = 'Win10_x64_Enterprise_EN_Eval'; - VirtualEngineLab_CustomBootStrap = @' - ## Unattend.xml will set the Administrator password, but it won't enable the account on client OSes - NET USER Administrator /active:yes; - Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force; - ## Kick-start PowerShell remoting on clients to permit applying DSC configurations - Enable-PSRemoting -SkipNetworkProfileCheck -Force; -'@ + <# VirtualEngineLab_CustomBootStrap = 'Now implemented in the Media's CustomData.CustomBootstrap property' #> } ); NonNodeData = @{ diff --git a/en-US/about_Media.help.txt b/en-US/about_Media.help.txt index 604b708d..d7fe9a0a 100644 --- a/en-US/about_Media.help.txt +++ b/en-US/about_Media.help.txt @@ -22,6 +22,7 @@ LONG DESCRIPTION Package PackagePath SetupComplete { CoreCLR } + CustomBootstrap } EXAMPLES From b3c8bdc683eee40f5262c2755143f9705625cb14 Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Sun, 3 Jan 2016 18:42:52 +0000 Subject: [PATCH 21/23] Formatting updates --- Lib/Internal.ps1 | 9 ++++++--- Src/LabVMDefaults.ps1 | 5 ----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Lib/Internal.ps1 b/Lib/Internal.ps1 index eb4d2e81..f6c080c3 100644 --- a/Lib/Internal.ps1 +++ b/Lib/Internal.ps1 @@ -67,7 +67,8 @@ function GetFormattedMessage { #> [CmdletBinding()] param ( - [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Message + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Message ) process { if (($labDefaults.CallStackLogging) -and ($labDefaults.CallStackLogging -eq $true)) { @@ -91,7 +92,8 @@ function WriteVerbose { #> [CmdletBinding()] param ( - [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Message + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Message ) process { $verboseMessage = GetFormattedMessage -Message $Message; @@ -106,7 +108,8 @@ function WriteWarning { #> [CmdletBinding()] param ( - [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Message + [Parameter(Mandatory, ValueFromPipeline)] + [System.String] $Message ) process { $warningMessage = GetFormattedMessage -Message $Message; diff --git a/Src/LabVMDefaults.ps1 b/Src/LabVMDefaults.ps1 index 61785813..9bd3ba30 100644 --- a/Src/LabVMDefaults.ps1 +++ b/Src/LabVMDefaults.ps1 @@ -111,11 +111,6 @@ function Set-LabVMDefault { process { $vmDefaults = GetConfigurationData -Configuration VM; - ## This property may not be present in the original VM default file - if ($vmDefaults.PSObject.Properties.Name -notcontains 'CustomBootstrapOrder') { - [ref] $null = Add-Member -InputObject $vmDefaults -MemberType NoteProperty -Name 'CustomBootstrapOrder' -Value $CustomBootstrapOrder; - } - if ($PSBoundParameters.ContainsKey('StartupMemory')) { $vmDefaults.StartupMemory = $StartupMemory; } From 3cb4e0675183e7568436543fad2f55712529e909 Mon Sep 17 00:00:00 2001 From: iainbrighton Date: Sun, 3 Jan 2016 18:43:03 +0000 Subject: [PATCH 22/23] Updates test coverage --- Tests/Lib/BootStrap.Tests.ps1 | 16 ++++++ Tests/Lib/ConfigurationData.Tests.ps1 | 33 +++++++++++- Tests/Lib/Internal.Tests.ps1 | 51 +++++++++++++++++++ Tests/Src/LabVM.Tests.ps1 | 72 +++++++++++++++++++++++++++ Tests/Src/LabVMDefaults.Tests.ps1 | 13 +++++ 5 files changed, 184 insertions(+), 1 deletion(-) diff --git a/Tests/Lib/BootStrap.Tests.ps1 b/Tests/Lib/BootStrap.Tests.ps1 index 51bf774c..3f3bf6fd 100644 --- a/Tests/Lib/BootStrap.Tests.ps1 +++ b/Tests/Lib/BootStrap.Tests.ps1 @@ -130,6 +130,22 @@ Describe 'BootStrap' { $bootstrap | Should Be "$mediaBootstrap`r`n$configurationBootStrap"; } + + It 'Returns configuration bootstrap when "MediaCustomBootstrap" is null' { + $configurationBootstrap = 'Configuration'; + + $bootstrap = ResolveCustomBootstrap -CustomBootstrapOrder ConfigurationFirst -ConfigurationCustomBootstrap $configurationBootstrap; + + $bootstrap | Should Be "$configurationBootStrap`r`n$mediaBootstrap"; + } + + It 'Returns media bootstrap when "ConfigurationCustomBootstrap" is null' { + $mediaBootstrap = 'Media'; + + $bootstrap = ResolveCustomBootstrap -CustomBootstrapOrder MediaFirst -MediaCustomBootstrap $mediaBootstrap; + + $bootstrap | Should Be "$mediaBootstrap`r`n$configurationBootStrap"; + } } #end context Validates "ResolveCustomBootStrap" method diff --git a/Tests/Lib/ConfigurationData.Tests.ps1 b/Tests/Lib/ConfigurationData.Tests.ps1 index 921d0306..55786501 100644 --- a/Tests/Lib/ConfigurationData.Tests.ps1 +++ b/Tests/Lib/ConfigurationData.Tests.ps1 @@ -78,6 +78,19 @@ Describe 'ConfigurationData' { Assert-MockCalled Get-Content -ParameterFilter { $Path -eq $testConfigurationPath } -Scope It; } + + It 'Adds missing "CustomBootstrapOrder" property to VM configuration' { + $testConfigurationFilename = 'TestVMConfiguration.json'; + $testConfigurationPath = "$env:SystemRoot\$testConfigurationFilename"; + $fakeConfiguration = '{ "ConfigurationPath": "%SYSTEMDRIVE%\\TestLab\\Configurations" }'; + [ref] $null = New-Item -Path $testConfigurationPath -ItemType File -Force; + Mock ResolveConfigurationDataPath -MockWith { return ('%SYSTEMROOT%\{0}' -f $testConfigurationFilename); } + Mock Get-Content -ParameterFilter { $Path -eq $testConfigurationPath } -MockWith { return $fakeConfiguration; } + + $vmConfiguration = GetConfigurationData -Configuration VM; + + $vmConfiguration.CustomBootstrapOrder | Should Be 'MediaFirst'; + } } #end context Validates "GetConfigurationData" method @@ -97,7 +110,25 @@ Describe 'ConfigurationData' { ## } ## ##} #end context Validates "GetConfigurationData" method + + Context 'Validates "RemoveConfigurationData" method' { + + It 'Removes configuration file' { + $testConfigurationFilename = 'TestVMConfiguration.json'; + $testConfigurationPath = "$env:SystemRoot\$testConfigurationFilename"; + $fakeConfiguration = '{ "ConfigurationPath": "%SYSTEMDRIVE%\\TestLab\\Configurations" }'; + [ref] $null = New-Item -Path $testConfigurationPath -ItemType File -Force; + Mock ResolveConfigurationDataPath -MockWith { return ('%SYSTEMROOT%\{0}' -f $testConfigurationFilename); } + Mock Test-Path -MockWith { return $true; } + Mock Remove-Item -ParameterFilter { $Path.EndsWith($testConfigurationFilename ) } -MockWith { } + + RemoveConfigurationData -Configuration VM; + + Assert-MockCalled Remove-Item -ParameterFilter { $Path.EndsWith($testConfigurationFilename) } -Scope It; + } + + } #end context Validates "RemoveConfigurationData" method } #end InModuleScope -} #end describe Bootstrap +} #end describe ConfigurationData diff --git a/Tests/Lib/Internal.Tests.ps1 b/Tests/Lib/Internal.Tests.ps1 index e02cdd82..ab00661f 100644 --- a/Tests/Lib/Internal.Tests.ps1 +++ b/Tests/Lib/Internal.Tests.ps1 @@ -76,12 +76,54 @@ Describe 'Internal' { } #end context Validates "InvokeExecutable" method + Context 'Validates "GetFormattedMessage" method' { + + It 'Does not return call stack information when "$labDefaults.CallStackLogging" = "$null"' { + $labDefaults = @{ } + $testMessage = 'This is a test message'; + + $message = GetFormattedMessage -Message $testMessage; + + $message -match '\] \[' | Should Be $false; + } + + It 'Does not return call stack information when "$labDefaults.CallStackLogging" = "$false"' { + $labDefaults = @{ CallStackLogging = $false; } + $testMessage = 'This is a test message'; + + $message = GetFormattedMessage -Message $testMessage; + + $message -match '\] \[' | Should Be $false; + } + + It 'Returns call stack information when "$labDefaults.CallStackLogging" = "$true"' { + $labDefaults = @{ CallStackLogging = $true; } + $testMessage = 'This is a test message'; + + $message = GetFormattedMessage -Message $testMessage; + + $message -match '\] \[' | Should Be $true; + } + + } #end context Validates "GetFormattedMessage" method + Context 'Validates "WriteVerbose" method' { + It 'Calls "GetFormattedMessage" method' { + $testMessage = 'This is a test message'; + Mock GetFormattedMessage -ParameterFilter { $Message -match $testMessage } -MockWith { return $testMessage; } + + WriteVerbose -Message $testMessage; + + Assert-MockCalled GetFormattedMessage -ParameterFilter { $Message -match $testMessage } -Scope It; + } + It 'Calls "Write-Verbose" method with test message' { $testMessage = 'This is a test message'; Mock Write-Verbose -ParameterFilter { $Message -match "$testMessage`$" } -MockWith { } + WriteVerbose -Message $testMessage; + Assert-MockCalled Write-Verbose -ParameterFilter { $Message -match $testMessage } -Scope It; } @@ -89,6 +131,15 @@ Describe 'Internal' { Context 'Validates "WriteWarning" method' { + It 'Calls "GetFormattedMessage" method' { + $testMessage = 'This is a test message'; + Mock GetFormattedMessage -ParameterFilter { $Message -match $testMessage } -MockWith { return $testMessage; } + + WriteVerbose -Message $testMessage; + + Assert-MockCalled GetFormattedMessage -ParameterFilter { $Message -match $testMessage } -Scope It; + } + It 'Calls "Write-Warning" method with test message' { $testMessage = 'This is a test message'; Mock Write-Warning -ParameterFilter { $Message -match "$testMessage`$" } -MockWith { } diff --git a/Tests/Src/LabVM.Tests.ps1 b/Tests/Src/LabVM.Tests.ps1 index 2e40deaa..bb7ffb86 100644 --- a/Tests/Src/LabVM.Tests.ps1 +++ b/Tests/Src/LabVM.Tests.ps1 @@ -315,6 +315,29 @@ Describe 'LabVM' { { NewLabVM -ConfigurationData $configurationData -Name $testVMName -Path 'TestDrive:\' -Credential $testPassword } | Should Throw; } + It 'Does not throw when "ClientCertificatePath" cannot be found and "IsQuickVM" = "$true"' { + $testVMName = 'TestVM'; + $testMedia = 'Test-Media'; + $testVMSwitch = 'Test Switch'; + $configurationData = @{ + AllNodes = @( + @{ NodeName = $testVMName; Media = $testMedia; } + ) + } + Mock ResolveLabMedia -MockWith { return $Id; } + Mock SetLabSwitch -MockWith { } + Mock ResetLabVMDisk -MockWith { } + Mock SetLabVirtualMachine -MockWith { } + Mock SetLabVMDiskResource -MockWith { } + Mock SetLabVMDiskFile -MockWith { } + Mock Checkpoint-VM -MockWith { } + Mock Get-VM -MockWith { } + Mock Test-LabImage -MockWith { return $false; } + Mock New-LabImage -MockWith { } + + { NewLabVM -ConfigurationData $configurationData -Name $testVMName -Path 'TestDrive:\' -Credential $testPassword -IsQuickVM } | Should Not Throw; + } + It 'Throws when "RootCertificatePath" cannot be found' { $testVMName = 'TestVM'; $configurationData = @{ @@ -326,6 +349,29 @@ Describe 'LabVM' { { NewLabVM -ConfigurationData $configurationData -Name $testVMName -Path 'TestDrive:\' -Credential $testPassword } | Should Throw; } + It 'Does not throw when "RootCertificatePath" cannot be found and "IsQuickVM" = "$true"' { + $testVMName = 'TestVM'; + $testMedia = 'Test-Media'; + $testVMSwitch = 'Test Switch'; + $configurationData = @{ + AllNodes = @( + @{ NodeName = $testVMName; Media = $testMedia; } + ) + } + Mock ResolveLabMedia -MockWith { return $Id; } + Mock SetLabSwitch -MockWith { } + Mock ResetLabVMDisk -MockWith { } + Mock SetLabVirtualMachine -MockWith { } + Mock SetLabVMDiskResource -MockWith { } + Mock SetLabVMDiskFile -MockWith { } + Mock Checkpoint-VM -MockWith { } + Mock Get-VM -MockWith { } + Mock Test-LabImage -MockWith { return $false; } + Mock New-LabImage -MockWith { } + + { NewLabVM -ConfigurationData $configurationData -Name $testVMName -Path 'TestDrive:\' -Credential $testPassword -IsQuickVM } | Should Not Throw; + } + It 'Creates parent image if it is not already present' { $testVMName = 'TestVM'; $testMedia = 'Test-Media'; @@ -376,6 +422,32 @@ Describe 'LabVM' { Assert-MockCalled SetLabSwitch -ParameterFilter { $Name -eq $testVMSwitch } -Scope It; } + It 'Does not call "SetLabSwitch" when "IsQuickVM" = "$true"' { + $testVMName = 'TestVM'; + $testMedia = 'Test-Media'; + $testVMSwitch = 'Test Switch'; + $configurationData = @{ + AllNodes = @( + @{ NodeName = $testVMName; Media = $testMedia; SwitchName = $testVMSwitch; } + ) + } + Mock ResolveLabMedia -MockWith { return $Id; } + Mock ResetLabVMDisk -MockWith { } + Mock SetLabVirtualMachine -MockWith { } + Mock SetLabVMDiskResource -MockWith { } + Mock SetLabVMDiskFile -MockWith { } + Mock Checkpoint-VM -MockWith { } + Mock Get-VM -MockWith { } + Mock Test-LabImage -MockWith { return $true; } + Mock New-LabImage -MockWith { } + Mock SetLabSwitch -MockWith { } + + $labVM = NewLabVM -ConfigurationData $configurationData -Name $testVMName -Path 'TestDrive:\' -Credential $testPassword -IsQuickVM; + + Assert-MockCalled SetLabSwitch -Scope It -Exactly 0; + + } + It 'Calls "ResetLabVMDisk" to create VM disk' { $testVMName = 'TestVM'; $testMedia = 'Test-Media'; diff --git a/Tests/Src/LabVMDefaults.Tests.ps1 b/Tests/Src/LabVMDefaults.Tests.ps1 index a5cb4ded..6f3fe890 100644 --- a/Tests/Src/LabVMDefaults.Tests.ps1 +++ b/Tests/Src/LabVMDefaults.Tests.ps1 @@ -13,6 +13,18 @@ Describe 'LabVMDefaults' { InModuleScope $moduleName { + Context 'Validates "Reset-LabVMDefault" method' { + + It 'Calls "RemoveConfigurationData" method' { + Mock RemoveConfigurationData -ParameterFilter { $Configuration -eq 'VM' } -MockWith { } + + $defaults = Reset-LabVMDefault; + + Assert-MockCalled RemoveConfigurationData -ParameterFilter { $Configuration -eq 'VM' } -Scope It; + } + + } #end context Validates "Reset-LabMDefault" method + Context 'Validates "Get-LabVMDefault" method' { It 'Returns a "System.Management.Automation.PSCustomObject" object type' { @@ -54,6 +66,7 @@ Describe 'LabVMDefaults' { @{ RegisteredOwner = 'Virtual Engine Ltd'; } @{ RegisteredOrganization = 'Virtual Engine Ltd'; } @{ BootDelay = 42; } + @{ CustomBootstrapOrder = 'Disabled'; } ) foreach ($property in $testProperties) { It "Sets ""$($property.Keys[0])"" value" { From 674bdf207ef300c3f2880e87e3677053ce7af505 Mon Sep 17 00:00:00 2001 From: Iain Brighton Date: Wed, 6 Jan 2016 17:05:34 +0000 Subject: [PATCH 23/23] Creates missing Internal switches in New-LabVM --- .gitignore | 1 + Config/Media.json | 2 +- Lib/DscResource.ps1 | 2 +- Resources.psd1 | 2 ++ Src/LabVM.ps1 | 67 +++++++++++++++++++++++++++++---------------- 5 files changed, 48 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index edb5a01d..c085eee7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.sln *.suo *.pssproj +.vscode/settings.json diff --git a/Config/Media.json b/Config/Media.json index 42ddf90b..44519e51 100644 --- a/Config/Media.json +++ b/Config/Media.json @@ -75,7 +75,7 @@ "Microsoft-NanoServer-DNS-Package" ] }, - "Hotfixes": null + "Hotfixes": [] }, { "Id": "2012R2_x64_Standard_EN_Eval", diff --git a/Lib/DscResource.ps1 b/Lib/DscResource.ps1 index 7c7264e0..72978cb2 100644 --- a/Lib/DscResource.ps1 +++ b/Lib/DscResource.ps1 @@ -13,7 +13,7 @@ function ImportDscResource { ) process { ## Check whether the resource is already imported/registered - WriteVerbose ($localized.CheckingDscResource -f $ModuleName, $ResourceName); + Write-Debug ($localized.CheckingDscResource -f $ModuleName, $ResourceName); $testCommandName = 'Test-{0}TargetResource' -f $Prefix; if (-not (Get-Command -Name $testCommandName -ErrorAction SilentlyContinue)) { if ($UseDefault) { diff --git a/Resources.psd1 b/Resources.psd1 index e4247b2c..b454a544 100644 --- a/Resources.psd1 +++ b/Resources.psd1 @@ -106,6 +106,7 @@ ConvertFrom-StringData -StringData @' CreatingQuickVM = Creating quick VM '{0}' using media '{1}'. RemovingQuickVM = Removing quick VM '{0}'. ResettingVM = Resetting VM '{0}'. + CreatingInternalVirtualSwitch = Creating Internal '{0}' virtual switch. NoCertificateFoundWarning = No '{0}' certificate was found. CannotLocateLcmFileWarning = Cannot locate LCM configuration file '{0}'. No DSC Local Configuration Manager configuration will be applied. @@ -116,6 +117,7 @@ ConvertFrom-StringData -StringData @' NoCustomMediaFoundWarning = No custom media '{0}' registered. UnsupportedConfigurationWarning = Configuration '{0}' is not supported by {1}. ShouldProcessWarning = Are you sure you want to perform this action? + MissingVirtualSwitchWarning = Virtual switch '{0}' is missing. InvalidPathError = {0} path '{1}' is invalid. InvalidDestinationPathError = Invalid destination path '{0}' specified. diff --git a/Src/LabVM.ps1 b/Src/LabVM.ps1 index 2ce42078..1aaa3d9c 100644 --- a/Src/LabVM.ps1 +++ b/Src/LabVM.ps1 @@ -439,6 +439,8 @@ function New-LabVM { 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 @@ -541,37 +543,54 @@ function New-LabVM { if (-not $Credential) { throw ($localized.CannotProcessCommandError -f 'Credential'); } elseif ($Credential.Password.Length -eq 0) { throw ($localized.CannotBindArgumentError -f 'Password'); } - ## Test VM Switch exists - foreach ($switch in $SwitchName) { - if (-not (Get-VMSwitch -Name $switch -ErrorAction SilentlyContinue)) { - throw ($localized.SwitchDoesNotExistError -f $switch); - } - } #end foreach switch + } #end begin process { - foreach ($vmName in $Name) { - ## Create a skelton node configuration - $configurationNode = @{ Nodename = $vmName; }; - + ## Skeleton configuration node + $configurationNode = @{ } + + if ($CustomData) { ## Add all -CustomData keys/values to the skeleton configuration - if ($CustomData) { - foreach ($key in $CustomData.Keys) { - $configurationNode[$key] = $CustomData.$key; - } + foreach ($key in $CustomData.Keys) { + $configurationNode[$key] = $CustomData.$key; + } + } + + ## Explicitly defined parameters override any -CustomData + $parameterNames = @('StartupMemory','MinimumMemory','MaximumMemory','SwitchName','Timezone','UILanguage', + 'ProcessorCount','InputLocale','SystemLocale','UserLocale','RegisteredOwner','RegisteredOrganization') + 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; - ## Explicitly defined parameters override any -CustomData - $parameterNames = @('StartupMemory','MinimumMemory','MaximumMemory','SwitchName','Timezone','UILanguage', - 'ProcessorCount','InputLocale','SystemLocale','UserLocale','RegisteredOwner','RegisteredOrganization') - foreach ($key in $parameterNames) { - if ($PSBoundParameters.ContainsKey($key)) { - $configurationNode[$key] = $PSBoundParameters.$key; + ## Ensure we have at lease the default switch if nothing was specified + if (-not $configurationNode.ContainsKey('SwitchName')) { + $configurationNode['SwitchName'] = (GetConfigurationData -Configuration VM).SwitchName; + } + ## Ensure the specified/default virtual switch(es) exist + foreach ($switch in $configurationNode.SwitchName) { + if (-not (Get-VMSwitch -Name $switch -ErrorAction SilentlyContinue)) { + WriteWarning -Message ($localized.MissingVirtualSwitchWarning -f $switch); + $switchConfigurationData = @{ + NonNodeData = @{ + $labDefaults.ModuleName = @{ + Network = @( @{ Name = $switch; Type = 'Internal'; } ) + } + } } + WriteVerbose -Message ($localized.CreatingInternalVirtualSwitch -f $switch); + SetLabSwitch -Name $switch -ConfigurationData $switchConfigurationData; } - - ## Ensure the specified MediaId is applied after any CustomData media entry! - $configurationNode['Media'] = $PSBoundParameters.MediaId; - + } #end foreach switch + + foreach ($vmName in $Name) { + ## Update the node name before creating the VM + $configurationNode['NodeName'] = $vmName; $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'New-LabVM', $vmName; $verboseProcessMessage = $localized.CreatingQuickVM -f $vmName, $PSBoundParameters.MediaId; if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) {