AutomatedLabVirtualMachines.psm1
#region New-LabVM function New-LabVM { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'ByName')] [string[]]$Name, [Parameter(ParameterSetName = 'All')] [switch]$All, [switch]$CreateCheckPoints ) Write-LogFunctionEntry $lab = Get-Lab if (-not $lab) { Write-Error 'No definitions imported, so there is nothing to do. Please use Import-Lab first' return } if ($Name) { $machines = $lab.Machines | Where-Object Name -in $Name } else { $machines = @() $machines += $lab.Machines | Where-Object { $_.Roles.Name -contains 'RootDC' } $machines += $lab.Machines | Where-Object { $_.OperatingSystem -notlike '*Server*' } $machines += $lab.Machines | Where-Object { $_.Roles.Name -notcontains 'RootDC' -and $_.OperatingSystem -like '*Server*' } } if (-not $machines) { $message = 'No machine found to create. Either the given name is wrong or there is no machine defined yet' Write-LogFunctionExitWithError -Message $message return } $jobs = @() foreach ($machine in $machines.GetEnumerator()) { Write-ScreenInfo -Message "Creating $($machine.HostType) machine '$machine'" -TaskStart -NoNewLine if ($machine.HostType -eq 'HyperV') { $result = New-LWHypervVM -Machine $machine if ('RootDC' -in $Machine.Roles.Name) { Start-LabVM -ComputerName $Machine.Name } if ($result) { Write-ProgressIndicatorEnd Write-ScreenInfo -Message 'Done' -TaskEnd } else { Write-ScreenInfo -Message "Could not create $($machine.HostType) machine '$machine'" -TaskEnd -Type Error } } elseif ($machine.HostType -eq 'VMWare') { $vmImageName = (New-Object AutomatedLab.OperatingSystem($machine.OperatingSystem)).VMWareImageName if (-not $vmImageName) { Write-Error "The VMWare image for operating system '$($machine.OperatingSystem)' is not defined in AutomatedLab. Cannot install the machine." continue } New-LWVMWareVM -Name $machine.Name -ReferenceVM $vmImageName -AdminUserName $machine.InstallationUser.UserName -AdminPassword $machine.InstallationUser.Password ` -DomainName $machine.DomainName -DomainJoinCredential $machine.GetCredential($lab) Start-LabVM -ComputerName $machine } elseif ($machine.HostType -eq 'Azure') { $jobs += New-LWAzureVM -Machine $machine Write-ScreenInfo -Message 'Done' -TaskEnd } } #test if the machine creation jobs succeeded $jobs | Wait-Job | Out-Null $failedJobs = $jobs | Where-Object State -eq 'Failed' $completedJobs = $jobs | Where-Object State -eq 'Completed' if ($failedJobs) { $machinesFailedToCreate = ($failedJobs.Name | ForEach-Object { ($_ -split '\(|\)')[3] }) -join ', ' throw "Failed to create the following Azure machines: $machinesFailedToCreate'. For further information take a look at the background job's result (Get-Job, Receive-Job)" } if ($completedJobs) { $azureVMs = $completedJobs.Name | ForEach-Object { ($_ -split '\(|\)')[3] } $azureVMs = Get-LabMachine -ComputerName $azureVMs } if ($azureVMs) { Write-ScreenInfo -Message 'Initializing machines' -TaskStart Initialize-LWAzureVM -Machine $azureVMs Write-ScreenInfo -Message 'Done' -TaskEnd } $vmwareVMs = $machines | Where-Object HostType -eq VMWare if ($vmwareVMs) { throw New-Object System.NotImplementedException } Write-LogFunctionExit } #endregion New-LabVM #region Start-LabVM function Start-LabVM { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding(DefaultParameterSetName = 'ByName')] param ( [Parameter(ParameterSetName = 'ByName', Position = 0)] [string[]]$ComputerName, [Parameter(Mandatory, ParameterSetName = 'ByRole')] [AutomatedLab.Roles]$RoleName, [Parameter(ParameterSetName = 'All')] [switch]$All, [switch]$Wait, [switch]$NoNewline, [int]$DelayBetweenComputers = 0, [int]$TimeoutInMinutes = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_StartLabMachine_Online, [int]$StartNextMachines, [int]$StartNextDomainControllers, [string]$Domain, [switch]$RootDomainMachines, [int]$ProgressIndicator, [int]$PreDelaySeconds = 0, [int]$PostDelaySeconds = 0 ) begin { Write-LogFunctionEntry $lab = Get-Lab $vms = @() $availableVMs = $lab.Machines } process { if (-not $lab.Machines) { $message = 'No machine definitions imported, please use Import-Lab first' Write-Error -Message $message Write-LogFunctionExitWithError -Message $message return } if ($PSCmdlet.ParameterSetName -eq 'ByName' -and -not $StartNextMachines -and -not $StartNextDomainControllers) { $vms = Get-LabMachine -ComputerName $ComputerName } elseif ($PSCmdlet.ParameterSetName -eq 'ByRole' -and -not $StartNextMachines -and -not $StartNextDomainControllers) { #get all machines that have a role assigned and the machine's role name is part of the parameter RoleName $vms = $lab.Machines | Where-Object { $_.Roles.Name } | Where-Object { $_.Roles | Where-Object { $RoleName.HasFlag([AutomatedLab.Roles]$_.Name) } } if (-not $vms) { Write-Error "There is no machine in the lab with the role '$RoleName'" return } } elseif ($PSCmdlet.ParameterSetName -eq 'ByRole' -and $StartNextMachines -and -not $StartNextDomainControllers) { $vms = $lab.Machines | Where-Object { $_.Roles.Name -and ((Get-LabVMStatus -ComputerName $_.Name) -ne 'Started')} | Where-Object { $_.Roles | Where-Object { $RoleName.HasFlag([AutomatedLab.Roles]$_.Name) } } if (-not $vms) { Write-Error "There is no machine in the lab with the role '$RoleName'" return } $vms = $vms | Select-Object -First $StartNextMachines } elseif (-not ($PSCmdlet.ParameterSetName -eq 'ByRole') -and -not $RootDomainMachines -and -not $StartNextMachines -and $StartNextDomainControllers) { $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'FirstChildDC' } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'DC' } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'CaRoot' -and (-not $_.DomainName) } $vms = $vms | Select-Object *, @{name='OSversion';expression={$_.OperatingSystem.Version}} | Sort-Object -Property OSversion $vms = $vms | Where-Object { (Get-LabVMStatus -ComputerName $_.Name) -ne 'Started' } | Select-Object -First $StartNextDomainControllers } elseif (-not ($PSCmdlet.ParameterSetName -eq 'ByRole') -and -not $RootDomainMachines -and $StartNextMachines -and -not $StartNextDomainControllers) { $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'CaRoot' -and $_.DomainName -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'CaSubordinate' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -like 'SqlServer*' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'WebServer' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'Orchestrator' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'Exchange2013' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'Exchange2016' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'VisualStudio2013' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'VisualStudio2015' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'Office2013' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { -not $_.Roles.Name -and $_ -notin $vms } #$vms = $vms | Select-Object *, @{name='OSversion';expression={$_.OperatingSystem.Version}} | Sort-Object -Property OSversion $vms = $vms | Where-Object { (Get-LabVMStatus -ComputerName $_.Name) -ne 'Started' } | Select-Object -First $StartNextMachines if ($Domain) { $vms = $vms | Where-Object { (Get-LabMachine -ComputerName $_) -eq $Domain } } } elseif (-not ($PSCmdlet.ParameterSetName -eq 'ByRole') -and -not $RootDomainMachines -and $StartNextMachines -and -not $StartNextDomainControllers) { $vms += Get-LabMachine | Where-Object { $_.Roles.Name -like 'SqlServer*' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'WebServer' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'Orchestrator' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'Exchange2013' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'Exchange2016' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'VisualStudio2013' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'VisualStudio2015' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'Office2013' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { -not $_.Roles.Name -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'CaRoot' -and $_ -notin $vms } $vms += Get-LabMachine | Where-Object { $_.Roles.Name -eq 'CaSubordinate' -and $_ -notin $vms } $vms = $vms | Select-Object *, @{name='OSversion';expression={$_.OperatingSystem.Version}} | Sort-Object -Property OSversion $vms = $vms | Where-Object { (Get-LabVMStatus -ComputerName $_.Name) -ne 'Started' } | Select-Object -First $StartNextMachines if ($Domain) { $vms = $vms | Where-Object { (Get-LabMachine -ComputerName $_) -eq $Domain } } } elseif (-not ($PSCmdlet.ParameterSetName -eq 'ByRole') -and $RootDomainMachines -and -not $StartNextDomainControllers) { $vms = Get-LabMachine | Where-Object { $_.DomainName -in (Get-LabMachine -Role RootDC).DomainName } | Where-Object { $_.Name -notin (Get-LabMachine -Role RootDC).Name -and $_.Roles.Name -notlike '*DC' } $vms = $vms | Select-Object *, @{name='OSversion';expression={$_.OperatingSystem.Version}} | Sort-Object -Property OSversion $vms = $vms | Select-Object -First $StartNextMachines } elseif ($PSCmdlet.ParameterSetName -eq 'All') { $vms = $availableVMs } } end { #if there are no VMs to start, just write a warning if (-not $vms) { return } $vmsCopy = $vms #filtering out all machines that are already running $vmStates = Get-LabVMStatus -ComputerName $vms -AsHashTable foreach ($vmState in $vmStates.GetEnumerator()) { if ($vmState.Value -eq 'Started') { $vms = $vms | Where-Object Name -ne $vmState.Name Write-Debug "Machine '$($vmState.Name)' is already running, removing it from the list of machines to start" } } Write-Verbose "Starting VMs '$($vms.Name -join ', ')'" $hypervVMs = $vms | Where-Object HostType -eq 'HyperV' if ($hypervVMs) { Start-LWHypervVM -ComputerName $hypervVMs -DelayBetweenComputers $DelayBetweenComputers -ProgressIndicator $ProgressIndicator -PreDelaySeconds $PreDelaySeconds -PostDelaySeconds $PostDelaySeconds -NoNewLine:$NoNewline } $azureVms = $vms | Where-Object HostType -eq 'Azure' if ($azureVms) { Start-LWAzureVM -ComputerName $azureVms -DelayBetweenComputers $DelayBetweenComputers -ProgressIndicator $ProgressIndicator -NoNewLine:$NoNewline } $vmwareVms = $vms | Where-Object HostType -eq 'VmWare' if ($vmwareVms) { Start-LWVMWareVM -ComputerName $vmwareVms -DelayBetweenComputers $DelayBetweenComputers } if ($Wait) { Wait-LabVM -ComputerName ($vmsCopy) -Timeout $TimeoutInMinutes -ProgressIndicator $ProgressIndicator -NoNewLine } if ($ProgressIndicator -and (-not $NoNewline)) { Write-ProgressIndicatorEnd } Write-LogFunctionExit } } #endregion Start-LabVM #region Save-LabVM function Save-LabVM { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding(DefaultParameterSetName = 'ByName')] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByName', Position = 0)] [string[]]$Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByRole')] [AutomatedLab.Roles]$RoleName, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'All')] [switch]$All ) begin { Write-LogFunctionEntry $lab = Get-Lab $vms = @() $availableVMs = $lab.Machines.Name } process { if (-not $lab.Machines) { $message = 'No machine definitions imported, please use Import-Lab first' Write-Error -Message $message Write-LogFunctionExitWithError -Message $message return } if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Name | ForEach-Object { if ($_ -in $availableVMs) { $vms += $_ } } } elseif ($PSCmdlet.ParameterSetName -eq 'ByRole') { #get all machines that have a role assigned and the machine's role name is part of the parameter RoleName $machines = ($lab.Machines | Where-Object { $_.Roles.Name } | Where-Object { $_.Roles | Where-Object { $RoleName.HasFlag([AutomatedLab.Roles]$_.Name) } }).Name $vms = $machines } elseif ($PSCmdlet.ParameterSetName -eq 'All') { $vms = $availableVMs } } end { $vms = Get-LabMachine -ComputerName $vms #if there are no VMs to start, just write a warning if (-not $vms) { Write-Warning 'There is no machine to start' return } foreach ($vm in $vms) { Write-Verbose "Saving VMs '$vm'" if ($vm.HostType -eq 'HyperV') { Save-LWHypervVM -ComputerName $vm } elseif ($vm.HostType -eq 'Azure') { Write-Error 'Azure does not support saving machines' } elseif ($vm.HostType -eq 'VMWare') { Save-LWVMWareVM -ComputerName $vm } } Write-LogFunctionExit } } #endregion Start-LabVM #region Restart-LabVM function Restart-LabVM { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding()] param ( [Parameter(Mandatory, Position = 0)] [string[]]$ComputerName, [switch]$Wait, [double]$ShutdownTimeoutInMinutes = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_RestartLabMachine_Shutdown, [int]$ProgressIndicator, [switch]$NoNewLine ) Write-LogFunctionEntry $lab = Get-Lab if (-not $lab.Machines) { Write-Error 'No machine definitions imported, so there is nothing to do. Please use Import-Lab first' return } $machines = Get-LabMachine -ComputerName $ComputerName if (-not $machines) { Write-Error "The machines '$($ComputerName -join ', ')' could not be found in the lab." return } Write-Verbose "Stopping machine '$ComputerName' and waiting for shutdown" Stop-LabVM -ComputerName $ComputerName -ShutdownTimeoutInMinutes $ShutdownTimeoutInMinutes -Wait -ProgressIndicator $ProgressIndicator -NoNewLine Write-Verbose "Machine '$ComputerName' is stopped" Write-Debug 'Waiting 10 seconds' Start-Sleep -Seconds 10 Write-Verbose "Starting machine '$ComputerName' and waiting for availability" Start-LabVM -ComputerName $ComputerName -Wait:$Wait -ProgressIndicator $ProgressIndicator -NoNewline:$NoNewLine Write-Verbose "Machine '$ComputerName' is started" Write-LogFunctionExit } #endregion Restart-LabVM #region Stop-LabVM function Stop-LabVM { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'ByName', Position = 0)] [string[]]$ComputerName, [double]$ShutdownTimeoutInMinutes = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_StopLabMachine_Shutdown, [Parameter(ParameterSetName = 'All')] [switch]$All, [switch]$Wait, [int]$ProgressIndicator, [switch]$NoNewLine ) Write-LogFunctionEntry $lab = Get-Lab if (-not $lab.Machines) { Write-Error 'No machine definitions imported, so there is nothing to do. Please use Import-Lab first' return } if ($ComputerName) { $machines = Get-LabMachine -ComputerName $ComputerName } elseif ($All) { $machines = Get-LabMachine } #filtering out all machines that are already stopped $vmStates = Get-LabVMStatus -ComputerName $machines -AsHashTable foreach ($vmState in $vmStates.GetEnumerator()) { if ($vmState.Value -eq 'Stopped') { $machines = $machines | Where-Object Name -ne $vmState.Name Write-Debug "Machine $($vmState.Name) is already stopped, removing it from the list of machines to stop" } } Remove-LabPSSession -ComputerName $machines $hypervVms = $machines | Where-Object HostType -eq 'HyperV' $azureVms = $machines | Where-Object HostType -eq 'Azure' $vmwareVms = $machines | Where-Object HostType -eq 'VMWare' if ($hypervVms) { Stop-LWHypervVM -ComputerName $hypervVms -TimeoutInMinutes $ShutdownTimeoutInMinutes -ProgressIndicator $ProgressIndicator -NoNewLine:$NoNewLine -ErrorVariable hypervErrors -ErrorAction SilentlyContinue } if ($azureVms) { Stop-LWAzureVM -ComputerName $azureVms -ErrorVariable azureErrors -ErrorAction SilentlyContinue } if ($vmwareVms) { Stop-LWVMWareVM -ComputerName $vmwareVms -ErrorVariable vmwareErrors -ErrorAction SilentlyContinue } $remainingTargets = @() if ($hypervErrors) { $remainingTargets += $hypervErrors.TargetObject } if ($azureErrors) { $remainingTargets + $azureErrors.TargetObject } if ($vmwareErrors) { $remainingTargets + $vmwareErrors.TargetObject } if ($remainingTargets) { Stop-LabVM2 -ComputerName $remainingTargets } if ($Wait) { Wait-LabVMShutdown -ComputerName $machines -TimeoutInMinutes $ShutdownTimeoutInMinutes } Write-LogFunctionExit } #endregion Stop-LabVM #region Stop-LabVM2 function Stop-LabVM2 { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'ByName', Position = 0)] [string[]]$ComputerName, [int]$ShutdownTimeoutInMinutes = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_StopLabMachine_Shutdown ) $scriptBlock = { $sessions = quser.exe $sessionNames = $sessions | Select-Object -Skip 1 | ForEach-Object -Process { ($_.Trim() -split ' +')[2] } Write-Verbose -Message "There are $($sessionNames.Count) open sessions" foreach ($sessionName in $sessionNames) { Write-Verbose -Message "Closing session '$sessionName'" logoff.exe $sessionName } Start-Sleep -Seconds 2 Write-Verbose -Message 'Stopping machine forcefully' Stop-Computer -Force } $jobs = Invoke-LabCommand -ComputerName $ComputerName -ActivityName Shutdown -NoDisplay -ScriptBlock $scriptBlock -AsJob -PassThru $jobs | Wait-Job -Timeout ($ShutdownTimeoutInMinutes * 60) | Out-Null if ($jobs.Count -ne ($jobs | Where-Object State -eq Completed).Count) { Write-Warning "Not all machines stopped in the timeout of $ShutdownTimeoutInMinutes" } } #endregion Stop-LabVM2 #region Wait-LabVM function Wait-LabVM { # .ExternalHelp AutomatedLab.Help.xml param ( [Parameter(Mandatory, Position = 0)] [string[]]$ComputerName, [double]$TimeoutInMinutes = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_WaitLabMachine_Online, [int]$PostDelaySeconds = 0, [ValidateRange(0, 300)] [int]$ProgressIndicator = 0, [switch]$NoNewLine ) begin { Write-LogFunctionEntry $lab = Get-Lab if (-not $lab) { Write-Error 'No definitions imported, so there is nothing to do. Please use Import-Lab first' return } $jobs = @() } process { $vms = Get-LabMachine -ComputerName $ComputerName foreach ($vm in $vms) { $session = $null netsh.exe interface ip delete arpcache | Out-Null $session = New-LabPSSession -ComputerName $vm -UseLocalCredential -UseCredSsp -Retries 1 -ErrorAction SilentlyContinue if ($session) { Write-Verbose "Computer '$vm' was reachable" $jobs += Start-Job -Name "Waiting for machine '$vm'" -ScriptBlock ` { param ( [string]$ComputerName ) $ComputerName } -ArgumentList $vm.Name } else { Write-Verbose "Computer '$($vm.ComputerName)' was not reachable, waiting..." $jobs += Start-Job -Name "Waiting for machine '$vm'" -ScriptBlock { param( [byte[]]$LabBytes, [string]$ComputerName ) #$VerbosePreference = 2 Import-Module -Name Azure* Write-Verbose "Importing Lab from $($LabBytes.Count) bytes" Import-Lab -LabBytes $LabBytes #do 5000 retries. This job is cancelled anyway if the timeout is reached Write-Verbose "Trying to create session to '$ComputerName'" $session = New-LabPSSession -ComputerName $ComputerName -UseLocalCredential -UseCredSsp -Retries 5000 return $ComputerName } -ArgumentList $lab.Export(), $vm.Name } } } end { Write-Verbose "Waiting for $($jobs.Count) machines to respond in timeout ($TimeoutInMinutes minute(s))" Wait-LWLabJob -Job $jobs -ProgressIndicator $ProgressIndicator -NoNewLine:$NoNewLine -NoDisplay $completed = $jobs | Where-Object State -eq Completed | Receive-Job -ErrorAction SilentlyContinue -Verbose:$VerbosePreference if ($completed) { $notReadyMachines = (Compare-Object -ReferenceObject $completed -DifferenceObject $vms.Name).InputObject $jobs | Remove-Job -Force } else { $notReadyMachines = $vms.Name } if ($notReadyMachines) { $message = "The following machines are not ready: $($notReadyMachines -join ', ')" Write-LogFunctionExitWithError -Message $message } else { Write-Verbose "The following machines are ready: $($completed -join ', ')" foreach ($machine in $completed) { if((Get-LabMachine $machine).HostType -eq 'HyperV') { $machineMetadata = Get-LWHypervVMDescription -ComputerName $machine if ($machineMetadata.InitState -lt 1) { $machineMetadata.InitState = 1 } Set-LWHypervVMDescription -Hashtable $machineMetadata -ComputerName $machine } } Write-LogFunctionExit } if ($PostDelaySeconds) { $job = Start-Job -Name "Wait $PostDelaySeconds seconds" -ScriptBlock { Start-Sleep -Seconds $Using:PostDelaySeconds } Wait-LWLabJob -Job $job -ProgressIndicator $ProgressIndicator -NoDisplay -NoNewLine:$NoNewLine } } } #endregion Wait-LabVM function Wait-LabVMRestart { # .ExternalHelp AutomatedLab.Help.xml param ( [Parameter(Mandatory, Position = 0)] [string[]]$ComputerName, [double]$TimeoutInMinutes = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_WaitLabMachine_Online, [ValidateRange(1, 300)] [int]$ProgressIndicator, [AutomatedLab.Machine[]]$StartMachinesWhileWaiting, [switch]$NoNewLine, $MonitorJob ) Write-LogFunctionEntry $lab = Get-Lab if (-not $lab) { Write-Error 'No definitions imported, so there is nothing to do. Please use Import-Lab first' return } $vms = Get-LabMachine -ComputerName $ComputerName $azureVms = $vms | Where-Object HostType -eq 'Azure' $hypervVms = $vms | Where-Object HostType -eq 'HyperV' $vmwareVms = $vms | Where-Object HostType -eq 'VMWare' $start = Get-Date if ($azureVms) { Wait-LWAzureRestartVM -ComputerName $azureVms -TimeoutInMinutes $TimeoutInMinutes -ProgressIndicator $ProgressIndicator -NoNewLine:$NoNewLine -ErrorAction SilentlyContinue -ErrorVariable azureWaitError } if ($hypervVms) { Wait-LWHypervVMRestart -ComputerName $hypervVms -TimeoutInMinutes $TimeoutInMinutes -ProgressIndicator $ProgressIndicator -NoNewLine:$NoNewLine -StartMachinesWhileWaiting $StartMachinesWhileWaiting -ErrorAction SilentlyContinue -ErrorVariable hypervWaitError -MonitorJob $MonitorJob} if ($vmwareVms) { Wait-LWVMWareRestartVM -ComputerName $vmwareVms -TimeoutInMinutes $TimeoutInMinutes -ProgressIndicator $ProgressIndicator -ErrorAction SilentlyContinue -ErrorVariable vmwareWaitError } $waitError = New-Object System.Collections.ArrayList if ($azureWaitError) { $waitError.AddRange($azureWaitError) } if ($hypervWaitError) { $waitError.AddRange($hypervWaitError) } if ($vmwareWaitError) { $waitError.AddRange($vmwareWaitError) } $waitError = $waitError | Where-Object { $_.Exception.Message -like 'Timeout while waiting for computers to restart*' } if ($waitError) { $nonRestartedMachines = $waitError.TargetObject Write-Error "The following machines have not restarted in the timeout of $TimeoutInMinutes minute(s): $($nonRestartedMachines -join ', ')" } Write-LogFunctionExit } #endregion Wait-LabVMRestart #region Wait-LabVMShutdown function Wait-LabVMShutdown { # .ExternalHelp AutomatedLab.Help.xml param ( [Parameter(Mandatory, Position = 0)] [string[]]$ComputerName, [double]$TimeoutInMinutes = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData.Timeout_WaitLabMachine_Online ) Write-LogFunctionEntry $start = Get-Date $lab = Get-Lab if (-not $lab) { Write-Error 'No definitions imported, so there is nothing to do. Please use Import-Lab first' return } $vms = Get-LabMachine -ComputerName $ComputerName $vms | Add-Member -Name HasShutdown -MemberType NoteProperty -Value $false -Force do { foreach ($vm in $vms) { $status = Get-LabVMStatus -ComputerName $vm -Verbose:$false if ($status -eq 'Stopped') { $vm.HasShutdown = $true } Start-Sleep -Seconds 5 } } until (($vms | Where-Object { $_.HasShutdown }).Count -eq $vms.Count -or (Get-Date).AddMinutes(- $TimeoutInMinutes) -gt $start) foreach ($vm in ($vms | Where-Object { -not $_.HasShutdown })) { Write-Error -Message "Timeout while waiting for computer '$($vm.Name)' to shutdown." -TargetObject $vm.Name -ErrorVariable shutdownError } if ($shutdownError) { Write-Error "The following machines have not shutdown in the timeout of $TimeoutInMinutes minute(s): $($shutdownError.TargetObject -join ', ')" } Write-LogFunctionExit } #endregion Wait-LabVMShutdown #region Remove-LabVM function Remove-LabVM { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'ByName', Position = 0)] [string[]]$Name, [Parameter(ParameterSetName = 'All')] [switch]$All ) Write-LogFunctionEntry $lab = Get-Lab if (-not $lab) { Write-Error 'No definitions imported, so there is nothing to do. Please use Import-Lab first' return } if ($Name) { $machines = $lab.Machines | Where-Object Name -in $Name } else { $machines = $lab.Machines } if (-not $machines) { $message = 'No machine found to remove' Write-LogFunctionExitWithError -Message $message return } foreach ($machine in $machines) { $doNotUseGetHostEntry = $MyInvocation.MyCommand.Module.PrivateData.DoNotUseGetHostEntryInNewLabPSSession if (-not $doNotUseGetHostEntry) { $computerName = (Get-HostEntry -Hostname $machine).IpAddress.IpAddressToString } <# removed 161023, might not be required if ((Get-LabVMStatus -ComputerName $machine) -eq 'Unknown') { Start-LabVM -ComputerName $machines -Wait }#> Get-PSSession | Where-Object {$_.ComputerName -eq $computerName} | Remove-PSSession Write-ScreenInfo -Message "Removing Lab VM '$($machine.Name)' (and its associated disks)" if ($virtualNetworkAdapter.HostType -eq 'VMWare') { Write-Error 'Managing networks is not yet supported for VMWare' continue } if ($machine.HostType -eq 'HyperV') { Remove-LWHypervVM -Name $machine } elseif ($machine.HostType -eq 'Azure') { Remove-LWAzureVM -Name $machine } elseif ($machine.HostType -eq 'VMWare') { Remove-LWVMWareVM -Name $machine } if ((Get-HostEntry -Section (Get-Lab).Name.ToLower() -HostName $machine)) { Remove-HostEntry -Section (Get-Lab).Name.ToLower() -HostName $machine } Write-ScreenInfo -Message "Lab VM '$machine' has been removed" } } #endregion Remove-LabVM #region Get-LabVMStatus function Get-LabVMStatus { # .ExternalHelp AutomatedLab.Help.xml param ( [Parameter(Mandatory)] [string[]]$ComputerName, [switch]$AsHashTable ) Write-LogFunctionEntry #required to suporess verbose messages, warnings and errors Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $vms = Get-LabMachine -ComputerName $ComputerName $hypervVMs = $vms | Where-Object HostType -eq 'HyperV' if ($hypervVMs) { $hypervStatus = Get-LWHypervVMStatus -ComputerName $hypervVMs.Name } $azureVMs = $vms | Where-Object HostType -eq 'Azure' if ($azureVMs) { $azureStatus = Get-LWAzureVMStatus -ComputerName $azureVMs.Name } $vmwareVMs = $vms | Where-Object HostType -eq 'VMWare' if ($vmwareVMs) { $vmwareStatus = Get-LWVMWareVMStatus -ComputerName $vmwareVMs.Name } $result = @{ } if ($hypervStatus) { $result = $result + $hypervStatus } if ($azureStatus) { $result = $result + $azureStatus } if ($vmwareStatus) { $result = $result + $vmwareStatus } if ($result.Count -eq 1 -and -not $AsHashTable) { $result.Values[0] } else { $result } Write-LogFunctionExit } #endregion Get-LabVMStatus #region Get-LabVMUptime function Get-LabVMUptime { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding()] param ( [Parameter(Mandatory)] [string[]]$ComputerName ) Write-LogFunctionEntry $cmdGetUptime = { $lastboottime = (Get-WmiObject -Class Win32_OperatingSystem).LastBootUpTime (Get-Date) - [System.Management.ManagementDateTimeconverter]::ToDateTime($lastboottime) } $uptime = Invoke-LabCommand -ComputerName $ComputerName -ActivityName GetUptime -ScriptBlock $cmdGetUptime -UseLocalCredential -PassThru if ($uptime) { Write-LogFunctionExit -ReturnValue $uptime $uptime } else { Write-LogFunctionExitWithError -Message 'Uptime could not be retrieved' } } #endregion Get-LabVMUptime #region Connect-LabVM function Connect-LabVM { # .ExternalHelp AutomatedLab.Help.xml param ( [Parameter(Mandatory)] [string[]]$ComputerName, [switch]$UseLocalCredential ) $machines = Get-LabMachine -ComputerName $ComputerName $lab = Get-Lab foreach ($machine in $machines) { if ($UseLocalCredential) { $cred = $machine.GetLocalCredential() } else { $cred = $machine.GetCredential($lab) } if ($machine.HostType = 'Azure') { $cn = Get-LWAzureVMConnectionInfo -ComputerName $machine.Name $cmd = 'cmdkey.exe /add:"TERMSRV/{0}" /user:"{1}" /pass:"{2}"' -f $cn.DnsName, $cred.UserName, $cred.GetNetworkCredential().Password Invoke-Expression $cmd | Out-Null mstsc.exe "/v:$($cn.DnsName):$($cn.RdpPort)" Start-Sleep -Seconds 5 #otherwise credentials get deleted too quickly $cmd = 'cmdkey /delete:TERMSRV/"{0}"' -f $cn.DnsName Invoke-Expression $cmd | Out-Null } elseif ($machine.HostType -eq 'HyperV') { $cmd = 'cmdkey.exe /add:"TERMSRV/{0}" /user:"{1}" /pass:"{2}"' -f $machine.Name, $cred.UserName, $cred.GetNetworkCredential().Password Invoke-Expression $cmd | Out-Null mstsc.exe "/v:$($cn.DnsName):$($cn.RdpPort)" Start-Sleep -Seconds 1 #otherwise credentials get deleted too quickly $cmd = 'cmdkey /delete:TERMSRV/"{0}"' -f $cn.DnsName Invoke-Expression $cmd | Out-Null } } } #endregion Connect-LabVM #region Get-LabVMRdpFile function Get-LabVMRdpFile { # .ExternalHelp AutomatedLab.Help.xml param ( [Parameter(Mandatory, ParameterSetName = 'ByName')] [string[]]$ComputerName, [switch]$UseLocalCredential, [Parameter(ParameterSetName = 'All')] [switch]$All ) if ($ComputerName) { $machines = Get-LabMachine -ComputerName $ComputerName } else { $machines = Get-LabMachine -All } $lab = Get-Lab foreach ($machine in $machines) { Write-Verbose "Creating RDP file for machine '$($machine.Name)'" $port = 3389 $name = $machine.Name if ($UseLocalCredential) { $cred = $machine.GetLocalCredential() } else { $cred = $machine.GetCredential($lab) } if ($machine.HostType = 'Azure') { $cn = Get-LWAzureVMConnectionInfo -ComputerName $machine.Name $cmd = 'cmdkey.exe /add:"TERMSRV/{0}" /user:"{1}" /pass:"{2}"' -f $cn.DnsName, $cred.UserName, $cred.GetNetworkCredential().Password Invoke-Expression $cmd | Out-Null $name = $cn.DnsName $port = $cn.RdpPort } elseif ($machine.HostType -eq 'HyperV') { $cmd = 'cmdkey.exe /add:"TERMSRV/{0}" /user:"{1}" /pass:"{2}"' -f $machine.Name, $cred.UserName, $cred.GetNetworkCredential().Password Invoke-Expression $cmd | Out-Null } $rdpContent = @" redirectclipboard:i:1 redirectprinters:i:1 redirectcomports:i:0 redirectsmartcards:i:1 devicestoredirect:s:* drivestoredirect:s:* redirectdrives:i:1 session bpp:i:32 prompt for credentials on client:i:0 span monitors:i:1 use multimon:i:0 server port:i:$port allow font smoothing:i:1 promptcredentialonce:i:0 videoplaybackmode:i:1 audiocapturemode:i:1 gatewayusagemethod:i:0 gatewayprofileusagemethod:i:1 gatewaycredentialssource:i:0 full address:s:$name use redirection server name:i:1 username:s:$($cred.UserName) authentication level:i:0 "@ $path = Join-Path -Path $lab.LabPath -ChildPath ($machine.Name + '.rdp') $rdpContent | Out-File -FilePath $path Write-Verbose "RDP file saved to '$path'" } } #endregion Get-LabVMRdpFile #region Join-LabVMDomain function Join-LabVMDomain { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding()] param( [Parameter(Mandatory, Position = 0)] [AutomatedLab.Machine[]]$Machine ) Write-LogFunctionEntry #region Join-Computer function Join-Computer { [cmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$DomainName, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential]$Credential ) Add-Computer -DomainName $DomainName -Credential $Credential -ErrorAction Stop $logonName = "$DomainName\$($Credential.UserName)" $password = $Credential.GetNetworkCredential().Password New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name AutoAdminLogon -Value 1 -Force | Out-Null New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultUserName -Value $logonName -Force | Out-Null New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultPassword -Value $password -Force | Out-Null Start-Sleep -Seconds 1 Restart-Computer -Force } #endregion $lab = Get-Lab $jobs = @() Write-Verbose "Starting joining $($Machine.Count) machines to domains" foreach ($m in $Machine) { if ($m.OperatingSystem.Installation -eq 'Nano Server') { $temp = [System.IO.Path]::GetTempFileName() $dc = Get-LabMachine -Role ADDS | Where-Object DomainName -eq $m.DomainName Remove-Item -Path $temp Invoke-LabCommand -ComputerName $dc -ScriptBlock { djoin /provision /domain $m.DomainName /machine $m.Name /savefile "C:\join_$($m.Name).txt" } -Variable (Get-Variable -Name m) -NoDisplay Receive-File -Source "C:\join_$($m.Name).txt" -Destination $temp -Session (Get-LabPSSession -ComputerName $dc) Copy-LabFileItem -Path $temp -ComputerName $m Invoke-LabCommand -ActivityName "Offline Domain Join on '$m'" -ComputerName $m -ScriptBlock { djoin /requestodj /loadfile "C:\$([System.IO.Path]::GetFileName($temp))" /windowspath C:\Windows /localos } -Variable (Get-Variable -Name temp) -NoDisplay Remove-Item -Path $temp } else { $domain = $lab.Domains | Where-Object Name -eq $m.DomainName $cred = $domain.GetCredential() Write-Verbose "Joining machine '$m' to domain '$domain'" $jobs += Invoke-LabCommand -ComputerName $m -ActivityName DomainJoin -ScriptBlock (Get-Command Join-Computer).ScriptBlock ` -UseLocalCredential -ArgumentList $domain, $cred -AsJob -PassThru -NoDisplay } } if ($jobs) #not for Nano Servers { Write-Verbose 'Waiting on jobs to finish' Wait-LWLabJob -Job $jobs -ProgressIndicator 15 -NoDisplay -NoNewLine Write-ProgressIndicatorEnd Write-ScreenInfo -Message 'Waiting for machines to restart' -NoNewLine Wait-LabVMRestart -ComputerName $Machine -ProgressIndicator 30 -NoNewLine } foreach ($m in $Machine) { $m.HasDomainJoined = $true } Export-Lab Write-LogFunctionExit } #endregion Join-LabVMDomain #region Mount-LabIsoImage function Mount-LabIsoImage { # .ExternalHelp AutomatedLab.Help.xml param( [Parameter(Mandatory, Position = 0)] [string[]]$ComputerName, [Parameter(Mandatory, Position = 1)] [string]$IsoPath, [switch]$SupressOutput, [switch]$PassThru ) Write-LogFunctionEntry $machines = Get-LabMachine -ComputerName $ComputerName $machines | Where-Object HostType -ne HyperV | ForEach-Object { Write-Warning "Using ISO images is only supported with Hyper-V VMs. Skipping machine '$($_.Name)'" } $machines = $machines | Where-Object HostType -eq HyperV foreach ($machine in $machines) { if (-not $SupressOutput) { Write-ScreenInfo -Message "Mounting ISO image '$IsoPath' to computer '$machine'" -Type Info } Mount-LWIsoImage -ComputerName $machine -IsoPath $IsoPath -PassThru:$PassThru } Write-LogFunctionExit } #endregion Mount-LabIsoImage #region Dismount-LabIsoImage function Dismount-LabIsoImage { # .ExternalHelp AutomatedLab.Help.xml param( [Parameter(Mandatory, Position = 0)] [string[]]$ComputerName, [switch]$SupressOutput ) Write-LogFunctionEntry $machines = Get-LabMachine -ComputerName $ComputerName $machines | Where-Object HostType -ne HyperV | ForEach-Object { Write-Warning "Using ISO images is only supported with Hyper-V VMs. Skipping machine '$($_.Name)'" } $machines = $machines | Where-Object HostType -eq HyperV foreach ($machine in $machines) { if (-not $SupressOutput) { Write-ScreenInfo -Message "Dismounting currently mounted ISO image on computer '$machine'." -Type Info } Dismount-LWIsoImage -ComputerName $machine } Write-LogFunctionExit } #endregion Dismount-LabIsoImage #region Get / Set-LabMachineUacStatus function Set-MachineUacStatus { # .ExternalHelp AutomatedLab.Help.xml [Cmdletbinding()] param( [bool]$EnableLUA, [int]$ConsentPromptBehaviorAdmin, [int]$ConsentPromptBehaviorUser ) $currentSettings = Get-MachineUacStatus -ComputerName $ComputerName $uacStatusChanges = $false $registryPath�= 'Software\Microsoft\Windows\CurrentVersion\Policies\System' $openRegistry = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, 'Default') $subkey = $openRegistry.OpenSubKey($registryPath,$true) if ($currentSettings.EnableLUA -ne $EnableLUA -and $PSBoundParameters.ContainsKey('EnableLUA')) { $subkey.SetValue('EnableLUA', [int]$EnableLUA) $uacStatusChanges = $true } if ($currentSettings.PromptBehaviorAdmin -ne $ConsentPromptBehaviorAdmin -and $PSBoundParameters.ContainsKey('ConsentPromptBehaviorAdmin')) { $subkey.SetValue('ConsentPromptBehaviorAdmin', $ConsentPromptBehaviorAdmin) $uacStatusChanges = $true } if ($currentSettings.PromptBehaviorUser -ne $ConsentPromptBehaviorUser -and $PSBoundParameters.ContainsKey('ConsentPromptBehaviorUser')) { $subkey.SetValue('ConsentPromptBehaviorUser', $ConsentPromptBehaviorUser) $uacStatusChanges = $true } if ($uacStatusChanges) { Write-Warning "Setting this requires a reboot of $ComputerName." } } function Get-MachineUacStatus { # .ExternalHelp AutomatedLab.Help.xml [CmdletBinding(SupportsShouldProcess�= $true)] param( [string]$ComputerName = $env:COMPUTERNAME ) $registryPath�= 'Software\Microsoft\Windows\CurrentVersion\Policies\System' $uacStatus�= $false $openRegistry�= [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, 'Default') $subkey�= $openRegistry.OpenSubKey($registryPath, $false) $uacStatus�= $subkey.GetValue('EnableLUA') $consentPromptBehaviorUser = $subkey.GetValue('ConsentPromptBehaviorUser') $consentPromptBehaviorAdmin = $subkey.GetValue('ConsentPromptBehaviorAdmin') New-Object -TypeName PSObject -Property @{ ComputerName = $ComputerName EnableLUA = $uacStatus PromptBehaviorUser = $consentPromptBehaviorUser PromptBehaviorAdmin = $consentPromptBehaviorAdmin } } function Set-LabMachineUacStatus { # .ExternalHelp AutomatedLab.Help.xml [Cmdletbinding()] param( [Parameter(Mandatory)] [string[]]$ComputerName, [bool]$EnableLUA, [int]$ConsentPromptBehaviorAdmin, [int]$ConsentPromptBehaviorUser, [switch]$PassThru ) Write-LogFunctionEntry $machines = Get-LabMachine -ComputerName $ComputerName if (-not $machines) { Write-Error 'The given machines could not be found' return } $functions = Get-Command -Name Get-MachineUacStatus, Set-MachineUacStatus, Sync-Parameter $variables = Get-Variable -Name PSBoundParameters Invoke-LabCommand -ActivityName 'Set Uac Status' -ComputerName $machines -ScriptBlock { Sync-Parameter -Command (Get-Command -Name Set-MachineUacStatus) Set-MachineUacStatus @ALBoundParameters } -Function $functions -Variable $variables if ($PassThru) { Get-LabMachineUacStatus -ComputerName $ComputerName } Write-LogFunctionExit } function Get-LabMachineUacStatus { # .ExternalHelp AutomatedLab.Help.xml [Cmdletbinding()] param( [Parameter(Mandatory)] [string[]]$ComputerName ) Write-LogFunctionEntry $machines = Get-LabMachine -ComputerName $ComputerName if (-not $machines) { Write-Error 'The given machines could not be found' return } Invoke-LabCommand -ActivityName 'Get Uac Status' -ComputerName $machines -ScriptBlock { Get-MachineUacStatus } -Function (Get-Command -Name Get-MachineUacStatus) -PassThru Write-LogFunctionExit } #endregion Get / Set-LabMachineUacStatus #region Test-LabMachineInternetConnectivity function Test-LabMachineInternetConnectivity { # .ExternalHelp AutomatedLab.Help.xml [OutputType([bool])] [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$ComputerName, [switch]$AsJob ) $cmd = { $result = 1..5 | ForEach-Object { Test-NetConnection www.microsoft.com -CommonTCPPort HTTP -InformationLevel Detailed -WarningAction SilentlyContinue Start-Sleep -Seconds 1 } #if two results are positive, return the first positive result, if all are negative, return the first negative result if (($result.TcpTestSucceeded | Where-Object { $_ -eq $true }).Count -ge 2) { $result | Where-Object TcpTestSucceeded -eq $true | Select-Object -First 1 } elseif (($result.TcpTestSucceeded | Where-Object { $_ -eq $false }).Count -eq 5) { $result | Where-Object TcpTestSucceeded -eq $false | Select-Object -First 1 } } if ($AsJob) { $job = Invoke-LabCommand -ComputerName $ComputerName -ActivityName "Testing Internet Connectivity of '$ComputerName'" ` -ScriptBlock $cmd -PassThru -NoDisplay -AsJob return $job } else { $result = Invoke-LabCommand -ComputerName $ComputerName -ActivityName "Testing Internet Connectivity of '$ComputerName'" ` -ScriptBlock $cmd -PassThru -NoDisplay return $result.TcpTestSucceeded } } #endregion Test-LabMachineInternetConnectivity |