NTS-ConfigMgrTools.psm1
function New-VMVolume { param ( # Driveletter [Parameter(Mandatory = $true)] [char] $VMDriveLetter, # StoragePool Name [Parameter(Mandatory = $true)] [string] $StoragePoolName, # VirtualDisk Name [Parameter(Mandatory = $true)] [string] $VirtualDiskName, # VMVolume Name [Parameter(Mandatory = $true)] [string] $VMVolumeName ) $LogPath = "$($env:ProgramData)\NTS\New-VMVolume-$(Get-Date -format "yyyy-MM-dd_HH.mm.ss").log" New-Item -Path $LogPath -ItemType File -Force | Out-Null Start-Transcript -Append $LogPath | Out-Null try { 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-Host "Create storage pool '$($StoragePoolName)'" New-StoragePool -StorageSubSystemFriendlyName $StorageSubSystemFriendlyName -FriendlyName $StoragePoolName -PhysicalDisks $SelectedDisks | Out-Null if($null -eq (Get-VirtualDisk -FriendlyName $VirtualDiskName -ErrorAction SilentlyContinue)){ Write-Host "Create vdisk '$($VirtualDiskName)'" New-VirtualDisk -StoragePoolFriendlyName $StoragePoolName -FriendlyName $VirtualDiskName -UseMaximumSize -ProvisioningType Fixed -ResiliencySettingName Simple | Out-Null Initialize-Disk -FriendlyName $VirtualDiskName -PartitionStyle GPT | Out-Null $VDiskNumber = (Get-Disk -FriendlyName $VirtualDiskName).Number Write-Host "Create volume '$($VMVolumeName)'" New-Volume -DiskNumber $VDiskNumber -FriendlyName $VMVolumeName -FileSystem ReFS -DriveLetter $VMDriveLetter | Out-Null } else{ Write-Host "Virtual disk '$($VirtualDiskName)' already exists" } } else{ Write-Host "Pool '$($StoragePoolName)' already exists" } } catch { Write-Host "error during creation of vm volume: $($PSItem.Exception.Message)" Stop-Transcript | Out-Null } Stop-Transcript | Out-Null } Export-ModuleMember -Function New-VMVolume function New-VMVSwitch { param ( # Course Shortcut [Parameter(Mandatory = $true)] [string] $Course_Shortcut ) $LogPath = "$($env:ProgramData)\NTS\New-VMVSwitch-$(Get-Date -format "yyyy-MM-dd_HH.mm.ss").log" New-Item -Path $LogPath -ItemType File -Force | Out-Null Start-Transcript -Append $LogPath | Out-Null try { if($null -eq (Get-VMSwitch -Name $Course_Shortcut -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 { (Get-NetAdapter -Physical | Where-Object -Property Status -eq "UP")[0] } Write-Host "create vswitch '$($Course_Shortcut)'" New-VMSwitch -Name $Course_Shortcut -NetAdapterName $Selected_NIC.Name -AllowManagementOS $false | Out-Null Add-VMNetworkAdapter -ManagementOS -SwitchName $Course_Shortcut -Name "vNIC-$($Course_Shortcut)" Rename-NetAdapter -Name $Selected_NIC.Name -NewName "pNIC-$($Course_Shortcut)" } else{ Write-Host "virtual vswitch '$($Course_Shortcut)' already exists" } } catch { Write-Host "error during creation of virtual switch: $($PSItem.Exception.Message)" Stop-Transcript | Out-Null } } Export-ModuleMember -Function New-VMVSwitch function Register-VM_in_CM { [CmdletBinding()] param ( # VM Object [Parameter()] [hashtable] $VM_Config_Obj, # ConfigMgr SMS Provider FQDN [Parameter()] [string] $CM_Siteserver_FQDN, # Credentials for PSRemoting to SMS Provider and permissions to configmgr [Parameter()] [PSCredential] $CM_Credentials ) try { Invoke-Command -ComputerName $CM_Siteserver_FQDN -Credential $CM_Credentials -ScriptBlock { $PSDriveName = "CM-PB2" $VM_Config_Obj = $using:VM_Config_Obj Import-Module -Name ConfigurationManager New-PSDrive -Name $PSDriveName -PSProvider "CMSite" -Root $using:CM_Siteserver_FQDN -Description "Primary site" | Out-Null Set-Location -Path "$($PSDriveName):\" foreach ($VM in $VM_Config_Obj.Keys | Sort-Object){ # check destination collection existance if ($null -eq (Get-CMCollection -Name $VM_Config_Obj.$VM.CM_Collection_Name)) { Get-CMDevice -Name $VM | Remove-CMDevice -Force -Confirm:$false throw "collection '$($VM_Config_Obj.$VM.CM_Collection_Name)' does not existing, device infos for '$($VM)' was removed" } } # create vm info foreach ($VM in $VM_Config_Obj.Keys | Sort-Object){ Write-Host "'$($VM)': creating vm computer info in configmgr - macaddress '$($VM_Config_Obj.$VM.MAC)'" Get-CMDevice -Name $VM | Remove-CMDevice -Force -Confirm:$false } Start-Sleep -Seconds 5 foreach ($VM in $VM_Config_Obj.Keys | Sort-Object){ Import-CMComputerInformation -CollectionName "All Systems" -ComputerName $VM -MacAddress $VM_Config_Obj.$VM.MAC } # check vm collection membership foreach ($VM in $VM_Config_Obj.Keys | Sort-Object){ #region all systems collection $Device_Exists_In_AllDevices = $false $All_Devices_Counter = 0 Write-Host "'$($VM)': checking collection memberships" while ($Device_Exists_In_AllDevices -eq $false -and $All_Devices_Counter -lt 30) { $CMDevice = Get-CMDevice -Name $VM if ($CMDevice.Name -eq $VM) { $Device_Exists_In_AllDevices = $true } else{ Start-Sleep -Seconds 5 $All_Devices_Counter++ } Write-Host "'$($VM)': 'All Systems' - not found" if ($All_Devices_Counter -eq 12) { Write-Host "'$($VM)': triggering membership update in Collection 'All Systems'" Start-Sleep -Seconds (10 + (Get-Random -Maximum 50 -Minimum 10)) Get-CMCollection -Name "All Systems" | Invoke-CMCollectionUpdate } } if ($All_Devices_Counter -ge 30) { throw "'$($VM)': could find in the collection 'All Systems'" } else { Write-Host "'$($VM)': 'All Systems' - found, continuing" } #endregion # create vm membership rule Add-CMDeviceCollectionDirectMembershipRule -CollectionName $VM_Config_Obj.$VM.CM_Collection_Name -ResourceID $CMDevice.ResourceID #region destination collection $Device_Exists_In_Specified_Collection = $false $Specified_Collection_Counter = 0 while ($Device_Exists_In_Specified_Collection -eq $false -and $Specified_Collection_Counter -lt 30) { $Collection_Direct_Members = Get-CMDeviceCollectionDirectMembershipRule -CollectionName $VM_Config_Obj.$VM.CM_Collection_Name | Where-Object RuleName -eq $VM if ($null -ne $Collection_Direct_Members) { $Device_Exists_In_Specified_Collection = $true } else{ Start-Sleep -Seconds 5 $Specified_Collection_Counter++ } Write-Host "'$($VM)': '$($VM_Config_Obj.$VM.CM_Collection_Name)' - not found" if ($Specified_Collection_Counter -eq 20) { Write-Host "'$($VM)': triggering membership update in Collection '$($VM_Config_Obj.$VM.CM_Collection_Name)'" Start-Sleep -Seconds (10 + (Get-Random -Maximum 50 -Minimum 10)) Get-CMCollection -Name $VM_Config_Obj.$VM.CM_Collection_Name | Invoke-CMCollectionUpdate } } if ($Specified_Collection_Counter -ge 30) { throw "'$($VM)': could find in the collection '$($VM_Config_Obj.$VM.CM_Collection_Name)'" } else { Write-Host "'$($VM)': '$($VM_Config_Obj.$VM.CM_Collection_Name)' - found, continuing" } #endregion } Start-Sleep -Seconds 60 Set-Location -Path $env:SystemDrive Remove-PSDrive -Name $PSDriveName } } catch { throw "error during registration of device infos in configmgr: $($PSItem.Exception.Message)" } } Export-ModuleMember -Function Register-VM_in_CM function Start-VM_Deployment { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [hashtable] $VM_Config_Obj ) try { foreach ($VM in $VM_Config_Obj.Keys | Sort-Object) { Write-Host "'$($VM)': starting deployment" Start-Sleep -Seconds 2 Start-VM -VMName $VM } } catch { throw "error while starting vms: $($PSItem.Exception.Message)" } } Export-ModuleMember -Function Start-VM_Deployment function Confirm-VM_Deployment { [CmdletBinding()] param ( # VM Object [Parameter(Mandatory = $true)] [hashtable] $VM_Config_Obj, # ConfigMgr SMS Provider FQDN [Parameter()] [string] $CM_Siteserver_FQDN, # Credentials for PSRemoting to SMS Provider and permissions to configmgr [Parameter()] [PSCredential] $CM_Credentials ) try { Invoke-Command -ComputerName $CM_Siteserver_FQDN -Credential $CM_Credentials -ScriptBlock { $PSDriveName = "CM-PB2" Import-Module -Name ConfigurationManager New-PSDrive -Name $PSDriveName -PSProvider "CMSite" -Root $using:CM_Siteserver_FQDN -Description "Primary site" | Out-Null Set-Location -Path "$($PSDriveName):\" $Deployment_Finished = $false $CM_Deployment_Running = @{} foreach ($VM in $using:VM_Config_Obj.keys | Sort-Object) { $CM_Deployment_Running.Add($VM,$true) } do { $CM_All_Deployments = Get-CMDeploymentStatus | Get-CMDeploymentStatusDetails | ` Where-Object -FilterScript { $using:VM_Config_Obj.keys -contains $PSItem.Devicename } | ` Sort-Object -Property DeviceName if ($null -ne $CM_All_Deployments) { foreach($VM_Deployment in $CM_All_Deployments){ if($VM_Deployment.StatusDescription -notlike "*The task sequence manager successfully completed execution of the task sequence*"){ Write-Host "'$($VM_Deployment.DeviceName)': still running - $($VM_Deployment.StatusDescription)" $CM_Deployment_Running.$($VM_Deployment.Devicename) = $true } else { Write-Host "'$($VM_Deployment.Devicename)': finished - $($VM_Deployment.StatusDescription)" $CM_Deployment_Running.$($VM_Deployment.Devicename) = $false } } } else { Write-Host "Waiting on Deployments" } Write-Host "------" if($CM_Deployment_Running.Values -notcontains $true){ $Deployment_Finished = $true } else { Start-Sleep -Seconds 15 } } while ($Deployment_Finished -eq $false) Write-Host "vm os deployment completed" Set-Location -Path $env:SystemDrive Remove-PSDrive -Name $PSDriveName } } catch { throw "error while checking deployment status: $($PSItem.Exception.Message)" } } Export-ModuleMember -Function Confirm-VM_Deployment function Deploy-VMBasedOnObject { param ( # VM Object [Parameter(Mandatory = $true)] [System.Object] $VM_Config, # Course Shortcut [Parameter(Mandatory = $true)] [string] $Course_Shortcut, # ConfigMgr SMS Provider FQDN [Parameter()] [string] $CM_Siteserver_FQDN, # Credentials for PSRemoting to SMS Provider and permissions to configmgr [Parameter()] [PSCredential] $CM_Credentials, # VM Drive Letter [Parameter(Mandatory = $true)] [char] $VMDriveLetter ) # VM Creation $VM_Deployment_Start = Get-Date $LogPath = "$($env:ProgramData)\NTS\Deploy-VMBasedOnObject-$(Get-Date -format "yyyy-MM-dd_HH.mm.ss").log" New-Item -Path $LogPath -ItemType File -Force | Out-Null Start-Transcript -Append $LogPath | Out-Null try { $VM_Base_Path = $VMDriveLetter + ":\VMs" Write-Host "`nstarting vm creation" Write-Host "------" foreach($VM in $VM_Config.Keys | Sort-Object){ $VMVHDXPath = ($VM_Base_Path + "\" + $VM_Config.$VM.Name + "\" + $VM_Config.$VM.Name + ".vhdx") Write-Host "'$($VM_Config.$VM.Name)': creating vm" try { New-VHD -Path $VMVHDXPath -SizeBytes $VM_Config.$VM.DiskSize -Dynamic | Out-Null New-VM -Name $VM_Config.$VM.Name -MemoryStartupBytes $VM_Config.$VM.RAM -Path $VM_Base_Path -Generation 2 -VHDPath $VMVHDXPath -BootDevice NetworkAdapter -SwitchName $Course_Shortcut | Out-Null } catch { throw "error during creation of vhdx or vm '$($VM_Config.$VM.Name)'" Stop-Transcript | Out-Null } try { Set-VMProcessor -VMName $VM_Config.$VM.Name -Count $VM_Config.$VM.CPU Set-VMKeyProtector -VMName $VM_Config.$VM.Name -NewLocalKeyProtector Enable-VMTPM -VMName $VM_Config.$VM.Name Set-VM -AutomaticStartAction Nothing -VMName $VM_Config.$VM.Name Set-VM -AutomaticStopAction ShutDown -VMName $VM_Config.$VM.Name Get-VMIntegrationService -VMName $VM_Config.$VM.Name | Where-Object -Property Enabled -EQ $false | Enable-VMIntegrationService } catch { throw "error while setting properties of vm '$($VM_Config.$VM.Name)'" Stop-Transcript | Out-Null } Start-VM -Name $VM_Config.$VM.Name Start-Sleep -Seconds 2 Stop-VM -Name $VM_Config.$VM.Name -Force -TurnOff Start-Sleep -Seconds 1 } foreach($VM in $VM_Config.Keys | Sort-Object){ $VM_Config.$VM.MAC = (Get-VM -Name $VM_Config.$VM.Name | Get-VMNetworkAdapter).MacAddress Set-VMNetworkAdapter -VMName $VM_Config.$VM.Name -StaticMacAddress $VM_Config.$VM.MAC } } catch { throw "error during creation of vms: $($PSItem.Exception.Message)" Stop-Transcript | Out-Null } # ConfigMgr / Deployment try { Write-Host "`nstarting configmgr preparation" Write-Host "------" Register-VM_in_CM -VM_Config_Obj $VM_Config ` -CM_Siteserver_FQDN $CM_Siteserver_FQDN ` -CM_Credentials $CM_Credentials Write-Host "`nfinished configmgr preparation, now waiting 90 seconds for the configmgr database updates and stabilization" Start-Sleep -Seconds 180 Write-Host "`nstarting vm deployments" Write-Host "------" Start-VM_Deployment -VM_Config_Obj $VM_Config Write-Host "`nchecking vm os deployment status" Write-Host "------" Confirm-VM_Deployment -VM_Config_Obj $VM_Config ` -CM_Siteserver_FQDN $CM_Siteserver_FQDN ` -CM_Credentials $CM_Credentials Write-Host "`nfinished vm deployments" Write-Host "------" } catch { throw "error during vm deployment: $($PSItem.Exception.Message)" Stop-Transcript | Out-Null } $VM_Deployment_Duration = (Get-Date) - $VM_Deployment_Start Write-Host "vm deployment took $($VM_Deployment_Duration.Hours)h $($VM_Deployment_Duration.Minutes)m $($VM_Deployment_Duration.Seconds)s" Stop-Transcript | Out-Null } Export-ModuleMember -Function Deploy-VMBasedOnObject function Test-VMConnection { [cmdletbinding()] param ( # VM object id [Parameter(Mandatory = $true)] [Guid] $VMId, # local admin credentials [Parameter()] [ValidateNotNullOrEmpty()] [PSCredential] $LocalAdminCreds ) $LogPath = "$($env:ProgramData)\NTS\Test-VMConnection-$(Get-Date -format "yyyy-MM-dd_HH.mm.ss").log" New-Item -Path $LogPath -ItemType File -Force | Out-Null Start-Transcript -Append $LogPath | Out-Null $VM = Get-VM -Id $VMId try { Write-Host "------" if ($VM.State -eq "Off") { Write-Host "------" Write-Host "'$($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-Host "'$($VM.Name)': heartbeat IC connected" } do { $WaitForMinitues = 5 $TimeElapsed = $(Get-Date) - $StartTime Write-Host "'$($VM.Name)': testing connection" if ($($TimeElapsed).TotalMinutes -ge 5) { Write-Host "'$($VM.Name)': could not connect to ps direct after $($WaitForMinitues) minutes" throw } Start-Sleep -sec 3 $PSReady = Invoke-Command -VMId $VMId -Credential $LocalAdminCreds -ErrorAction SilentlyContinue -ScriptBlock { $True } } until ($PSReady) } catch { Write-Host "'$($VM.Name)': $($PSItem.Exception.Message)" Stop-Transcript | Out-Null break } Stop-Transcript | Out-Null return $PSReady } Export-ModuleMember -Function Test-VMConnection |