NTS.Tools.MSHyperV.psm1
function New-VMVolume { <# .Description Creates a storage pool, virtual disk and volume intended for VMs RAID Level will always be 0, so be carefull .Parameter VMDriveLetter Letter for the volume .Parameter StoragePoolName Name of the StoragePool .Parameter VirtualDiskName Name of the VirtualDisk .Parameter VMVolumeName Name of the Volume .Parameter VDiskRaidLevel RAID Level of the virtual disk, allowed is Simple, Mirror, Parity .Example # Creates a volume with the letter W New-VMVolume -VMDriveLetter 'W' .NOTES - There must be at least one other disk in addition to disk 0. - If an NVMe disk is present, only this is taken #> param ( [Parameter(Mandatory = $false)] [char] $VMDriveLetter = 'V', [Parameter(Mandatory = $false)] [string] $StoragePoolName = "Pool01", [Parameter(Mandatory = $false)] [string] $VirtualDiskName = "VDisk01", [Parameter(Mandatory = $false)] [string] $VMVolumeName = "VMs", [Parameter(Mandatory = $false)] [ValidateSet("Simple", "Mirror", "Parity")] [string] $VDiskRaidLevel = "Simple", [Parameter(Mandatory = $false)] [int] $VDiskPhysicalDiskRedundancy = 1 ) $ErrorActionPreference = 'Stop' try { if ($null -eq (Get-Volume -DriveLetter $VMDriveLetter -ErrorAction SilentlyContinue)) { if ($null -eq (Get-StoragePool -FriendlyName $StoragePoolName -ErrorAction SilentlyContinue)) { $PhysicalDisks = Get-PhysicalDisk -CanPool $true | Where-Object -FilterScript { $PSitem.Bustype -ne "USB" } $NVMe_Devices = $PhysicalDisks | Where-Object -FilterScript { $PSItem.Bustype -eq "NVMe" -and $PSitem.Size -gt 256GB } $Non_NVMe_Devices = $PhysicalDisks | Where-Object -FilterScript { $PSItem.Bustype -ne "NVMe" } if ($null -ne $NVMe_Devices) { $SelectedDisks = $NVMe_Devices } else { $SelectedDisks = $Non_NVMe_Devices } $StorageSubSystemFriendlyName = (Get-StorageSubSystem -FriendlyName "*Windows*").FriendlyName Write-Output "$($env:COMPUTERNAME): create storage pool $($StoragePoolName)" if ($null -eq $SelectedDisks) { throw "no disks were found that can be used for the storagepool" } if ($null -ne $NVMe_Devices -and ($SelectedDisks | Measure-Object).Count) { Initialize-Disk -Number $SelectedDisks.DeviceId -PartitionStyle GPT | Out-Null New-Volume -DiskNumber $SelectedDisks.DeviceId -FriendlyName $VMVolumeName -FileSystem ReFS -DriveLetter $VMDriveLetter | Out-Null } else { New-StoragePool -StorageSubSystemFriendlyName $StorageSubSystemFriendlyName -FriendlyName $StoragePoolName -PhysicalDisks $SelectedDisks | Out-Null if ($null -eq (Get-VirtualDisk -FriendlyName $VirtualDiskName -ErrorAction SilentlyContinue)) { Write-Output "$($env:COMPUTERNAME): create vdisk $($VirtualDiskName)" if ($VDiskRaidLevel -ne "Simple") { New-VirtualDisk -StoragePoolFriendlyName $StoragePoolName ` -FriendlyName $VirtualDiskName -UseMaximumSize ` -ProvisioningType Fixed ` -ResiliencySettingName $VDiskRaidLevel ` -PhysicalDiskRedundancy $VDiskPhysicalDiskRedundancy | Out-Null } else { New-VirtualDisk -StoragePoolFriendlyName $StoragePoolName ` -FriendlyName $VirtualDiskName -UseMaximumSize ` -ProvisioningType Fixed ` -ResiliencySettingName $VDiskRaidLevel | Out-Null } Initialize-Disk -FriendlyName $VirtualDiskName -PartitionStyle GPT | Out-Null $VDiskNumber = (Get-Disk -FriendlyName $VirtualDiskName).Number Write-Output "$($env:COMPUTERNAME): create volume $($VMVolumeName)" New-Volume -DiskNumber $VDiskNumber -FriendlyName $VMVolumeName -FileSystem ReFS -DriveLetter $VMDriveLetter | Out-Null } else { Write-Output "$($env:COMPUTERNAME): virtual disk $($VirtualDiskName) already exists - skipping" } } } else { Write-Output "$($env:COMPUTERNAME): pool $($StoragePoolName) already exists - skipping" } } else { Write-Output "$($env:COMPUTERNAME): volume $($VMDriveLetter) already exists - skipping" } } catch { throw "$($env:COMPUTERNAME): error during creation of vm volume: $($PSItem.Exception.Message)" } } function New-VMVSwitch { <# .Description Creates a VM switch based on a network card with the Up state. 10Gbit NICs are preferred .Parameter Name Name of the VM Switch .Example # Creates a new VM Switch with the name IC New-VMVSwitch -Name 'IC' .NOTES there must be at least one nic with status 'up' #> param ( # Course Shortcut [Parameter(Mandatory = $false)] [string] $Name = "LAN" ) try { if ($null -eq (Get-VMSwitch -Name $Name -ErrorAction SilentlyContinue)) { $pNICs = Get-NetAdapter -Physical | Where-Object -Property Status -eq "UP" $10G_NICs = $pNICs | Where-Object -Property LinkSpeed -EQ "10 Gbps" $1G_NICs = $pNICs | Where-Object -Property LinkSpeed -EQ "1 Gbps" if ($10G_NICs) { $Selected_NIC = $10G_NICs[0] } elseif ($1G_NICs) { $Selected_NIC = $1G_NICs[0] } else { $Selected_NIC = (Get-NetAdapter -Physical | Where-Object -Property Status -eq "UP")[0] } Write-Output "$($env:COMPUTERNAME): create vswitch $($Name)" New-VMSwitch -Name $Name -NetAdapterName $Selected_NIC.Name -AllowManagementOS $false | Out-Null Add-VMNetworkAdapter -ManagementOS -SwitchName $Name -Name "vNIC-$($Name)" Rename-NetAdapter -Name $Selected_NIC.Name -NewName "pNIC-$($Name)" } else { Write-Output "$($env:COMPUTERNAME): virtual vswitch $($Name) already exists - skipping" } } catch { throw "$($env:COMPUTERNAME): error during creation of virtual switch: $($PSItem.Exception.Message)" } } function Register-VM_in_CM { <# .Description Registers the VM Objects in ConfigMgr with its MAC address for Required Deployments .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Parameter CM_Siteserver_FQDN FQDN of ConfigMgr .Parameter CM_Credentials Credentials of a user that can create/edit/delete CMDevices and add them to a Collection. Should be able to start a collection update .Example # Registers the VMs from $VM_Config with $CM_Siteserver_FQDN Register-VM_in_CM -VM_Config_Obj $VM_Config -CM_Siteserver_FQDN $CM_Siteserver_FQDN -CM_Credentials $CM_Credentials .NOTES the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [hashtable] $VM_Config_Obj, [Parameter(Mandatory = $true)] [string] $CM_Siteserver_FQDN, [Parameter(Mandatory = $true)] [PSCredential] $CM_Credentials ) Confirm-VMPresence -VM_Config_Obj $VM_Config_Obj try { Invoke-Command -ComputerName $CM_Siteserver_FQDN -Credential $CM_Credentials -ScriptBlock { function Start-CMCollectionUpdate { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $CollectionName, [Parameter(Mandatory = $false)] [int] $NotBeforeMinutues = 2 ) $Collection = Get-CMCollection -Name $CollectionName $RefreshTime = (Get-Date).AddHours(-1) - $Collection.IncrementalEvaluationLastRefreshTime if ($RefreshTime.TotalMinutes -gt $NotBeforeMinutues) { Write-Output "$($env:COMPUTERNAME): doing cm collection update on $($Collection.Name)" Invoke-CMCollectionUpdate -CollectionId $Collection.CollectionID Start-Sleep -Seconds 5 } } function Confirm-CMCollectionMembership { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $DeviceName, [Parameter(Mandatory = $true)] [string] $CollectionName ) $Device_Exists_In_Collection = $false $Counter = 0 Write-Output "$($DeviceName) - $("{0:d2}" -f $Counter): checking collection memberships in $($CollectionName)" while ($Device_Exists_In_Collection -eq $false -and $Counter -lt 40) { $CurrentCollectionMembers = Get-CMCollectionMember -CollectionName $CollectionName -Name $DeviceName if ($null -ne $CurrentCollectionMembers) { $Device_Exists_In_Collection = $true } else { Write-Output "$($DeviceName) - $("{0:d2}" -f $Counter): device not found in collection $($CollectionName)" Start-Sleep -Seconds 10 $Counter++ } if ($Counter -eq 12 -or $Counter -eq 24 -or $Counter -eq 36) { Start-CMCollectionUpdate -CollectionName $CollectionName } } if ($Counter -ge 40) { throw "$($DeviceName) - $("{0:d2}" -f $Counter): could not find in the collection $($CollectionName)" } else { Write-Output "$($DeviceName) - $("{0:d2}" -f $Counter): $($CollectionName) - found, continuing" } } $VM_Config_Obj = $using:VM_Config_Obj $CMPSDriveName = "CMPS-$(Get-Random)" $CM_Collection_All_Systems_ID = "SMS00001" # load configmgr ps module Import-Module -Name "ConfigurationManager" New-PSDrive -Name $CMPSDriveName -PSProvider "CMSite" -Root $using:CM_Siteserver_FQDN -Description "Primary site" | Out-Null Set-Location -Path "$($CMPSDriveName):\" $CM_Collection_All_Systems_Name = (Get-CMCollection -Id $CM_Collection_All_Systems_ID).Name $CM_SiteCode = (Get-CMSite).SiteCode $CM_Devices_Names = $VM_Config_Obj.Keys | Sort-Object # checking existing devices try { $CM_Devices_Names | ForEach-Object { $Temp_CMDevice = Get-CMDevice -Name $PSItem if (($null -ne $Temp_CMDevice) -and ($Temp_CMDevice.MACAddress.ToString().Replace(":", "") -ne $VM_Config_Obj.$PSItem.MAC)) { Write-Output "$($PSItem): removing existing computer info in configmgr - macaddress $($VM_Config_Obj.$PSItem.MAC), because the mac address is not correct" Remove-CMDevice -Name $PSItem -Force -Confirm:$false } } } catch { throw "error checking/removing device in configmgr - $($PSItem.Exception.Message)" } # check destination collection existance try { $CM_Devices_Names | ForEach-Object { if ($null -eq (Get-CMCollection -Name $VM_Config_Obj.$PSItem.CM_Collection_Name)) { throw "collection $($VM_Config_Obj.$PSItem.CM_Collection_Name) does not existing" } } } catch { throw "error checking collection in configmgr - $($PSItem.Exception.Message)" } Start-Sleep -Seconds 10 # import device try { $CM_Devices_Names | ForEach-Object { $Temp_CMDevice = Get-CMDevice -Name $PSItem if ($null -eq $Temp_CMDevice) { Write-Output "$($PSItem): creating computer info in configmgr - macaddress $($VM_Config_Obj.$PSItem.MAC)" Import-CMComputerInformation -CollectionName $CM_Collection_All_Systems_Name -ComputerName $PSItem -MacAddress $VM_Config_Obj.$PSItem.MAC } } } catch { throw "error adding device in configmgr - $($PSItem.Exception.Message)" } # add device to target collection try { # checking all system refreshinterval Start-CMCollectionUpdate -CollectionName $CM_Collection_All_Systems_Name Start-Sleep -Seconds 10 # check collection membership all systems $CM_Devices_Names | ForEach-Object { Confirm-CMCollectionMembership -DeviceName $PSItem -CollectionName $CM_Collection_All_Systems_Name } # create membership rule $CM_Devices_Names | ForEach-Object { Add-CMDeviceCollectionDirectMembershipRule -CollectionName $VM_Config_Obj.$PSItem.CM_Collection_Name -ResourceID (Get-CMDevice -Name $PSItem).ResourceID } Start-Sleep -Seconds 5 # check collection membership target $CM_Devices_Names | ForEach-Object { Confirm-CMCollectionMembership -DeviceName $PSItem -CollectionName $VM_Config_Obj.$PSItem.CM_Collection_Name } } catch { throw "error adding device to target collection - $($PSItem.Exception.Message)" } # remove collections, so there is only the targeted try { $CM_Devices_Names | ForEach-Object { $Temp_CMDevice = Get-CMDevice -Name $PSItem $TargetCollection = Get-CMCollection -Name $($VM_Config_Obj.$PSItem.CM_Collection_Name) if ($null -ne $Temp_CMDevice) { $Collections = Get-CimInstance -Namespace "root/Sms/site_$($CM_SiteCode)" -ClassName "SMS_FullCollectionMembership" -Filter "ResourceID = $($Temp_CMDevice.ResourceID)" | ` Where-Object -Property CollectionID -ne $CM_Collection_All_Systems_ID | ` Where-Object -Property CollectionID -ne $TargetCollection.CollectionID if ($null -ne $Collections) { $Collections | ForEach-Object { $MembershipRule = Get-CMCollectionDirectMembershipRule -CollectionId $PSItem.CollectionID | Where-Object -Property RuleName -EQ $Temp_CMDevice.Name if ($null -ne $MembershipRule) { Write-Output "$($Temp_CMDevice.Name): removing collection membership in $((Get-CMCollection -Id $PSItem.CollectionID).Name)" Remove-CMDeviceCollectionDirectMembershipRule -CollectionId $PSItem.CollectionID -ResourceId $MembershipRule.ResourceId -Confirm:$false -Force } } } } } } catch { throw "error removing additional collections - $($PSItem.Exception.Message)" } Set-Location -Path $env:SystemDrive Remove-PSDrive -Name $CMPSDriveName } $SecondsToWait = 100 Write-Output "$($env:COMPUTERNAME): finished registration, now waiting $($SecondsToWait) seconds for the configmgr database updates and stabilization" Start-Sleep -Seconds $SecondsToWait } catch { throw "error during registration of device infos in configmgr: $($PSItem.Exception.Message)" } } function Start-VM_Deployment { <# .Description Starts VMs .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Example # Starts VMs, based on objects in $VM_Config Start-VM_Deployment -VM_Config_Obj $VM_Config .NOTES the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [hashtable] $VM_Config_Obj ) try { foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { Write-Output "$($VM): starting vm for pxe deployment" Start-Sleep -Seconds 2 Start-VM -VMName $VM } } catch { throw "error while starting vms: $($PSItem.Exception.Message)" } } function Confirm-VM_Deployment { <# .Description Checks the ConfigMgr database to see if the deployment of VM objects is complete. .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Parameter CM_Siteserver_FQDN FQDN of ConfigMgr .Parameter CM_Credentials Credentials of a user that can create/edit/delete CMDevices and add them to a Collection. Should be able to start a collection update .Example # Checks the ConfigMgr database to see if the deployment of VM objects in $VM_Config is complete. Confirm-VM_Deployment -VM_Config_Obj $VM_Config -CM_Siteserver_FQDN $CM_Siteserver_FQDN -CM_Credentials $CM_Credentials .NOTES the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [hashtable] $VM_Config_Obj, [Parameter(Mandatory = $true)] [string] $CM_Siteserver_FQDN, [Parameter(Mandatory = $true)] [PSCredential] $CM_Credentials, [Parameter(Mandatory = $true)] [PSCredential] $VM_Credentials, [Parameter(Mandatory = $false)] [int] $TimeoutInMinutes = 120 ) # PS Session try { Confirm-VMPresence -VM_Config_Obj $VM_Config_Obj $CMPS_Session = New-PSSession -ComputerName $CM_Siteserver_FQDN -Credential $CM_Credentials Invoke-Command -Session $CMPS_Session -ScriptBlock { $CMPSDriveName = "CMPS-$(Get-Random)" Import-Module -Name "ConfigurationManager" New-PSDrive -Name $CMPSDriveName -PSProvider "CMSite" -Root $using:CM_Siteserver_FQDN -Description "Primary site" | Out-Null Set-Location -Path "$($CMPSDriveName):\" } } catch { throw "error ps session to $($CM_Siteserver_FQDN): $($PSItem.Exception.Message)" } # check status try { $Deployment_Finished = $false $StartDate = Get-Date $CM_Deployment_Running = @{} $DeploymentProperties = @{} # set environment try { $VM_Config_Obj.Keys | Sort-Object | ForEach-Object { $CM_Deployment_Running.Add($PSItem, $true) $ResourceID = Invoke-Command -Session $CMPS_Session -ArgumentList $PSItem -ScriptBlock { try { return (Get-CMDevice -Name $args[0] -Fast).ResourceID } catch { throw "$($PSItem.Exception.Message)" } } $DeploymentID = Invoke-Command -Session $CMPS_Session -ArgumentList $($VM_Config_Obj).$PSItem.CM_Collection_Name -ScriptBlock { try { return (Get-CMDeployment -CollectionName $args[0]).DeploymentID } catch { throw "$($PSItem.Exception.Message)" } } $TaskSequenceName = Invoke-Command -Session $CMPS_Session -ArgumentList $($VM_Config_Obj).$PSItem.CM_Collection_Name -ScriptBlock { try { return (Get-CMDeployment -CollectionName $args[0]).SoftwareName } catch { throw "$($PSItem.Exception.Message)" } } $Properties = @{ ResourceID = $ResourceID DeploymentID = $DeploymentID TaskSequenceName = $TaskSequenceName } $DeploymentProperties.Add($PSItem, $Properties) } } catch { throw "could not set environment - $($PSItem.Exception.Message)" } Write-Output "---" $VM_Config_Obj.keys | ForEach-Object { Write-Output "$($PSItem): the task sequence $($($DeploymentProperties).$PSItem.TaskSequenceName) for the os deployment is used" } Write-Output "---" # check status of deployment $WaitForCMDatabaseCount = 0 do { $VM_Config_Obj.keys | ForEach-Object { $StatusMessages = $null $ResourceID = $($DeploymentProperties).$PSItem.ResourceID $DeploymentID = $($DeploymentProperties).$PSItem.DeploymentID $StatusMessages = Invoke-Command -Session $CMPS_Session -ArgumentList ($ResourceID, $DeploymentID) -ScriptBlock { try { $CM_SiteCode = (Get-CMSite).SiteCode $Query = "Select AdvertisementID,ResourceID,Step,ActionName,LastStatusMessageIDName from v_TaskExecutionStatus where (AdvertisementID = '$($args[1])' AND ResourceID = '$($args[0])')" return (Invoke-Sqlcmd -ServerInstance "$($using:CM_Siteserver_FQDN)\$($CM_SiteCode)" -Database "CM_$($CM_SiteCode)" -Query $Query | Sort-Object -Property Step -Descending) } catch { throw "could not get data from db - $($PSItem.Exception.Message)" } } # try { # $ConfigMgrAgentInstalled = Invoke-Command -VMName $PSItem -Credential $VM_Credentials -ErrorAction SilentlyContinue -ScriptBlock { # $CCMExecServiceName = "CcmExec" # $CCMSetupFilePath = "$($env:windir)\ccmsetup\ccmsetup.exe" # if ($null -ne (Get-Service -Name $CCMExecServiceName -ErrorAction SilentlyContinue) -or (Test-Path -Path $CCMSetupFilePath)) { # return $true # } # else { # return $false # } # } # } # catch { # $ConfigMgrAgentInstalled = $true # } # output status try { if ($null -eq $StatusMessages -or $StatusMessages -eq "") { Write-Output "$($PSItem): waiting on data in configmgr database" $WaitForCMDatabaseCount++ } else { $StatusObject = @{ "DeviceName" = $PSItem "AdvertisementID" = $StatusMessages[0].AdvertisementID "ResourceID" = $StatusMessages[0].ResourceID "Step" = $StatusMessages[0].Step "ActionName" = $StatusMessages[0].ActionName "LastStatusMessageIDName" = $StatusMessages[0].LastStatusMessageIDName # "ConfigMgrAgentInstalled" = $ConfigMgrAgentInstalled } #region set actioname if empty if ($StatusMessages[0].ActionName -ne "") { $StatusObject.ActionName = $StatusMessages[0].ActionName } elseif ($StatusMessages[1].ActionName -ne "") { $StatusObject.ActionName = $StatusMessages[1].ActionName } elseif ($StatusMessages[2].ActionName -ne "") { $StatusObject.ActionName = $StatusMessages[2].ActionName } elseif ($StatusMessages[3].ActionName -ne "") { $StatusObject.ActionName = $StatusMessages[3].ActionName } elseif ($StatusMessages[4].ActionName -ne "") { $StatusObject.ActionName = $StatusMessages[4].ActionName } elseif ($StatusMessages[5].ActionName -ne "") { $StatusObject.ActionName = $StatusMessages[5].ActionName } elseif ($StatusMessages[6].ActionName -ne "") { $StatusObject.ActionName = $StatusMessages[6].ActionName } else { $StatusObject.ActionName = "no actionname" } #endregion if ($StatusObject.ActionName -like "*Final Restart*" -or ` $StatusObject.LastStatusMessageIDName -like "*The task sequence manager successfully completed execution of the task sequence*" -or ` $StatusObject.LastStatusMessageIDName -like "*The task execution engine successfully completed a task sequence*" ` # -or $StatusObject.ConfigMgrAgentInstalled -eq $false ) { $CM_Deployment_Running.$($PSItem) = $false } if ($CM_Deployment_Running.$($PSItem) -eq $false) { Write-Output "$($StatusObject.DeviceName): finished" } else { Write-Output "$($StatusObject.DeviceName): step $($StatusObject.Step) - $($StatusObject.ActionName): $($StatusObject.LastStatusMessageIDName)" } } } catch { throw "could not generate output - $($PSItem.Exception.Message)" } if ($WaitForCMDatabaseCount -ge 300) { throw "waited for 300 seconds, but no data in cm database. verify osd started on the vm" } Start-Sleep -Milliseconds 1000 # strange errors, session maybe broke } Write-Output "---" if ($CM_Deployment_Running.Values -notcontains $true) { $Deployment_Finished = $true } else { Start-Sleep -Seconds 15 } $Duration = ((Get-Date) - $StartDate).TotalMinutes } while ($Deployment_Finished -eq $false -and $Duration -le $TimeoutInMinutes) if ($Duration -ge $TimeoutInMinutes) { throw "deployment not finished after $($TimeoutInMinutes) mins, check the logs in configmgr or inside the vms" Write-Output "Duration $($Duration.TotalMinutes -ge $TimeoutInMinutes)" } Invoke-Command -Session $CMPS_Session -ScriptBlock { Set-Location -Path $env:SystemDrive Remove-PSDrive -Name $CMPSDriveName } Remove-PSSession -Session $CMPS_Session Start-Sleep -Seconds 10 } catch { throw "error checking status - $($PSItem.Exception.Message)" } } function New-VMs_Objectbased { <# .Description Creates vms based on objects .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Parameter SwitchName Name of the VM Switch .Parameter VMDriveLetter Letter for the volume where the vms should be stored .Example # Creates vms based on $VM_Config and connects them to the vm switch $SwitchName New-VMs_Objectbased -VM_Config $VM_Config -SwitchName $SwitchName .NOTES edits $VM_Config by updating the macaddress attribute of each vm object the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" vTPMEnabled = $true AutoStartEnabled = $false } #> param ( [Parameter(Mandatory = $true)] [hashtable] $VM_Config_Obj, [Parameter(Mandatory = $false)] [string] $VMPath = "V:\VMs", [Parameter(Mandatory = $true)] [string] $SwitchName ) try { if ($VMPath[-1] -eq "\") { $VMPath = $VMPath.Substring(0, $VMPath.Length - 1) } foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { $VMVHDXPath = ($VMPath + "\" + $VM_Config_Obj.$VM.Name + "\" + $VM_Config_Obj.$VM.Name + ".vhdx") Write-Output "$($env:COMPUTERNAME): creating vm $($VM_Config_Obj.$VM.Name)" try { if (Test-Path -Path $VMVHDXPath) { throw "vhdx for $($VM_Config_Obj.$VM.Name) already exists, please remove it" } New-VHD -Path $VMVHDXPath -SizeBytes $VM_Config_Obj.$VM.DiskSize -Dynamic | Out-Null New-VM -Name $VM_Config_Obj.$VM.Name -MemoryStartupBytes $VM_Config_Obj.$VM.RAM -Path $VMPath -Generation 2 -VHDPath $VMVHDXPath -BootDevice NetworkAdapter -SwitchName $SwitchName | Out-Null } catch { throw "error during creation of vhdx or vm ($($VM_Config_Obj.$VM.Name)) - $($PSItem.Exception.Message)" } try { Set-VMProcessor -VMName $VM_Config_Obj.$VM.Name -Count $VM_Config_Obj.$VM.CPU if ((Get-VM -Name $VM_Config_Obj.$VM.Name).DynamicMemoryEnabled) { Set-VM -Name $VM_Config_Obj.$VM.Name -StaticMemory } if ($VM_Config_Obj.$VM.vTPMEnabled) { Set-VMKeyProtector -VMName $VM_Config_Obj.$VM.Name -NewLocalKeyProtector Enable-VMTPM -VMName $VM_Config_Obj.$VM.Name } if ($VM_Config_Obj.$VM.AutoStartEnabled) { Set-VM -AutomaticStartAction Start -VMName $VM_Config_Obj.$VM.Name -AutomaticStartDelay 10 } else { Set-VM -AutomaticStartAction Nothing -VMName $VM_Config_Obj.$VM.Name } Set-VM -AutomaticStopAction ShutDown -VMName $VM_Config_Obj.$VM.Name Get-VMIntegrationService -VMName $VM_Config_Obj.$VM.Name | Where-Object -Property Enabled -EQ $false | Enable-VMIntegrationService } catch { throw "error while setting properties ($($VM_Config_Obj.$VM.Name)) - $($PSItem.Exception.Message)" } Start-VM -Name $VM_Config_Obj.$VM.Name Start-Sleep -Seconds 2 Stop-VM -Name $VM_Config_Obj.$VM.Name -Force -TurnOff Start-Sleep -Seconds 1 } foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { $VM_Config_Obj.$VM.MAC = (Get-VM -Name $VM_Config_Obj.$VM.Name | Get-VMNetworkAdapter).MacAddress Set-VMNetworkAdapter -VMName $VM_Config_Obj.$VM.Name -StaticMacAddress $VM_Config_Obj.$VM.MAC } Confirm-VMPresence -VM_Config_Obj $VM_Config_Obj } catch { throw "$($env:COMPUTERNAME): error during creation of vms - $($PSItem.Exception.Message)" } } function Test-VMConnection { <# .Description checks if a powershell direct connection to the vm can be established .Parameter VMId Id of the vm .Parameter LocalAdminCreds local admin credentials of the vm .Example # checks the powershell direct connection to VDC01 Test-VMConnection -VMId (Get-VM -Name VDC01).Id -LocalAdminCreds $VM_Credentials .NOTES #> [cmdletbinding()] param ( # VM object id [Parameter(Mandatory = $true)] [Guid] $VMId, # local admin credentials [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCredential] $LocalAdminCreds ) $VM = Get-VM -Id $VMId try { Write-Output "------" if ($VM.State -eq "Off") { Write-Output "------" Write-Output "$($VM.Name): not running - starting" $VM | Start-VM -WarningAction SilentlyContinue } # Wait for the VM's heartbeat integration component to come up if it is enabled $HearbeatIC = (Get-VMIntegrationService -VM $VM | Where-Object Id -match "84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47") if ($HearbeatIC -and ($HearbeatIC.Enabled -eq $true)) { $StartTime = Get-Date do { $WaitForMinitues = 5 $TimeElapsed = $(Get-Date) - $StartTime if ($($TimeElapsed).TotalMinutes -ge 5) { throw "$($VM.Name): integration components did not come up after $($WaitForMinitues) minutes" } Start-Sleep -sec 1 } until ($HearbeatIC.PrimaryStatusDescription -eq "OK") Write-Output "$($VM.Name): heartbeat IC connected" } do { $WaitForMinitues = 5 $TimeElapsed = $(Get-Date) - $StartTime Write-Output "$($VM.Name): testing connection" if ($($TimeElapsed).TotalMinutes -ge 5) { throw "$($VM.Name): could not connect to ps direct after $($WaitForMinitues) minutes" } Start-Sleep -sec 3 $PSReady = Invoke-Command -VMId $VMId -Credential $LocalAdminCreds -ErrorAction SilentlyContinue -ScriptBlock { $True } } until ($PSReady) } catch { throw "$($VM.Name): $($PSItem.Exception.Message)" } return $PSReady } function Confirm-VMPresence { <# .Description checks if vms are registered with the local hypervisor .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Example # checks if the vms in $VM_Config_Obj are registerd to the local hypervisor Confirm-VMPresence -VM_Config_Obj $VM_Config_Obj .NOTES the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> param ( [Parameter(Mandatory = $false)] [hashtable] $VM_Config_Obj ) try { $VM_Config_Obj.Keys | ForEach-Object { if ($null -eq (Get-VM -Name $VM_Config_Obj.$PSItem.Name)) { throw "$($VM_Config_Obj.$PSItem.Name): could not be found" } } } catch { throw "$($PSItem.Exception.Message)" } } function Add-VMDisk { <# .Description add a virtual disk to a vm .Parameter VMName name of the vm .Parameter VHDXPath where should the vhdx file be stored .Parameter VHDXSize size in byte of the disk .Example # this will add a disk to the vm and store ist at the file location from the vm Add-VMDisk -VMName $VM.Name -VHDXPath ($VM.ConfigurationLocation + "\" + $VM.Name + "-2.vhdx") -VHDXSize $DMP_ContentLibDisk_Size .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $VMName, [Parameter(Mandatory = $true)] [string] $VHDXPath, [Parameter(Mandatory = $false)] [Int64] $VHDXSize = 80GB ) try { Write-Output "$($VMName): creating disk $($VHDXPath)" New-VHD -Path $VHDXPath -SizeBytes $VHDXSize -Dynamic | Out-Null Add-VMHardDiskDrive -VMName $VMName -Path $VHDXPath } catch { throw "$($PSItem.Exception.Message)" } } function Set-VMIPConfig { <# .Description configures the network interface of a vm .Parameter VMName name of the vm .Parameter VMCredential local admin credentials of the vm .Parameter IPAddress ip address that should be assigned .Parameter NetPrefix subnet prefix, aka 24 or 16 .Parameter DefaultGateway gateway of the subnet .Parameter DNSAddresses dns server ip addresses .Example # this will configure the ip interface Set-VMIPConfig -VMName $DMP_VM.Name -VMCredential $VMCredential-IPAddress "192.168.1.21" -NetPrefix $NetPrefix -DefaultGateway $DefaultGateway -DNSAddresses $DNSAddresses .NOTES always uses the first nic founc on the system #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $VMName, [Parameter(Mandatory = $true)] [pscredential] $VMCredential, [Parameter(Mandatory = $true)] [string] $IPAddress, [Parameter(Mandatory = $true)] [int] $NetPrefix, [Parameter(Mandatory = $true)] [string] $DefaultGateway, [Parameter(Mandatory = $true)] [string[]] $DNSAddresses ) Write-Output "$($VMName): configuring network" Invoke-Command -VMName $VMName -Credential $VMCredential -ScriptBlock { try { $InterfaceObject = (Get-NetAdapter)[0] Write-Output "$($env:COMPUTERNAME): nic with mac $($InterfaceObject.MacAddress) was selected" If (($InterfaceObject | Get-NetIPConfiguration).IPv4Address.IPAddress) { $InterfaceObject | Remove-NetIPAddress -AddressFamily "IPv4" -Confirm:$false } If (($InterfaceObject | Get-NetIPConfiguration).Ipv4DefaultGateway) { $InterfaceObject | Remove-NetRoute -AddressFamily "IPv4" -Confirm:$false } Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\$($InterfaceObject.InterfaceGuid)" -Name EnableDHCP -Value 0 Start-Sleep -Seconds 2 Write-Output "$($env:COMPUTERNAME): nic with mac $($InterfaceObject.MacAddress) will have ip $($using:IPAddress)" $InterfaceObject | New-NetIPAddress -IPAddress $using:IPAddress -AddressFamily "IPv4" -PrefixLength $using:NetPrefix -DefaultGateway $using:DefaultGateway | Out-Null $InterfaceObject | Set-DnsClientServerAddress -ServerAddresses $using:DNSAddresses } catch { throw "$($env:COMPUTERNAME): error setting ip interface - $($PSItem.Exception.Message)" } } } function Add-VMToDomain { <# .Description takes the vm into a domain .Parameter VMName name of the vm .Parameter VMCredential local admin credentials of the vm .Parameter DomainName name of the domain where the vm should be joined .Parameter DomainCredential domain credentials with the permission to join devices .Parameter OUPath ou path in the domain where the vm should be organized .Parameter NoReboot when used, the vm will not reboot after join .Example # this will join the vm to the specified domain and OU Add-VMToDomain -VMName $SiteServer_VM.Name -VMCredential $VMCredential -DomainName $DomainName -DomainCredential $Domain_Credentials -OUPath "OU=Servers,OU=CM,OU=TIER-1,OU=ESAE,DC=INTUNE-CENTER,DC=DE" .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $VMName, [Parameter(Mandatory = $true)] [pscredential] $VMCredential, [Parameter(Mandatory = $true)] [string] $DomainName, [Parameter(Mandatory = $true)] [pscredential] $DomainCredential, [Parameter(Mandatory = $false)] [string] $OUPath, [Parameter(Mandatory = $false)] [switch] $NoReboot ) $ErrorActionPreference = 'Stop' try { Confirm-VMState -VMObject (Get-VM -Name $VMName) -VMCredential $VMCredential Write-Output "$($env:COMPUTERNAME): joining vm $($VMName) to domain $($DomainName)" Invoke-Command -VMName $VMName -Credential $VMCredential -ScriptBlock { try { Confirm-DomainConnectivity -DomainName $using:DomainName Start-Sleep -Seconds 1 if ($NoReboot -eq $true) { if ($null -ne $OUPath) { Add-Computer -Credential $using:DomainCredential -DomainName $using:DomainName -OUPath $OUPath -WarningAction SilentlyContinue } else { Add-Computer -Credential $using:DomainCredential -DomainName $using:DomainName -WarningAction SilentlyContinue } } else { if ($null -ne $OUPath) { Add-Computer -Credential $using:DomainCredential -DomainName $using:DomainName -OUPath $OUPath -Restart -WarningAction SilentlyContinue } else { Add-Computer -Credential $using:DomainCredential -DomainName $using:DomainName -Restart -WarningAction SilentlyContinue } } } catch { throw $PSItem.Exception.Message } } if ($NoReboot -eq $true) { Write-Output "$($env:COMPUTERNAME): domainjoin of vm $($VMName) successfull - reboot required" } else { Write-Output "$($env:COMPUTERNAME): domainjoin of vm $($VMName) successfull - vm will do reboot" } } catch { throw "$($env:COMPUTERNAME): error joining vm $($VMName) to domain $($DomainName) - $($PSItem.Exception.Message)" } } function Confirm-VMState { <# .Description checks if the vm is running and starts it if necessary then checks the connection to the vm via powershell direct .Parameter VMObject name of the vm .Parameter VMCredential local admin credentials of the vm .Example # this will check the specified vm Confirm-VMState -VMObject $VM -VMCredential $VMCred .NOTES VMObject should be like this: $VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" #> param ( # VM Objects [Parameter(Mandatory = $true)] [System.Object] $VMObject, [Parameter(Mandatory = $true)] [PSCredential] $VMCredential ) try { if ($VMObject.State -ne "Running") { Write-Output "$($env:COMPUTERNAME): starting $($VMObject.Name) because the vm was stopped" Start-VM -VM $VMObject Start-Sleep -Seconds 10 } Write-Output "$($env:COMPUTERNAME): verify the connection to $($VMObject.Name)" if (Test-VMConnection -VMId $VMObject.Id -LocalAdminCreds $VMCredential) { Write-Output "$($env:COMPUTERNAME): connected to $($VMObject.Name) successful - continue" } else { throw "error while connecting to $($VMObject.Name) with ps direct - $($PSItem.Exception.Message)" } } catch { throw "$($env:COMPUTERNAME): $($PSItem.Exception.Message)" } } function Confirm-HyperV { <# .Description this function throws errors, when hyper-v is not installed .Example # this checks if hyper-v is installed Confirm-HyperV .NOTES #> $OS_Info = Get-CimInstance -ClassName Win32_OperatingSystem if ($OS_Info.ProductType -eq 3) { if ((Get-WindowsFeature -Name Hyper-V).installed -ne $true) { throw "Hyper-v not installed" } } elseif ($OS_Info.ProductType -eq 1) { if ((Get-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V -Online).State -ne "Enabled") { throw "Hyper-v not installed" } } } function Set-VMInstallSnapshot { <# .Description this creates a snapshot .Parameter VMName name of vm .Parameter SnapshotName name of snapshot .Parameter VMCredential credentials for vm .Example # this creates a snapshot for the vm Set-VMInstallSnapshot -VMName $PSItem -SnapshotName "$(Get-Date -format "yyyy-MM-dd_HH.mm.ss") - initial configuration" -VMCredential $VM_Credentials .NOTES vm will stop and start during this process #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $VMName, [Parameter(Mandatory = $true)] [string] $SnapshotName, [Parameter(Mandatory = $true)] [pscredential] $VMCredential ) try { Confirm-VMState -VMObject (Get-VM -Name $VMName) -VMCredential $VMCredential Stop-VM -Name $VMName -Force Write-Output "$($env:COMPUTERNAME): creating snapshot $($VMName) - $($SnapshotName)" Checkpoint-VM -Name $VMName -SnapshotName $SnapshotName Start-VM -Name $VMName Confirm-VMState -VMObject (Get-VM -Name $VMName) -VMCredential $VMCredential } catch { throw $PSItem.Exception.Message } } function Restart-VMIfNeeded { <# .Description this function checks if the vm has to reboot and does it, if needed .Parameter VMName name of vm .Parameter Credentials credentials for vm .Example # this does a restart of the vm if needed Restart-VMIfNeeded -VMName $PSItem -Credential $Domain_Credentials .NOTES vm will stop and start during this process #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $VMName, [Parameter(Mandatory = $true)] [pscredential] $Credentials ) $VM = Get-VM -Name $VMName $RebootPending = Invoke-Command -VMName $VMName -Credential $Credentials -ScriptBlock { Import-Module -Name "NTS.Tools" -DisableNameChecking return (Test-RebootPending) } if ($RebootPending) { Write-Output "$($env:COMPUTERNAME): doing a reboot of $($VMName)" Restart-VM -VM $VM -Type Reboot -Force -Wait Confirm-VMState -VMObject $VM -VMCredential $Credentials } } function New-VMHost { <# .Description this function checks if the vm has to reboot and does it, if needed .Parameter Course_Shortcut shortcut of the participant, used for the switchname .Parameter TrustedHostsValue value for trustedhosts to add, needed for configmgr things .Parameter VM_Drive_Letter letter for the vm volume, which will be created .Example # this prepared the host and creates vm switch, vm volume Build-VMHost -Course_Shortcut $Course_Shortcut -TrustedHostsValue "$($CM_Siteserver_NetBIOS),$($CM_Siteserver_FQDN)" .NOTES #> [CmdletBinding()] [Alias("Build-VMHost")] param ( [Parameter(Mandatory = $true)] [string] $Course_Shortcut, [Parameter(Mandatory = $true)] [string] $TrustedHostsValue, [Parameter(Mandatory = $false)] [char] $VM_Drive_Letter = 'V' ) $Host_Preparations_Start = Get-Date try { Write-Output "`n$($env:COMPUTERNAME): prepare host" # configmgr Start-Service -Name "WinRM" Set-Item -Path "WSMan:\localhost\Client\TrustedHosts" -Value $TrustedHostsValue -Force -Concatenate # prepare host for vms New-VMVolume -VMDriveLetter $VM_Drive_Letter New-VMVSwitch -Name $Course_Shortcut # hyperv $VM_DefaultPath = "$($VM_Drive_Letter):\VMs" Set-VMHost -EnableEnhancedSessionMode $True -VirtualHardDiskPath $VM_DefaultPath -VirtualMachinePath $VM_DefaultPath } catch { throw "$($env:COMPUTERNAME): host preparations failed - $($PSItem.Exception.Message)" } $Host_Preparations_Duration = (Get-Date) - $Host_Preparations_Start Write-Output "$($env:COMPUTERNAME): host preparation took $($Host_Preparations_Duration.Hours)h $($Host_Preparations_Duration.Minutes)m $($Host_Preparations_Duration.Seconds)s" } function Install-VMs { <# .Description this function checks if the vm has to reboot and does it, if needed .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Parameter SwitchName shortcut of the participant .Parameter CM_Siteserver_FQDN fqdn of siet server .Parameter CM_Credentials credentials to connect to the config .Parameter VM_Credentials admin credentials to connect to the vm .Example # this does a restart of the vm if needed Deploy-VMs -VM_Config_Obj $VM_Config -SwitchName $SwitchName -CM_Siteserver_FQDN $CM_Siteserver_FQDN -CM_Credentials $CM_Credentials -VM_Credentials $VM_Credentials .NOTES #> [CmdletBinding()] [Alias("Deploy-VMs")] param ( [Parameter(Mandatory = $true)] [hashtable] $VM_Config_Obj, [Parameter(Mandatory = $true)] [string] $SwitchName, [Parameter(Mandatory = $false)] [string] $VMPath = "V:\VMs", [Parameter(Mandatory = $true)] [string] $CM_Siteserver_FQDN, [Parameter(Mandatory = $true)] [pscredential] $CM_Credentials, [Parameter(Mandatory = $true)] [pscredential] $VM_Credentials ) # checks try { $VM_Config_Obj.Keys | ForEach-Object { if ($null -ne (Get-VM -Name $VM_Config_Obj.$PSItem.Name -ErrorAction SilentlyContinue)) { throw "vm $($VM_Config_Obj.$PSItem.Name) already exists, stopping" } } } catch { throw "error doing prereq checks - $($PSItem.Exception.Message)" } # deployment $VM_Deployment_Start = Get-Date try { Write-Output "`n$($env:COMPUTERNAME): starting vm deployments" New-VMs_Objectbased -VM_Config_Obj $VM_Config_Obj -VMPath $VMPath -SwitchName $SwitchName Write-Output "`n$($env:COMPUTERNAME): starting vm registration in configmgr" Register-VM_in_CM -VM_Config_Obj $VM_Config_Obj -CM_Siteserver_FQDN $CM_Siteserver_FQDN -CM_Credentials $CM_Credentials Write-Output "`n$($env:COMPUTERNAME): starting vms" Start-VM_Deployment -VM_Config_Obj $VM_Config_Obj Write-Output "`n$($env:COMPUTERNAME): validating vm deployments" Confirm-VM_Deployment -VM_Config_Obj $VM_Config_Obj -CM_Siteserver_FQDN $CM_Siteserver_FQDN -CM_Credentials $CM_Credentials -VM_Credentials $VM_Credentials Write-Output "$($env:COMPUTERNAME): now waiting for 120 seconds" Start-Sleep -Seconds 120 Write-Output "$($env:COMPUTERNAME): finished vm deployments - doing cleanup in configmgr" Remove-VMConfigMgrDeployment -VM_Config_Obj $VM_Config_Obj -CM_Siteserver_FQDN $CM_Siteserver_FQDN -CM_Credentials $CM_Credentials } catch { throw "$($env:COMPUTERNAME): error deploying vms: $($PSItem.Exception.Message)" } # snapshots $VM_Config_Obj.Keys | Sort-Object | ForEach-Object { Set-VMInstallSnapshot -VMName $PSItem -SnapshotName "$($PSItem) - $(Get-Date -format "yyyy-MM-dd_HH.mm.ss") - initial deployment" -VMCredential $VM_Credentials } $VM_Deployment_Duration = (Get-Date) - $VM_Deployment_Start Write-Output "$($env:COMPUTERNAME): vm deployment took $($VM_Deployment_Duration.Hours)h $($VM_Deployment_Duration.Minutes)m $($VM_Deployment_Duration.Seconds)s" } function Remove-VMConfigMgrDeployment { <# .Description Removes the ConfigMgr Object related to the vms .Parameter VM_Config_Obj Object that contains multiple descriptive objects for deployment from a VM. .Parameter CM_Siteserver_FQDN FQDN of ConfigMgr .Parameter CM_Credentials Credentials of a user that can create/edit/delete CMDevices and add them to a Collection. Should be able to start a collection update .Example # Removes all vm objects in configmgr Remove-VMConfigMgrDeployment -VM_Config_Obj $VM_Config -CM_Siteserver_FQDN $CM_Siteserver_FQDN -CM_Credentials $CM_Credentials .NOTES the VM_Config_Obj object, should be like ` `$VM_Config_Obj = @{} `$VM_01 = @{ Name = '`$Course_Shortcut)-VWIN11-`$Participant_Number)1' RAM = `$RAM CPU = `$CPUCount CM_Collection_Name = `$CM_Collection_W11_Autopilot Credentials = `$VM_Credentials DiskSize = `$DynamicDiskSize MAC = "" } #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [hashtable] $VM_Config_Obj, [Parameter(Mandatory = $true)] [string] $CM_Siteserver_FQDN, [Parameter(Mandatory = $true)] [PSCredential] $CM_Credentials ) Invoke-Command -ComputerName $CM_Siteserver_FQDN -Credential $CM_Credentials -ScriptBlock { $VM_Config_Obj = $using:VM_Config_Obj $CMPSDriveName = "CMPS-$(Get-Random)" # load configmgr ps module Import-Module -Name "ConfigurationManager" New-PSDrive -Name $CMPSDriveName -PSProvider "CMSite" -Root $using:CM_Siteserver_FQDN -Description "Primary site" | Out-Null Set-Location -Path "$($CMPSDriveName):\" # remove collections membership rules try { $CM_SiteCode = (Get-CMSite).SiteCode $VM_Config_Obj.Keys | ForEach-Object { $Temp_CMDevice = Get-CMDevice -Name $PSItem $TargetCollection = Get-CMCollection -Name $($VM_Config_Obj.$PSItem.CM_Collection_Name) if ($null -ne $Temp_CMDevice) { $Collections = Get-CimInstance -Namespace "root/Sms/site_$($CM_SiteCode)" -ClassName "SMS_FullCollectionMembership" -Filter "ResourceID = $($Temp_CMDevice.ResourceID)" | ` Where-Object -Property CollectionID -eq $TargetCollection.CollectionID if ($null -ne $Collections) { $Collections | ForEach-Object { $MembershipRule = Get-CMCollectionDirectMembershipRule -CollectionId $PSItem.CollectionID | Where-Object -Property RuleName -EQ $Temp_CMDevice.Name if ($null -ne $MembershipRule) { Write-Output "$($Temp_CMDevice.Name): removing collection membership in $((Get-CMCollection -Id $PSItem.CollectionID).Name) after the deployment" Remove-CMDeviceCollectionDirectMembershipRule -CollectionId $PSItem.CollectionID -ResourceId $MembershipRule.ResourceId -Confirm:$false -Force } } } } } } catch { throw "error removing collection membership rules - $($PSItem.Exception.Message)" } # removing device object after the deployment try { $VM_Config_Obj.Keys | Sort-Object | ForEach-Object { $Temp_CMDevice = Get-CMDevice -Name $PSItem if ($null -ne $Temp_CMDevice) { Write-Output "$($PSItem): removing computer info in configmgr after the deployment" Remove-CMDevice -Name $PSItem -Force -Confirm:$false } } } catch { throw "error removing device - $($PSItem.Exception.Message)" } Set-Location -Path $env:SystemDrive Remove-PSDrive -Name $CMPSDriveName } } function New-VMDiskFormated { <# .Description this function adds a disk to the vm and formats .Parameter VMName name of vm .Parameter VolumeDriveLetter letter for volume .Parameter VolumeFriendlyName volume label .Parameter VolumeSize size in bytes .Example # adds a disk with the size of $ContentLibDisk_Size and formats it with letter L New-VMDiskFormated -VMName $PSItem -VolumeDriveLetter "L" -VolumeFriendlyName "ContentLib" -VolumeSize $ContentLibDisk_Size .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $VMName, [Parameter(Mandatory = $true)] [char] $VolumeDriveLetter, [Parameter(Mandatory = $true)] [string] $VolumeFriendlyName, [Parameter(Mandatory = $true)] [string] $VolumeSize, [Parameter(Mandatory = $true)] [pscredential] $VMCredential, [Parameter(Mandatory = $false)] [string] $VHDXPathXPath, [Parameter(Mandatory = $false)] [ValidateSet("NTFS", "REFS")] [string] $FileSystem = "NTFS" ) try { $VM = Get-VM -Name $VMName $CurrentVMDiskCount = (Get-VHD -VMId $VM.Id).count if ($null -eq $VHDXPathXPath -or $VHDXPathXPath -eq "") { $VHDXPathXPath = "$($VM.ConfigurationLocation)\$($VM.Name)-$($CurrentVMDiskCount).vhdx" } } catch { throw "could not find out the vm or the number of disks present - $($PSItem.Exception.Message)" } try { $VolumeExists = Invoke-Command -VMName $VM.Name -Credential $VMCredential -ScriptBlock { if ($null -eq (Get-Volume -DriveLetter $using:VolumeDriveLetter -ErrorAction SilentlyContinue)) { return $false } else { $true } } if ($VolumeExists -eq $false) { Write-Output "$($env:COMPUTERNAME): adding disk to $($VM.Name)" Add-VMDisk -VMName $VM.Name -VHDXPath $VHDXPathXPath -VHDXSize $VolumeSize Invoke-Command -VMName $VM.Name -Credential $VMCredential -ScriptBlock { try { Write-Output "$($env:COMPUTERNAME): formating disk" $PhysicalDisk = Get-PhysicalDisk | Where-Object -Property Size -eq $using:VolumeSize | Sort-Object -Property DeviceId if ($PhysicalDisk.Count -gt 1) { $Disk = ($PhysicalDisk | ForEach-Object { Get-Disk -Number $PSItem.DeviceId | Where-Object -Property PartitionStyle -eq "RAW" })[0] New-Volume -DiskNumber $Disk.Number -FriendlyName $using:VolumeFriendlyName -FileSystem $using:FileSystem -DriveLetter $using:VolumeDriveLetter | Out-Null } else { New-Volume -DiskNumber $PhysicalDisk.DeviceId -FriendlyName $using:VolumeFriendlyName -FileSystem $using:FileSystem -DriveLetter $using:VolumeDriveLetter | Out-Null } } catch { throw $PSItem.Exception.Message } } } else { Write-Output "$($VM.Name): volume already exits" } } catch { throw "could not format volume - $($PSItem.Exception.Message)" } } function Connect-VMsToCourseNetwork { <# .Description this function can be used to connect vm nics to a specific network .Parameter VMNamePattern pattern to search for the name of a vm .Parameter CourseRoomName name of the room / network which the vms should be connected to .Example # connects the vms with "VSQL" in its names to the network "SR_3_EG_Klein" Connect-VMsToCourseNetwork -VMNamePattern "VSQL" -CourseRoomName SR_3_EG_Klein .NOTES #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $VMNamePattern, [Parameter(Mandatory = $true)] [ValidateSet( "SR_1_4OG", # Schulungsraum 1 im 4.OG "SR_2_EG_Gross", # Schulungsraum 2 (Groß) im EG "SR_3_EG_Klein", # Schulungsraum 3 (Klein) im EG "vSR_1", # virtueller Schulungsraum 1 "vSR_2", # virtueller Schulungsraum 2 "vSR_3", # virtueller Schulungsraum 3 "Preparation" # VLAN (NTS-Install) für die Kursvorbereitung )] [string] $CourseRoomName ) $ErrorActionPreference = 'Stop' # define parameters for vmnetworkadapters switch ($CourseRoomName) { "SR_1_4OG" { $SwitchName = "OG1(OBEN)" $VLANId = "Untagged" } "SR_2_EG_Gross" { $SwitchName = "EG2(GROSS)" $VLANId = "Untagged" } "SR_3_EG_Klein" { $SwitchName = "EG3(KLEIN)" $VLANId = "Untagged" } "vSR_1" { $SwitchName = "VSCHULUNG" $VLANId = "191" } "vSR_2" { $SwitchName = "VSCHULUNG" $VLANId = "192" } "vSR_3" { $SwitchName = "VSCHULUNG" $VLANId = "193" } "Preparation" { $SwitchName = "VSCHULUNG" $VLANId = "404" } Default { "courseroom does not exist" } } $VMs = Get-VM | Where-Object -Property Name -like "$($VMNamePattern)*" if ($null -eq (Get-VMSwitch -Name $SwitchName -ErrorAction SilentlyContinue)) { throw "vm switch $($SwitchName) on $($env:COMPUTERNAME) not found" } [int]$WrongAnswerCount = 0 do { Write-Output "found the following vms: " $VMs.Name | ForEach-Object { Write-Host $PSItem.ToString() } Write-Output "vm switch: $($SwitchName) and vlan $($VLANId)" $askyesno = (Read-Host "`nare these correct? (Y/N)").ToLower() $WrongAnswerCount++ if ($WrongAnswerCount -eq 2) { Write-Output "" Write-Warning "you need to enter 'y' to accept or 'n' to decline" } elseif ($WrongAnswerCount -eq 4) { throw "you had one job, please read the console again" } } while ($askyesno -notin @('y', 'n')) if ($askyesno -eq 'y') { $VMs | ForEach-Object { $VMNetworkAdapter = Get-VMNetworkAdapter -VMName $PSItem.Name if ($VMNetworkAdapter.count -gt 1 ) { Write-Warning "skipping $($PSItem), because there are more than one vnic. this must be configured manually" } else { Write-Output "connect vnic $($VMNetworkAdapter.Name) of $($PSItem.Name) to vmswitch $($SwitchName) and vlan $($VLANId)" $VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $SwitchName if ($VLANId -eq "Untagged") { $VMNetworkAdapter | Set-VMNetworkAdapterVlan -Untagged } else { $VMNetworkAdapter | Set-VMNetworkAdapterVlan -Access -VlanId $VLANId } } } } else { Write-Warning "stopping script, nothing changed" } } |