Public/New-DSCPullServerLab.ps1

function New-DSCPullServerLab {
    [CmdletBinding(SupportsShouldProcess = $true)]
    Param(
        [Parameter (Mandatory = $true)]
        [PSCredential]$Credential,

        [Parameter (Mandatory = $true)]
        [string]$ISODestinationPath,

        [Parameter (Mandatory = $true)]
        [string]$ParentVHDDestinationPath,

        [Parameter (Mandatory = $true)]
        [string]$ParentVHDLocale,

        [Parameter (Mandatory = $true)]
        [string]$ParentVHDTimeZone,

        [Parameter (Mandatory = $true)]
        [string]$DSCPullServerName,

        [Parameter (ParameterSetName = 'TargetNodes')]
        [string[]]$TargetNodeNames,

        [Parameter (Mandatory = $true)]
        [int]$DSCPullServerRamMB,

        [Parameter (Mandatory = $true, ParameterSetName = 'TargetNodes')]
        [Parameter (Mandatory = $false, ParameterSetName = 'Default')]
        [int]$TargetNodeRamMB,

        [Parameter (Mandatory = $true)]
        [string]$VMRootPath,

        [Parameter (Mandatory = $true)]
        [string]$VMIPAddress,
        
        [Parameter (Mandatory = $true)]
        [byte]$VMSubnetBits,

        [Parameter (Mandatory = $true)]
        [string[]]$VMDNSServers,
        
        [Parameter (Mandatory = $true)]
        [string]$VMDefaultGateway,

        [Parameter (Mandatory = $true)]
        [string]$VirtualSwitchName,

        [switch]$StartVMs,

        [switch]$Force,

        [string]$Uri = 'https://software-download.microsoft.com/download/pr/Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.ISO',

        [string]$OSEdition = '4',

        [string]$XPSDesiredStateConfigurationVersion = '3.13.0.0'
    )
    try {
        $ResourcesNeeded = @()
        if ($Force) {
            $ConfirmPreference = 'Low'
            Write-Warning 'The force parameter will overwrite existing components if required. The ISO will also be re-downloaded.'
            $ResourcesNeeded = 'ISO', 'ParentVHD', 'VSwitch', 'VM', 'VMConfig', 'DSCServiceConfig', 'TestVMs'
        }
        else {
            if ((Test-Path $ISODestinationPath) -eq $false) {
                $ResourcesNeeded += 'ISO'
            }
            if ((Test-Path $ParentVHDDestinationPath) -eq $false) {
                $ResourcesNeeded += 'ParentVHD'
            }
            if (-not(Get-VMSwitch $VirtualSwitchName -ErrorAction SilentlyContinue)) {
                $ResourcesNeeded += 'VSwitch'
            }
            if (-not($VM = Get-VM -Name $DSCPullServerName -ErrorAction SilentlyContinue)) {
                $ResourcesNeeded += 'VM'
                $ResourcesNeeded += 'VMConfig'
                $ResourcesNeeded += 'DSCServiceConfig'
            }
            else {
                # check whether the existing vm has already been configured as a pull server by Install-DSCPullServer
                $ErrorActionPreference = 'Stop'
                if ($VM.State -ne 'Running') {
                    Start-VM -VM $VM
                }
                $error.Clear()
                $i = 0
                do {
                    $i++
                    Write-Verbose "Waiting for $($VM.Name)..."
                    Wait-VM -Name $VM.Name -For IPAddress -Timeout 1800 -ErrorAction Stop
                    try {
                        # compare-Object will return null if pull server is already configured
                        $ConfigurationDrift = Invoke-Command -VMId $VM.Id -Credential $Credential -ScriptBlock {$ResourcesInDesiredState = (Get-DscConfigurationStatus).resourcesindesiredstate.resourceid
                            $DesiredResources = '[WindowsFeature]DSCServiceFeature',
                            '[xDSCWebService]PSDSCPullServer',
                            '[File]RegistrationKeyFile',
                            '[WindowsFeature]DHCPServerFeature',
                            '[WindowsFeature]DHCPRSAT',
                            '[xDhcpServerScope]DHCPScope',
                            '[xDhcpServerOption]DHCPScopeDNSServer',
                            '[WindowsFeature]DNSServerFeature',
                            '[xDnsServerPrimaryZone]DNSZone',
                            '[WindowsFeature]DNSRSAT',
                            '[xDnsServerPrimaryZone]DNSZone',
                            '[xDnsRecord]DSCPullServerARecord'
                            Compare-Object -ReferenceObject $DesiredResources -DifferenceObject $ResourcesInDesiredState}
                        if ($ConfigurationDrift) {
                            $ResourcesNeeded += 'DSCServiceConfig'   
                        }
                        else {
                            Write-Warning "The VM 'DSCPullServer' already exists and is configured as a pull server."
                        }
                        $error.Clear()
                    }
                    catch {
                        if ($_.Exception.Message -like "*The Consistency Check or Pull cmdlet is in progress and must return before Get-DscConfigurationStatus can be invoked*") {
                            Start-Sleep -Seconds 20
                            $error.Add($_) | Out-Null
                        }
                        # check whether Get-DSCConfigurationStatus reported that there is no current configuration (by means of an error)
                        elseif ($_.Exception.Message -like "No status information*" -or $_.Exception.Message -eq "Cannot bind argument to parameter 'DifferenceObject' because it is null.") {
                            $ResourcesNeeded += 'DSCServiceConfig'
                            $error.Clear()
                        }
                        else {
                            if ($i -gt 9) {
                                throw "Timed out while trying to determine whether $($VM.name) is already configured as a pull server"
                            }
                            $error.Add($_) | Out-Null
                        }
                    }
                }
                # keep trying if there is any other error (i.e. vm decides to reboot midway through the remote command)
                while ($error)
                $ErrorActionPreference = 'Continue'
            }
            if ($PSBoundParameters.ContainsKey('TargetNodeNames')) {
                # no need to check for existing test vms with colliding names as New-WindowsServerLabVM will warn and skip
                $ResourcesNeeded += 'TestVMs'
            }
            if ($ResourcesNeeded.Length -eq 0) {
                $ResourcesNeeded = 'None'
            }
        }
        if ($PSCmdlet.ShouldProcess("$env:COMPUTERNAME")) {
            $ConfirmPreference = 'High'
            if ($Force) {
                if (Get-VM -Name $DSCPullServerName -ErrorAction 'SilentlyContinue' | Where-Object state -EQ 'running' -ErrorAction 'SilentlyContinue') {
                    Remove-WindowsServerLabVM -Name $DSCPullServerName -RemoveDisk
                }
            }
            switch ($ResourcesNeeded) {
                'ISO' {New-WindowsServerISO -ISODestinationPath $ISODestinationPath -Uri $Uri -Force:$Force}
                'ParentVHD' {New-WindowsServerParentVHD -ParentVHDDestinationPath $ParentVHDDestinationPath -ISOPath $ISODestinationPath -OSEdition $OSEdition -Force:$Force; Set-VHDUnattendFile -VHDPath $ParentVHDDestinationPath -UnattendAdminPassword $Credential.Password -Locale $ParentVHDLocale -TimeZone $ParentVHDTimeZone}
                'VSwitch' {New-LabVirtualSwitch -SwitchName $VirtualSwitchName -Force:$Force}
                'VM' {New-WindowsServerLabVM -Name $DSCPullServerName -ParentVHDPath $ParentVHDDestinationPath -RootPath $VMRootPath -RamMB $DSCPullServerRamMB -VirtualSwitchName $VirtualSwitchName -Start:$StartVMs -Force:$Force}
                'VMConfig' {Set-WindowsServerLabVM -Name $DSCPullServerName -Credential $Credential -IPAddress $VMIPAddress -SubnetBits $VMSubnetBits -DefaultGateway $VMDefaultGateway -DNSServers $VMDNSServers -EthernetAdapterName 'Ethernet' -Attempts 10}
                'DSCServiceConfig' {Install-DSCPullServer -VMName $DSCPullServerName -Credential $Credential -XPSDesiredStateConfigurationVersion $XPSDesiredStateConfigurationVersion -Attempts 10}
                'TestVMs' {New-WindowsServerLabVM -Name $TargetNodeNames -ParentVHDPath $ParentVHDDestinationPath -RootPath $VMRootPath -RamMB $TargetNodeRamMB -VirtualSwitchName $VirtualSwitchName -Start:$StartVMs -Force:$Force -PassThru |
                           Set-WindowsServerLabVM -Credential $Credential -Attempts 20}
                Default {Write-Warning 'All lab components already exist. Use the force switch to replace them or delete the components you want to replace then run the command again.'}
            }
        }
    }
    catch {
        $PSCmdlet.ThrowTerminatingError($_)
    }
}