AutomatedLabRemoting.psm1
#region New-LabPSSession function New-LabPSSession { param ( [Parameter(Mandatory, ParameterSetName = 'ByName', Position = 0)] [string[]]$ComputerName, [Parameter(Mandatory, ParameterSetName = 'ByMachine')] [AutomatedLab.Machine[]]$Machine, #this is used to recreate a broken session [Parameter(Mandatory, ParameterSetName = 'BySession')] [System.Management.Automation.Runspaces.PSSession]$Session, [switch]$UseLocalCredential, [switch]$DoNotUseCredSsp, [pscredential]$Credential, [int]$Retries = 2, [int]$Interval = 5, [switch]$UseSSL ) begin { Write-LogFunctionEntry $sessions = @() $lab = Get-Lab #Due to a problem in Windows 10 not being able to reach VMs from the host if (-not ($IsLinux -or $IsMacOs)) { netsh.exe interface ip delete arpcache | Out-Null } $testPortTimeout = (Get-LabConfigurationItem -Name Timeout_TestPortInSeconds) * 1000 } process { if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Machine = Get-LabVM -ComputerName $ComputerName -IncludeLinux if (-not $Machine) { Write-Error "There is no computer with the name '$ComputerName' in the lab" } } elseif ($PSCmdlet.ParameterSetName -eq 'BySession') { $internalSession = $Session $Machine = Get-LabVM -ComputerName $internalSession.LabMachineName -IncludeLinux if ($internalSession.Runspace.ConnectionInfo.AuthenticationMechanism -ne 'Credssp') { $DoNotUseCredSsp = $true } if ($internalSession.Runspace.ConnectionInfo.Credential.UserName -like "$($Machine.Name)*") { $UseLocalCredential = $true } } foreach ($m in $Machine) { $machineRetries = $Retries if ($Credential) { $cred = $Credential } elseif ($UseLocalCredential -and ($IsLinux -and $m.IsDomainJoined -and -not $m.HasDomainJoined)) { $cred = $m.GetLocalCredential($true) } elseif ($UseLocalCredential) { $cred = $m.GetLocalCredential() } else { $cred = $m.GetCredential($lab) } $param = @{} $param.Add('Name', "$($m)_$([guid]::NewGuid())") $param.Add('Credential', $cred) $param.Add('UseSSL', $false) if ($DoNotUseCredSsp) { $param.Add('Authentication', 'Default') } else { $param.Add('Authentication', 'Credssp') } if ($m.HostType -eq 'Azure') { $param.Add('ComputerName', $m.AzureConnectionInfo.DnsName) Write-PSFMessage "Azure DNS name for machine '$m' is '$($m.AzureConnectionInfo.DnsName)'" $param.Add('Port', $m.AzureConnectionInfo.Port) if ($UseSSL) { $param.Add('SessionOption', (New-PSSessionOption -SkipCACheck -SkipCNCheck)) $param.UseSSL = $true } } elseif ($m.HostType -eq 'HyperV' -or $m.HostType -eq 'VMWare') { $doNotUseGetHostEntry = Get-LabConfigurationItem -Name DoNotUseGetHostEntryInNewLabPSSession if (-not $doNotUseGetHostEntry) { $name = (Get-HostEntry -Hostname $m).IpAddress.IpAddressToString } if ($name) { Write-PSFMessage "Connecting to machine '$m' using the IP address '$name'" $param.Add('ComputerName', $name) } else { Write-PSFMessage "Connecting to machine '$m' using the DNS name '$m'" $param.Add('ComputerName', $m) } $param.Add('Port', 5985) } if ($m.OperatingSystemType -eq 'Linux') { Set-Item -Path WSMan:\localhost\Client\Auth\Basic -Value $true -Force $param['SessionOption'] = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck $param['UseSSL'] = $true $param['Port'] = 5986 $param['Authentication'] = 'Basic' } if ($IsLinux -or $IsMacOs) { $param['Authentication'] = 'Negotiate' } Write-PSFMessage ("Creating a new PSSession to machine '{0}:{1}' (UserName='{2}', Password='{3}', DoNotUseCredSsp='{4}')" -f $param.ComputerName, $param.Port, $cred.UserName, $cred.GetNetworkCredential().Password, $DoNotUseCredSsp) #session reuse. If there is a session to the machine available, return it, otherwise create a new session $internalSession = Get-PSSession | Where-Object { $_.ComputerName -eq $param.ComputerName -and $_.Runspace.ConnectionInfo.Port -eq $param.Port -and $_.Availability -eq 'Available' -and $_.Runspace.ConnectionInfo.AuthenticationMechanism -eq $param.Authentication -and $_.State -eq 'Opened' -and $_.Name -like "$($m)_*" -and $_.Runspace.ConnectionInfo.Credential.UserName -eq $param.Credential.UserName } if ($internalSession) { if ($internalSession.Runspace.ConnectionInfo.AuthenticationMechanism -eq 'CredSsp' -and -not $internalSession.ALLabSourcesMapped -and (Get-LabVM -ComputerName $internalSession.LabMachineName).HostType -eq 'Azure' ) { #remove the existing session if connecting to Azure LabSource did not work in case the session connects to an Azure VM. Write-ScreenInfo "Removing session to '$($internalSession.LabMachineName)' as ALLabSourcesMapped was false" -Type Warning Remove-LabPSSession -ComputerName $internalSession.LabMachineName $internalSession = $null } if ($internalSession.Count -eq 1) { Write-PSFMessage "Session $($internalSession.Name) is available and will be reused" $sessions += $internalSession } elseif ($internalSession.Count -ne 0) { $sessionsToRemove = $internalSession | Select-Object -Skip (Get-LabConfigurationItem -Name MaxPSSessionsPerVM) Write-PSFMessage "Found orphaned sessions. Removing $($sessionsToRemove.Count) sessions: $($sessionsToRemove.Name -join ', ')" $sessionsToRemove | Remove-PSSession Write-PSFMessage "Session $($internalSession[0].Name) is available and will be reused" #Replaced Select-Object with array indexing because of https://github.com/PowerShell/PowerShell/issues/9185 $sessions += ($internalSession | Where-Object State -eq 'Opened')[0] #| Select-Object -First 1 } } while (-not $internalSession -and $machineRetries -gt 0) { if (-not ($IsLinux -or $IsMacOs)) { netsh.exe interface ip delete arpcache | Out-Null } Write-PSFMessage "Testing port $($param.Port) on computer '$($param.ComputerName)'" $portTest = Test-Port -ComputerName $param.ComputerName -Port $param.Port -TCP -TcpTimeout $testPortTimeout if ($portTest.Open) { Write-PSFMessage 'Port was open, trying to create the session' $internalSession = New-PSSession @param -ErrorAction SilentlyContinue -ErrorVariable sessionError $internalSession | Add-Member -Name LabMachineName -MemberType ScriptProperty -Value { $this.Name.Substring(0, $this.Name.IndexOf('_')) } if ($internalSession) { Write-PSFMessage "Session to computer '$($param.ComputerName)' created" $sessions += $internalSession if ((Get-LabVM -ComputerName $internalSession.LabMachineName).HostType -eq 'Azure') { Connect-LWAzureLabSourcesDrive -Session $internalSession } } else { Write-PSFMessage -Message "Session to computer '$($param.ComputerName)' could not be created, waiting $Interval seconds ($machineRetries retries). The error was: '$($sessionError[0].FullyQualifiedErrorId)'" if ($Retries -gt 1) { Start-Sleep -Seconds $Interval } $machineRetries-- } } else { Write-PSFMessage 'Port was NOT open, cannot create session.' Start-Sleep -Seconds $Interval $machineRetries-- } } if (-not $internalSession) { if ($sessionError.Count -gt 0) { Write-Error -ErrorRecord $sessionError[0] } elseif ($machineRetries -lt 1) { if (-not $portTest.Open) { Write-Error -Message "Could not create a session to machine '$m' as the port is closed after $Retries retries." } else { Write-Error -Message "Could not create a session to machine '$m' after $Retries retries." } } } } } end { Write-LogFunctionExit -ReturnValue "Session IDs: $(($sessions.ID -join ', '))" $sessions } } #endregion New-LabPSSession #region Get-LabPSSession function Get-LabPSSession { [cmdletBinding()] [OutputType([System.Management.Automation.Runspaces.PSSession])] param ( [string[]]$ComputerName, [switch]$DoNotUseCredSsp ) $pattern = '\w+_[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}' if ($ComputerName) { $computers = Get-LabVM -ComputerName $ComputerName -IncludeLinux } else { $computers = Get-LabVM -IncludeLinux } if (-not $computers) { Write-Error 'The machines could not be found' -TargetObject $ComputerName } $sessions = foreach ($computer in $computers) { $session = Get-PSSession | Where-Object { $_.Name -match $pattern -and $_.Name -like "$($computer.Name)_*" } if (-not $session -and $ComputerName) { Write-Error "No session found for computer '$computer'" -TargetObject $computer } else { $session } } if ($DoNotUseCredSsp) { $sessions | Where-Object { $_.Runspace.ConnectionInfo.AuthenticationMechanism -ne 'CredSsp' } } else { $sessions } } #endregion Get-LabPSSession #region Remove-LabPSSession function Remove-LabPSSession { [cmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'ByName')] [string[]]$ComputerName, [Parameter(Mandatory, ParameterSetName = 'ByMachine')] [AutomatedLab.Machine[]]$Machine, [Parameter(ParameterSetName = 'All')] [switch]$All ) Write-LogFunctionEntry if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Machine = Get-LabVM -ComputerName $ComputerName -IncludeLinux } if ($PSCmdlet.ParameterSetName -eq 'All') { $Machine = Get-LabVM -All -IncludeLinux } $sessions = foreach ($m in $Machine) { $param = @{} if ($m.HostType -eq 'Azure') { $param.Add('ComputerName', $m.AzureConnectionInfo.DnsName) $param.Add('Port', $m.AzureConnectionInfo.Port) } elseif ($m.HostType -eq 'HyperV' -or $m.HostType -eq 'VMWare') { if (Get-LabConfigurationItem -Name DoNotUseGetHostEntryInNewLabPSSession) { $param.Add('ComputerName', $m.Name) } else { $param.Add('ComputerName', (Get-HostEntry -Hostname $m).IpAddress.IpAddressToString) } $param.Add('Port', 5985) } Get-PSSession | Where-Object { $_.ComputerName -eq $param.ComputerName -and $_.Runspace.ConnectionInfo.Port -eq $param.Port -and $_.Name -like "$($m)_*" } } $sessions | Remove-PSSession -ErrorAction SilentlyContinue Write-PSFMessage "Removed $($sessions.Count) PSSessions..." Write-LogFunctionExit } #endregion Remove-LabPSSession #region Enter-LabPSSession function Enter-LabPSSession { param ( [Parameter(Mandatory, ParameterSetName = 'ByName', Position = 0)] [string]$ComputerName, [Parameter(Mandatory, ParameterSetName = 'ByMachine', Position = 0)] [AutomatedLab.Machine]$Machine, [switch]$DoNotUseCredSsp, [switch]$UseLocalCredential ) if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Machine = Get-LabVM -ComputerName $ComputerName -IncludeLinux } if ($Machine) { $session = New-LabPSSession -Machine $Machine -DoNotUseCredSsp:$DoNotUseCredSsp -UseLocalCredential:$UseLocalCredential $session | Enter-PSSession } else { Write-Error 'The specified machine could not be found in the lab.' } } #endregion Enter-LabPSSession #region Invoke-LabCommand function Invoke-LabCommand { [cmdletBinding()] param ( [string]$ActivityName = '<unnamed>', [Parameter(Mandatory, ParameterSetName = 'ScriptBlockFileContentDependency', Position = 0)] [Parameter(Mandatory, ParameterSetName = 'ScriptFileContentDependency', Position = 0)] [Parameter(Mandatory, ParameterSetName = 'ScriptFileNameContentDependency', Position = 0)] [Parameter(Mandatory, ParameterSetName = 'Script', Position = 0)] [Parameter(Mandatory, ParameterSetName = 'ScriptBlock', Position = 0)] [Parameter(Mandatory, ParameterSetName = 'PostInstallationActivity', Position = 0)] [string[]]$ComputerName, [Parameter(Mandatory, ParameterSetName = 'ScriptBlockFileContentDependency', Position = 1)] [Parameter(Mandatory, ParameterSetName = 'ScriptBlock', Position = 1)] [scriptblock]$ScriptBlock, [Parameter(Mandatory, ParameterSetName = 'ScriptFileContentDependency')] [Parameter(Mandatory, ParameterSetName = 'Script')] [string]$FilePath, [Parameter(Mandatory, ParameterSetName = 'ScriptFileNameContentDependency')] [string]$FileName, [Parameter(ParameterSetName = 'ScriptFileNameContentDependency')] [Parameter(Mandatory, ParameterSetName = 'ScriptBlockFileContentDependency')] [Parameter(Mandatory, ParameterSetName = 'ScriptFileContentDependency')] [string]$DependencyFolderPath, [Parameter(ParameterSetName = 'PostInstallationActivity')] [switch]$PostInstallationActivity, [Parameter(ParameterSetName = 'PostInstallationActivity')] [string[]]$CustomRoleName, [object[]]$ArgumentList, [switch]$DoNotUseCredSsp, [switch]$UseLocalCredential, [pscredential]$Credential, [System.Management.Automation.PSVariable[]]$Variable, [System.Management.Automation.FunctionInfo[]]$Function, [Parameter(ParameterSetName = 'ScriptBlock')] [Parameter(ParameterSetName = 'ScriptBlockFileContentDependency')] [Parameter(ParameterSetName = 'ScriptFileContentDependency')] [Parameter(ParameterSetName = 'Script')] [Parameter(ParameterSetName = 'ScriptFileNameContentDependency')] [int]$Retries, [Parameter(ParameterSetName = 'ScriptBlock')] [Parameter(ParameterSetName = 'ScriptBlockFileContentDependency')] [Parameter(ParameterSetName = 'ScriptFileContentDependency')] [Parameter(ParameterSetName = 'Script')] [Parameter(ParameterSetName = 'ScriptFileNameContentDependency')] [int]$RetryIntervalInSeconds, [int]$ThrottleLimit = 32, [switch]$AsJob, [switch]$PassThru, [switch]$NoDisplay ) Write-LogFunctionEntry $customRoleCount = 0 if ($PSCmdlet.ParameterSetName -in 'Script', 'ScriptBlock', 'ScriptFileContentDependency', 'ScriptBlockFileContentDependency','ScriptFileNameContentDependency') { if (-not $Retries) { $Retries = Get-LabConfigurationItem -Name InvokeLabCommandRetries } if (-not $RetryIntervalInSeconds) { $RetryIntervalInSeconds = Get-LabConfigurationItem -Name InvokeLabCommandRetryIntervalInSeconds } } if ($AsJob) { Write-ScreenInfo -Message "Executing lab command activity: '$ActivityName' on machines '$($ComputerName -join ', ')'" -TaskStart Write-ScreenInfo -Message 'Activity started in background' -TaskEnd } else { Write-ScreenInfo -Message "Executing lab command activity: '$ActivityName' on machines '$($ComputerName -join ', ')'" -TaskStart Write-ScreenInfo -Message 'Waiting for completion' } Write-PSFMessage -Message "Executing lab command activity '$ActivityName' on machines '$($ComputerName -join ', ')'" #required to suppress verbose messages, warnings and errors Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState if (-not (Get-LabVM -IncludeLinux)) { Write-LogFunctionExitWithError -Message 'No machine definitions imported, so there is nothing to do. Please use Import-Lab first' return } if ($FilePath) { $isLabPathIsOnLabAzureLabSourcesStorage = if ((Get-Lab).DefaultVirtualizationEngine -eq 'Azure') { Test-LabPathIsOnLabAzureLabSourcesStorage -Path $FilePath } if ($isLabPathIsOnLabAzureLabSourcesStorage) { Write-PSFMessage "$FilePath is on Azure. Skipping test." } elseif (-not (Test-Path -Path $FilePath)) { Write-LogFunctionExitWithError -Message "$FilePath is not on Azure and does not exist" return } } if ($PostInstallationActivity) { $machines = Get-LabVM -ComputerName $ComputerName | Where-Object { $_.PostInstallationActivity -and -not $_.SkipDeployment } if (-not $machines) { Write-PSFMessage 'There are no machine with PostInstallationActivity defined, exiting...' return } } else { $machines = Get-LabVM -ComputerName $ComputerName -IncludeLinux } if (-not $machines) { Write-ScreenInfo "Cannot invoke the command '$ActivityName', as the specified machines ($($ComputerName -join ', ')) could not be found in the lab." -Type Warning return } if ('Stopped' -in (Get-LabVMStatus -ComputerName $machines -AsHashTable).Values) { Start-LabVM -ComputerName $machines -Wait } if ($PostInstallationActivity) { Write-ScreenInfo -Message 'Performing post-installations tasks defined for each machine' -TaskStart -OverrideNoDisplay $results = @() foreach ($machine in $machines) { foreach ($item in $machine.PostInstallationActivity) { if ($item.RoleName -notin $CustomRoleName -and $CustomRoleName.Count -gt 0) { Write-PSFMessage "Skipping installing custom role $($item.RoleName) as it is not part of the parameter `$CustomRoleName" continue } if ($item.IsCustomRole) { Write-ScreenInfo "Installing Custom Role '$(Split-Path -Path $item.DependencyFolder -Leaf)' on machine '$machine'" -TaskStart -OverrideNoDisplay $customRoleCount++ #if there is a HostStart.ps1 script for the role $hostStartPath = Join-Path -Path $item.DependencyFolder -ChildPath 'HostStart.ps1' if (Test-Path -Path $hostStartPath) { $hostStartScript = Get-Command -Name $hostStartPath $hostStartParam = Sync-Parameter -Command $hostStartScript -Parameters $item.Properties if ($hostStartScript.Parameters.ContainsKey('ComputerName')) { $hostStartParam['ComputerName'] = $machine.Name } $results += & $hostStartPath @hostStartParam } } $ComputerName = $machine.Name $param = @{} $param.Add('ComputerName', $ComputerName) Write-PSFMessage "Creating session to computers) '$ComputerName'" $session = New-LabPSSession -ComputerName $ComputerName -DoNotUseCredSsp:$item.DoNotUseCredSsp if (-not $session) { Write-LogFunctionExitWithError "Could not create a session to machine '$ComputerName'" return } $param.Add('Session', $session) if ($item.DependencyFolder.Value) { $param.Add('DependencyFolderPath', $item.DependencyFolder.Value) } if ($item.ScriptFileName) { $param.Add('ScriptFileName',$item.ScriptFileName) } if ($item.ScriptFilePath) { $param.Add('ScriptFilePath', $item.ScriptFilePath) } if ($item.KeepFolder) { $param.Add('KeepFolder', $item.KeepFolder) } if ($item.ActivityName) { $param.Add('ActivityName', $item.ActivityName) } if ($Retries) { $param.Add('Retries', $Retries) } if ($RetryIntervalInSeconds) { $param.Add('RetryIntervalInSeconds', $RetryIntervalInSeconds) } $param.AsJob = $true $param.PassThru = $PassThru $param.Verbose = $VerbosePreference if ($PSBoundParameters.ContainsKey('ThrottleLimit')) { $param.Add('ThrottleLimit', $ThrottleLimit) } $scriptFullName = Join-Path -Path $param.DependencyFolderPath -ChildPath $param.ScriptFileName if ($item.Properties.Count -and (Test-Path -Path $scriptFullName)) { $script = Get-Command -Name $scriptFullName $temp = Sync-Parameter -Command $script -Parameters $item.Properties Add-VariableToPSSession -Session $session -PSVariable (Get-Variable -Name temp) $param.ParameterVariableName = 'temp' } if ($item.IsCustomRole) { if (Test-Path -Path $scriptFullName) { $param.PassThru = $true $results += Invoke-LWCommand @param } } else { $results += Invoke-LWCommand @param } if ($item.IsCustomRole) { Wait-LWLabJob -Job ($results | Where-Object { $_ -is [System.Management.Automation.Job]} )-ProgressIndicator 15 -NoDisplay #if there is a HostEnd.ps1 script for the role $hostEndPath = Join-Path -Path $item.DependencyFolder -ChildPath 'HostEnd.ps1' if (Test-Path -Path $hostEndPath) { $hostEndScript = Get-Command -Name $hostEndPath $hostEndParam = Sync-Parameter -Command $hostEndScript -Parameters $item.Properties if ($hostEndScript.Parameters.ContainsKey('ComputerName')) { $hostEndParam['ComputerName'] = $machine.Name } $results += & $hostEndPath @hostEndParam } } } } if ($customRoleCount) { $jobs = $results | Where-Object { $_ -is [System.Management.Automation.Job] -and $_.State -eq 'Running' } if ($jobs) { Write-ScreenInfo -Message "Waiting on $($results.Count) custom role installations to finish..." -NoNewLine -OverrideNoDisplay Wait-LWLabJob -Job $jobs -Timeout 60 -NoDisplay } else { Write-ScreenInfo -Message "$($customRoleCount) custom role installation finished." -OverrideNoDisplay } } Write-ScreenInfo -Message 'Post-installations done' -TaskEnd -OverrideNoDisplay } else { $param = @{} $param.Add('ComputerName', $machines) Write-PSFMessage "Creating session to computer(s) '$machines'" $session = @(New-LabPSSession -ComputerName $machines -DoNotUseCredSsp:$DoNotUseCredSsp -UseLocalCredential:$UseLocalCredential -Credential $credential) if (-not $session) { Write-LogFunctionExitWithError "Could not create a session to machine '$machines'" return } if ($Function) { Write-PSFMessage "Adding functions '$($Function -join ',')' to session" $Function | Add-FunctionToPSSession -Session $session } if ($Variable) { Write-PSFMessage "Adding variables '$($Variable -join ',')' to session" $Variable | Add-VariableToPSSession -Session $session } $param.Add('Session', $session) if ($FilePath) { $scriptContent = if ($isLabPathIsOnLabAzureLabSourcesStorage) { #if the script is on an Azure file storage, the host machine cannot access it. The read operation is done on the first Azure machine. Invoke-LabCommand -ComputerName ($machines | Where-Object HostType -eq 'Azure')[0] -ScriptBlock { Get-Content -Path $FilePath -Raw } -Variable (Get-Variable -Name FilePath) -NoDisplay -PassThru } else { Get-Content -Path $FilePath -Raw } $ScriptBlock = [scriptblock]::Create($scriptContent) } if ($ScriptBlock) { $param.Add('ScriptBlock', $ScriptBlock) } if ($Retries) { $param.Add('Retries', $Retries) } if ($RetryIntervalInSeconds) { $param.Add('RetryIntervalInSeconds', $RetryIntervalInSeconds) } if ($FileName) { $param.Add('ScriptFileName', $FileName) } if ($ActivityName) { $param.Add('ActivityName', $ActivityName) } if ($ArgumentList) { $param.Add('ArgumentList', $ArgumentList) } if ($DependencyFolderPath) { $param.Add('DependencyFolderPath', $DependencyFolderPath) } $param.PassThru = $PassThru $param.AsJob = $AsJob $param.Verbose = $VerbosePreference if ($PSBoundParameters.ContainsKey('ThrottleLimit')) { $param.Add('ThrottleLimit', $ThrottleLimit) } $results = Invoke-LWCommand @param } if ($AsJob) { Write-ScreenInfo -Message 'Activity started in background' -TaskEnd } else { Write-ScreenInfo -Message 'Activity done' -TaskEnd } if ($PassThru) { $results } Write-LogFunctionExit } #endregion Invoke-LabCommand #region New-LabCimSession function New-LabCimSession { [CmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'ByName', Position = 0)] [string[]] $ComputerName, [Parameter(Mandatory, ParameterSetName = 'ByMachine')] [AutomatedLab.Machine[]] $Machine, #this is used to recreate a broken session [Parameter(Mandatory, ParameterSetName = 'BySession')] [Microsoft.Management.Infrastructure.CimSession] $Session, [switch] $UseLocalCredential, [switch] $DoNotUseCredSsp, [pscredential] $Credential, [int] $Retries = 2, [int] $Interval = 5, [switch] $UseSSL ) begin { Write-LogFunctionEntry $sessions = @() $lab = Get-Lab #Due to a problem in Windows 10 not being able to reach VMs from the host $testPortTimeout = (Get-LabConfigurationItem -Name Timeout_TestPortInSeconds) * 1000 } process { if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Machine = Get-LabVM -ComputerName $ComputerName -IncludeLinux if (-not $Machine) { Write-Error "There is no computer with the name '$ComputerName' in the lab" } } elseif ($PSCmdlet.ParameterSetName -eq 'BySession') { $internalSession = $Session $Machine = Get-LabVM -ComputerName $internalSession.LabMachineName -IncludeLinux } foreach ($m in $Machine) { $machineRetries = $Retries if ($Credential) { $cred = $Credential } elseif ($UseLocalCredential -and ($m.IsDomainJoined -and -not $m.HasDomainJoined)) { $cred = $m.GetLocalCredential($true) } elseif ($UseLocalCredential) { $cred = $m.GetLocalCredential() } else { $cred = $m.GetCredential($lab) } $param = @{} $param.Add('Name', "$($m)_$([guid]::NewGuid())") $param.Add('Credential', $cred) if ($DoNotUseCredSsp) { $param.Add('Authentication', 'Default') } else { $param.Add('Authentication', 'Credssp') } if ($m.HostType -eq 'Azure') { $param.Add('ComputerName', $m.AzureConnectionInfo.DnsName) Write-PSFMessage "Azure DNS name for machine '$m' is '$($m.AzureConnectionInfo.DnsName)'" $param.Add('Port', $m.AzureConnectionInfo.Port) if ($UseSSL) { $param.Add('SessionOption', (New-CimSessionOption -SkipCACheck -SkipCNCheck -UseSsl)) } } elseif ($m.HostType -eq 'HyperV' -or $m.HostType -eq 'VMWare') { $doNotUseGetHostEntry = Get-LabConfigurationItem -Name DoNotUseGetHostEntryInNewLabPSSession if (-not $doNotUseGetHostEntry) { $name = (Get-HostEntry -Hostname $m).IpAddress.IpAddressToString } if ($name) { Write-PSFMessage "Connecting to machine '$m' using the IP address '$name'" $param.Add('ComputerName', $name) } else { Write-PSFMessage "Connecting to machine '$m' using the DNS name '$m'" $param.Add('ComputerName', $m) } $param.Add('Port', 5985) } if ($m.OperatingSystemType -eq 'Linux') { Set-Item -Path WSMan:\localhost\Client\Auth\Basic -Value $true -Force $param['SessionOption'] = New-CimSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck -UseSsl $param['Port'] = 5986 $param['Authentication'] = 'Basic' } if ($IsLinux -or $IsMacOs) { $param['Authentication'] = 'Negotiate' } Write-PSFMessage ("Creating a new CIM Session to machine '{0}:{1}' (UserName='{2}', Password='{3}', DoNotUseCredSsp='{4}')" -f $param.ComputerName, $param.Port, $cred.UserName, $cred.GetNetworkCredential().Password, $DoNotUseCredSsp) #session reuse. If there is a session to the machine available, return it, otherwise create a new session $internalSession = Get-CimSession | Where-Object { $_.ComputerName -eq $param.ComputerName -and $_.TestConnection() -and $_.Name -like "$($m)_*" } if ($internalSession) { if ($internalSession.Runspace.ConnectionInfo.AuthenticationMechanism -eq 'CredSsp' -and (Get-LabVM -ComputerName $internalSession.LabMachineName).HostType -eq 'Azure') { #remove the existing session if connecting to Azure LabSource did not work in case the session connects to an Azure VM. Write-ScreenInfo "Removing session to '$($internalSession.LabMachineName)' as ALLabSourcesMapped was false" -Type Warning Remove-LabCimSession -ComputerName $internalSession.LabMachineName $internalSession = $null } if ($internalSession.Count -eq 1) { Write-PSFMessage "Session $($internalSession.Name) is available and will be reused" $sessions += $internalSession } elseif ($internalSession.Count -ne 0) { $sessionsToRemove = $internalSession | Select-Object -Skip (Get-LabConfigurationItem -Name MaxPSSessionsPerVM) Write-PSFMessage "Found orphaned sessions. Removing $($sessionsToRemove.Count) sessions: $($sessionsToRemove.Name -join ', ')" $sessionsToRemove | Remove-CimSession Write-PSFMessage "Session $($internalSession[0].Name) is available and will be reused" #Replaced Select-Object with array indexing because of https://github.com/PowerShell/PowerShell/issues/9185 $sessions += ($internalSession | Where-Object State -eq 'Opened')[0] #| Select-Object -First 1 } } while (-not $internalSession -and $machineRetries -gt 0) { Write-PSFMessage "Testing port $($param.Port) on computer '$($param.ComputerName)'" $portTest = Test-Port -ComputerName $param.ComputerName -Port $param.Port -TCP -TcpTimeout $testPortTimeout if ($portTest.Open) { Write-PSFMessage 'Port was open, trying to create the session' $internalSession = New-CimSession @param -ErrorAction SilentlyContinue -ErrorVariable sessionError $internalSession | Add-Member -Name LabMachineName -MemberType ScriptProperty -Value { $this.Name.Substring(0, $this.Name.IndexOf('_')) } if ($internalSession) { Write-PSFMessage "Session to computer '$($param.ComputerName)' created" $sessions += $internalSession } else { Write-PSFMessage -Message "Session to computer '$($param.ComputerName)' could not be created, waiting $Interval seconds ($machineRetries retries). The error was: '$($sessionError[0].FullyQualifiedErrorId)'" if ($Retries -gt 1) { Start-Sleep -Seconds $Interval } $machineRetries-- } } else { Write-PSFMessage 'Port was NOT open, cannot create session.' Start-Sleep -Seconds $Interval $machineRetries-- } } if (-not $internalSession) { if ($sessionError.Count -gt 0) { Write-Error -ErrorRecord $sessionError[0] } elseif ($machineRetries -lt 1) { if (-not $portTest.Open) { Write-Error -Message "Could not create a session to machine '$m' as the port is closed after $Retries retries." } else { Write-Error -Message "Could not create a session to machine '$m' after $Retries retries." } } } } } end { Write-LogFunctionExit -ReturnValue "Session IDs: $(($sessions.ID -join ', '))" $sessions } } #endregion #region Get-LabCimSession function Get-LabCimSession { [CmdletBinding()] [OutputType([Microsoft.Management.Infrastructure.CimSession])] param ( [string[]] $ComputerName, [switch] $DoNotUseCredSsp ) $pattern = '\w+_[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}' if ($ComputerName) { $computers = Get-LabVM -ComputerName $ComputerName -IncludeLinux } else { $computers = Get-LabVM -IncludeLinux } if (-not $computers) { Write-Error 'The machines could not be found' -TargetObject $ComputerName } foreach ($computer in $computers) { $session = Get-CimSession | Where-Object { $_.Name -match $pattern -and $_.Name -like "$($computer.Name)_*" } if (-not $session -and $ComputerName) { Write-Error "No session found for computer '$computer'" -TargetObject $computer } else { $session } } } #endregion #region Remove-LabCimSession function Remove-LabCimSession { [CmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'ByName')] [string[]] $ComputerName, [Parameter(Mandatory, ParameterSetName = 'ByMachine')] [AutomatedLab.Machine[]] $Machine, [Parameter(ParameterSetName = 'All')] [switch] $All ) Write-LogFunctionEntry if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Machine = Get-LabVM -ComputerName $ComputerName -IncludeLinux } if ($PSCmdlet.ParameterSetName -eq 'All') { $Machine = Get-LabVM -All -IncludeLinux } $sessions = foreach ($m in $Machine) { $param = @{} if ($m.HostType -eq 'Azure') { $param.Add('ComputerName', $m.AzureConnectionInfo.DnsName) $param.Add('Port', $m.AzureConnectionInfo.Port) } elseif ($m.HostType -eq 'HyperV' -or $m.HostType -eq 'VMWare') { if (Get-LabConfigurationItem -Name DoNotUseGetHostEntryInNewLabPSSession) { $param.Add('ComputerName', $m.Name) } else { $param.Add('ComputerName', (Get-HostEntry -Hostname $m).IpAddress.IpAddressToString) } $param.Add('Port', 5985) } Get-CimSession | Where-Object { $_.ComputerName -eq $param.ComputerName -and $_.Name -like "$($m)_*" } } $sessions | Remove-CimSession -ErrorAction SilentlyContinue Write-PSFMessage "Removed $($sessions.Count) PSSessions..." Write-LogFunctionExit } #endregion |