Occasionally, I mention that I use saved scripts to auto-configure the Hyper-V hosts in my lab. Small, homegrown tools like this are perfect for smaller installations that can’t justify enterprise tools like VMM and SCOM, which can handle a lot of situations like this automatically. However, I never shared any of those tools because they just weren’t very high quality. They did what I needed, but I understood them and could fix any problems easily enough that I never bothered to make any of the scripts particularly resilient.
Until now.
What follows is a thoroughly retooled and streamlined version of all the separate little things I’ve built for myself over the past few years to facilitate host rebuilds, and configure a Hyper-V Host. It’s not perfectly bullet-proof and I did not build in any recovery problems from errors. However, if set correctly, it should leave you with a perfectly functional host.
Script Discussion
Normally, I post the script first so that you can copy/paste it and be on your way. This one requires you to understand some things up front. First: it is not in a usable state. You will need to modify it before you can use it. When I sat down to work something up for you to use, I had a lot of choices on the way that it could have gone. What I decided was that it needed to be a one-stop experience — no digging around in other files, no organizing things, no re-tooling Windows images, no running one tool in order to run another tool, etc. That’s because I’ve been in a small business environment and I know that “automation” processes that take as much effort as just doing the work by hand are not going to be followed. So, if you’re looking at anything in here and wondering why I did it that way, the reason is almost certainly related to this philosophy.
Requirements
To use this script, you need:
- A way to reinstall Windows Server 2012 R2 or Hyper-V Server 2012 R2 on the target host
- A way to edit a PowerShell file. PowerShell ISE, Visual Studio 2015 Community Edition with PowerShell Tools, and Notepad are all freely available solutions that will suffice. The first two might serve you better because I seeded the beginning of the file with a table of contents with line numbers, and Notepad does not display line numbers. As you use the file, the line numbers will likely be changed anyway, so that might not help much. I used another technique that will work just fine with CRTL+F in all three tools. You’ll see that in the instructions.
Nice to have, but not required, is the raw files for any drivers that you want to install.
I choose to install Windows and Hyper-V Server my physical hosts by using an 8GB USB stick. This is because I can drop this file and anything else that I need right on it without modifying an ISO file. I can also tinker with it, or delete anything on it, without any effort. I found these directions once upon a time on a Stack Overflow site but can no longer find my way back to the exact answer for proper crediting, so apologies to whomever I stole this from. To prepare a USB disk to be bootable and hold Windows installation files, use DISKPART.EXE with the following commands:
|
1 2 3 4 5 6 7 8 |
diskpart list disk (find the number of the disk that is the USB disk; it's probably the one measured in MB instead of GB) select disk N (where N is the disk you want to format) clean create partition primary active format fs=ntfs quick assign |
Once the USB stick is prepared, copy the entire contents of a Windows/Hyper-V Server ISO to it.
Next, follow the directions in the script. If you do not follow the directions, I left a reminder for you. Place the edited file on the USB stick. If you have drivers, place them as well. The script as-written looks for its drivers in sub-folders of a “Drivers” folder but you can do whatever you want.
The script as you see it is for one of my systems. Your task will be to change it for one of your systems.
It performs these operations in order:
- Very basic host configuration, like a name change.
- Installs drivers. Feed it .INF files.
- Enables roles and features. Hyper-V and MPIO are handled automatically.
- Reboots. You must have supplied a valid local administrator account in the beginning of the file or the whole thing will stop here. Remember that enabling the Hyper-V role requires two restarts.
- Configures physical adapters. My hosts do not support Consistent Device Naming (CDN) so I configure them by MAC. Tinker with the Get-NetAdapter lines to suit your system.
- Configures network team(s). Comment this section out as necessary.
- Configures virtual switches.
- Configures virtual network adapters for the management operating system.
- Joins the domain, if one is specified along with valid credentials for a domain account that has permissions to add computers to the domain.
- Reboots if domain join was successful.
- iSCSI is configured. I placed the iSCSI section here because you might have configured your target to expect particular initiators, and initiators for a host change when it joins a domain.
- Some Hyper-V defaults are set.
- Any customizations that you make are performed.
Everything that this script does is recorded to C:\Windows\Logs\RebuildHost.log. Absolutely read this file after each use.
I tried to make it very clear where you need to make changes and how they need to be made. Just follow my lead. The toughest spot will probably be the iSCSI section. There are so many possible ways to configure iSCSI that it’s tough to make an all-purpose script.
WARNING/DISCLAIMER: The following script is intended to be run on a freshly installed Windows Server or Hyper-V Server host. It will cause catastrophe-level changes if run on a functioning host, including but not limited to: all teams being deleted, all network information being lost, all virtual switches being deleted, and release of those sensitive pictures you have on your phone (just kidding on that last part). Neither Altaro Software nor I are responsible for any bad things that happen because of this script, especially if they happen because you didn’t read the instructions.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 |
<# Rebuild script for host SVHV2 #> <# Last Modified February 7, 2016 by Eric Siron #> #requires -Version 4 #requires -RunAsAdministrator param ( [Parameter()][String][ValidateSet('Start', 'Main', 'Finish')]$Action = 'Start' ) <############################################ Instructions for use Save this file with the name of the host you want to rebuild Search this file for instances of the following and change items as necessary: BEGIN Modifiable lines Table of contents for the file as originally written: Set general host information starting on line 39 Set Windows features on line 137 Set driver information on line 140 Set physical adapter parameters starting on line 335 Set teaming information starting on line 386 Set virtual switch parameters starting on line 413 Set virtual adapter parameters starting on line 477 Set iSCSI information on line 659 Any other customizations begin on line 731 Remove or comment out line 744 ############################################> Set-StrictMode -Version Latest <# Begin script variable definitions #> $MinimalQoSWeight = 1 <# End script variable definitions #> <# Begin general host information #> ## These are globals and will be available in all functions ## #### BEGIN Modifiable lines #### $ComputerName = 'svhv2' $TimeZone = 'Central Standard Time' $LocalUser = 'administrator' # this is a local user account that will restart the computer to apply changes. the account must exist before the script is run $LocalPassword = 'P@ssw0rd' # domain information -- if $DomainName is empty, the entire domain section is skipped $DomainName = 'siron.int' # leave empty to skip domain configuration $DomainUser = 'adduser' # this account only needs sufficient privileges to add computers to the domain $DomainPassword = '2easy2guess!' $DomainOU = 'Servers: Hyper-V Hosts' # make empty for default Computers OU or pre-staged account. ensure $DomainUser can add computer accounts here # Hyper-V settings $DefaultVMPath = 'C:\LocalVMs' # make empty to leave at default $DefaultVHDPath = 'C:\LocalVMs\Virtual Hard Disks' # make empty to leave at default $TeamVSwitchName = 'vSwitchTeam' # to avoid breaking line numbering, append additional entries on this line like: ; $TeamDMZName = 'DMZTeam'; $TeamWhyName = 'UnneededTeam' # storage settings $UseMPIO = $true #### END Modifiable lines #### <# End general host information #> <# Begin log file configuration #> $LogFile = '{0}\{1}\{2}' -f $env:SystemRoot, 'Logs', 'RebuildHost.log' # change the final string to change the log file's name if(-not (Test-Path -Path $LogFile -PathType Leaf)) { try { $HideOutput = New-Item -Path $LogFile -ItemType File -ErrorAction Stop } catch { Write-Warning -Message ('Log file "{0}" not created due to error: {1}' -f $LogFile, $_.Exception.Message) } } function New-LogLine { param([Parameter(Mandatory=$true)][String]$Value) Add-Content -Path $LogFile -Value $Value } function New-LogWarning { param([Parameter(Mandatory=$true)][String]$Value) Add-Content -Path $LogFile -Value ('WARNING: {0}' -f $Value) Write-Warning -Message $Value } function New-LogError { param([Parameter(Mandatory=$true)][String]$Value) Add-Content -Path $LogFile -Value ('ERROR: {0}' -f $Value) Write-Warning -Message $Value } function New-LogSeparatorLine { New-LogLine -Value '***********************************************************' } function Restart-PartiallyRebuiltHost { param( [Parameter(Mandatory=$true)][String]$NextAction ) $JobCredential = New-Object -TypeName pscredential -ArgumentList ($LocalUser, (ConvertTo-SecureString -String $LocalPassword -AsPlainText -Force)) $JobTrigger = New-JobTrigger -AtStartup -RandomDelay (New-TimeSpan -Seconds 30) $JobOptions = New-ScheduledJobOption -RunElevated -MultipleInstancePolicy StopExisting try { $IgnoreOutput = Register-ScheduledJob -Name 'RebuildHost' -Credential $JobCredential -FilePath $PSCommandPath -Trigger $JobTrigger -ScheduledJobOption $JobOptions -ArgumentList @($NextAction) -ErrorAction Stop New-LogLine -Value 'Built scheduled job to restart host and automatically resume script.' New-LogLine -Value 'Restarting computer...' Restart-Computer } catch { New-LogError -Value ('Unable to schedule the script continuation task: {0}' -f $_ex.Exception.Message) New-LogWarning -Value ('Manually restart the computer. After it restarts, open an elevated PowerShell prompt and run: {0} -{1}' -f $PSCommandPath, $NextAction) } } function Start-HostRebuild { New-LogSeparatorLine New-LogLine -Value ('Configuration began on {0}' -f (Get-Date)) New-LogSeparatorLine $RestartRequired = $false <# Begin basic host configuration #> tzutil /s $TimeZone New-LogLine -Value ('Time zone set to {0}' -f $TimeZone) $FeaturesToAdd = @('Hyper-V', 'RSAT-Hyper-V-Tools') if($UseMPIO) { $FeaturesToAdd += 'Multipath-IO' } #### BEGIN Modifiable lines #### # add other Windows features (Hyper-V and MPIO already added - use Get-WindowsFeature to discover exact names) $FeaturesToAdd += 'SNMP-Service' # drivers pnputil -i -a $PSScriptRoot\Drivers\Broadcom\b57nd60a.inf pnputil -i -a $PSScriptRoot\Drivers\Realtek\rt630x64.inf #### END Modifiable lines #### New-LogSeparatorLine New-LogLine -Value 'Beginning basic host configuration' if($env:COMPUTERNAME -ne $ComputerName) { Rename-Computer -NewName $ComputerName New-LogLine -Value ('Computer renamed to "{0}"' -f $ComputerName) $RestartRequired = $true } $InstallResult = Install-WindowsFeature -Name $FeaturesToAdd -IncludeAllSubFeature -IncludeManagementTools if($InstallResult.RestartNeeded -eq 'Yes') { New-LogLine -Value 'Hyper-V installed.' $RestartRequired = $true } <# End basic host configuration #> if($RestartRequired) { Restart-PartiallyRebuiltHost -NextAction 'Main' } else { Resume-HostRebuild } } function Resume-HostRebuild { if(Get-ScheduledJob | Where-Object -Property Name -Value 'RebuildHost' -EQ) { Unregister-ScheduledTask -TaskName 'RebuildHost' -Confirm:$false New-LogLine -Value 'Restart successful. Deleting scheduled job.' } <# Begin clearing configuration #> Get-VMSwitch | Remove-VMSwitch -Force -Confirm:$false New-LogLine -Value 'Any existing virtual switches removed.' Get-NetLbfoTeam | Remove-NetLbfoTeam New-LogLine -Value 'Any existing teams cleared.' Get-NetIPAddress | where -Property InterfaceAlias -NotMatch 'Loopback' -Confirm:$false New-LogLine -Value 'Any existing IP information cleared.' Get-NetAdapter | Set-DnsClientServerAddress -ResetServerAddresses -ErrorAction SilentlyContinue New-LogLine -Value 'Any existing DNS server address information cleared.' try { Get-NetRoute -DestinationPrefix 0.0.0.0/0 -ErrorAction Stop | Remove-NetRoute -Confirm:$false -ErrorAction Stop New-LogLine -Value 'Any existing default gateway information cleared.' } catch { New-LogWarning -Value ('Error while detecting or removing gateways. Errors are expected when no gateways exist. Message: ' -f $_.Exception.Message) } <# End clearing configuration #> <# Begin adapter configuration utility functions; no user serviceable parts inside #> function Configure-Adapter { param( [Parameter(Mandatory=$true)][CimInstance]$NetAdapter, [Parameter()][String]$NewName = '', [Parameter()][String]$IPAddress = '', [Parameter()][Int32]$PrefixLength = '', [Parameter()][String]$DefaultGateway = '', [Parameter()][String[]]$DNSServers = @(), [Parameter()][Switch]$RegisterInDns, [Parameter()][Switch]$EnableVmq ) if($NetAdapter.CimClass.CimClassName -eq 'MSFT_NetAdapter') { if($NewName) { Rename-NetAdapter -InputObject $NetAdapter -NewName $PhysicalAdapterConfiguration.Name New-LogLine -Value ('Renamed to {0}' -f $PhysicalAdapterConfiguration.Name) } if($IPAddress) { $IPErrored = $false try { $IgnoreOutput = New-NetIPAddress -InterfaceIndex $NetAdapter.InterfaceIndex -IPAddress $IPAddress -PrefixLength $PrefixLength -ErrorAction Stop } catch { if($_.Exception.Message.Contains('Inconsistent parameters')) { # this message appears sometimes when IP information has already been set on an object. it does not affect IP assignment New-LogWarning -Value ('Error while assigning IP {0}. Usually this message can be ignored: {1}' -f $IPAddress, $_.Exception.Message) } else { New-LogError -Value ('Error while assigning IP {0}: {1}' -f $IPAddress, $_.Exception.Message) $IPErrored = $true } } if(-not $IPErrored) { New-LogLine -Value ('IP address set to {0} with a prefix length of {1}' -f $IPAddress, $PrefixLength) } } if($DefaultGateway) { $IgnoreOutput = New-NetRoute -InterfaceIndex $NetAdapter.InterfaceIndex -DestinationPrefix 0.0.0.0/0 -NextHop $DefaultGateway New-LogLine -Value ('Default gateway set to {0}' -f $DefaultGateway) } if($DNSServers.Count) { Set-DnsClientServerAddress -InterfaceIndex $NetAdapter.InterfaceIndex -ServerAddresses $DNSServers New-LogLine -Value ('DNS Servers set to {0}' -f ([String]::Join(', ', $PhysicalAdapterConfiguration.DNSServers))) } try { Set-DnsClient -InterfaceIndex $NetAdapter.InterfaceIndex -RegisterThisConnectionsAddress $RegisterInDns -ErrorAction Stop New-LogLine -Value ('Adapter will register in DNS: {0}' -f $PhysicalAdapterConfiguration.RegisterInDns) } catch { New-LogWarning -Value ('Error generated while setting adapter to not register in DNS. These errors are typically benign: {0}' -f $_.Exception.Message) } New-LogLine -Value 'Preparing to set VMQ values. If no VMQ-related entries appear below, no VMQ-related settings were found on this adapter.' $NewVMQValue = 0 if($EnableVmq) { $NewVMQValue = 1 } $VMQValues = Get-NetAdapterAdvancedProperty -InterfaceDescription $NetAdapter.InterfaceDescription | Where-Object -Property 'DisplayName' -Value 'v(irtual)\W*m(achine)\W*q(ueue)?' -Match foreach ($VMQRegistryValue in $VMQValues) { try { Set-NetAdapterAdvancedProperty -InputObject $VMQRegistryValue -RegistryValue $NewVMQValue -ErrorAction Stop New-LogLine -Value ('Set property "{0}" to {1}' -f $VMQRegistryValue.DisplayName, $NewVMQValue) } catch { New-LogError -Value ('Set property "{0}" to {1}. Error message: {2}' -f $VMQRegistryValue.DisplayName, $NewVMQValue, $_.Exception.Message) } } } else { New-LogWarning -Value 'Object supplied for network configuration is not a network adapter' } } <# End adapter configuration utility functions #> <# Begin physical adapter configuration #> New-LogSeparatorLine New-LogLine -Value 'Beginning configuration of physical adapters.' function New-PhysicalAdapterDefinition { param( [Parameter(Mandatory=$true)][String]$Name, [Parameter(Mandatory=$true)][String]$MacAddress, [Parameter()][String]$IPAddress = '', [Parameter()][Int32]$PrefixLength = 24, [Parameter()][String]$DefaultGateway = '', [Parameter()][String[]]$DNSServers = @(), [Parameter()][Switch]$RegisterInDns = $false, [Parameter()][String]$TeamName = '', [Parameter()][Switch]$EnableVmq ) $AdapterObject = New-Object PSObject Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'Name' -Value $Name Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'MacAddress' -Value $MacAddress Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'IPAddress' -Value $IPAddress Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'PrefixLength' -Value $PrefixLength Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'DefaultGateway' -Value $DefaultGateway Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'DNSServers' -Value $DNSServers Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'RegisterInDns' -Value $RegisterInDns Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'TeamName' -Value $TeamName Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'EnableVmq' -Value $EnableVmq $AdapterObject } $PhysicalAdapterConfigurations = @() #### BEGIN Modifiable lines #### # Use the following lines to configure adapters. To leave an adapter unconfigured, do not include an entry. Add or remove entries as necessary. $PhysicalAdapterConfigurations += New-PhysicalAdapterDefinition ` -Name 'Onboard' ` -MacAddress 'A0-B3-CC-E4-F5-5D' ` -IPAddress '192.168.25.11' ` -PrefixLength 24 ` -DefaultGateway '192.168.25.1' ` -DNSServers @('192.168.25.5', '192.168.25.6') ` -RegisterInDns ` -TeamName '' ` -EnableVmq:$false # this entry is only present to show that it exists. If not specified, VMQ is disabled. To enable, just use -EnableVmq $PhysicalAdapterConfigurations += New-PhysicalAdapterDefinition -Name 'PBR' -MacAddress '00-0A-CD-20-DB-0C' -TeamName $TeamVSwitchName $PhysicalAdapterConfigurations += New-PhysicalAdapterDefinition -Name 'PBL' -MacAddress '00-0A-CD-20-DA-F6' -TeamName $TeamVSwitchName $PhysicalAdapterConfigurations += New-PhysicalAdapterDefinition -Name 'PTL' -MacAddress '00-0A-CD-20-DB-0D' -IPAddress '192.168.50.11' -PrefixLength 24 $PhysicalAdapterConfigurations += New-PhysicalAdapterDefinition -Name 'PTR' -MacAddress '00-0A-CD-20-DA-F7' -IPAddress '192.168.51.11' # if not specified, a PrefixLength of 24 is assumed #### END Modifiable lines #### $PhysicalAdapterList = Get-NetAdapter foreach($PhysicalAdapterConfiguration in $PhysicalAdapterConfigurations) { New-LogSeparatorLine New-LogLine -Value ('Configuring physical adapter with MAC address {0}' -f $PhysicalAdapterConfiguration.MacAddress) $PhysicalAdapter = $PhysicalAdapterList | Where-Object -Property 'MacAddress' -Value $PhysicalAdapterConfiguration.MacAddress -EQ if($PhysicalAdapter) { if(-not([String]::IsNullOrEmpty($PhysicalAdapterConfiguration.IPAddress)) -and $PhysicalAdapterConfiguration.TeamName) { New-LogWarning -Value ('Adapter "{0}" has IP information and is marked to be joined to team "{1}". IP information will be lost.' -f $PhysicalAdapterConfiguration.Name, $PhysicalAdapterConfiguration.TeamName) } Configure-Adapter -NetAdapter $PhysicalAdapter -NewName $PhysicalAdapterConfiguration.Name -IPAddress $PhysicalAdapterConfiguration.IPAddress -PrefixLength $PhysicalAdapterConfiguration.PrefixLength -DefaultGateway $PhysicalAdapterConfiguration.DefaultGateway -DNSServers $PhysicalAdapterConfiguration.DNSServers -RegisterInDns:$PhysicalAdapterConfiguration.RegisterInDns -EnableVmq:$PhysicalAdapterConfiguration.EnableVmq } else { New-LogError -Value ('Adapter not found with MAC address {0}' -f $PhysicalAdapterConfiguration.MacAddress) } } <# End physical adapter configuration #> <# Begin teaming configuration #> New-LogSeparatorLine New-LogLine -Value 'Beginning network teaming configuration.' function Get-TeamMembers { param( [Parameter(Mandatory=$true)][String]$TeamName ) ($PhysicalAdapterConfigurations | Where-Object -Property 'TeamName' -Value $TeamName -EQ | Select-Object -Property 'Name').Name } #### BEGIN Modifiable lines #### # Add/remove lines as necessary to create new teams. Match with the team name strings from the beginning of the file $IgnoreOutput = New-NetLbfoTeam -Confirm:$false -TeamMembers (Get-TeamMembers -TeamName $TeamVSwitchName) -Name $TeamVSwitchName -TeamNicName $TeamVSwitchName -TeamingMode Lacp -LoadBalancingAlgorithm Dynamic Configure-Adapter -NetAdapter (Get-NetAdapter $TeamVSwitchName) # as-is, assumes tNIC name equals team name and clears any IP, DNS, and VMQ settings. for teams that won't hold vswitches, configure IP info # Add/remove lines to create team NICs other than the default #Add-NetLbfoTeamNic -Team $TeamNotAVSwitchName -VlanID 42 -Name 'ThisAdditionalTeamNicName' -Confirm:$false #ConfigureAdapter -NetAdapter 'ThisAdditionalTeamNicName' -IPAddress... #### END Modifiable lines #### $Teams = Get-NetLbfoTeam $TeamNICs = Get-NetLbfoTeamNic foreach ($Team in $Teams) { $ThisTeamsMembers = [String]::Join((", ", $Team.Members)) New-LogLine -Value ('Team {0} created from members {1}' -f $Team.Name, $ThisTeamsMembers) } foreach ($TeamNIC in $TeamNICs) { New-LogLine -Value ('Team NIC "{0}" created on team "{1}"' -f $TeamNIC.Name, $TeamNIC.Team) } <# End teaming configuration #> <# Begin virtual switch configuration #> New-LogSeparatorLine New-LogLine -Value 'Beginning virtual switch configuration.' #### BEGIN Modifiable lines #### # modify, add, or remove following lines to configure virtual switch(es) $VirtualSwitchName = 'vSwitch' # use different variable names for additional virtual switches; variables used again in the virtual adapters segment $IgnoreOutput = New-VMSwitch -Name $VirtualSwitchName -Confirm:$false -AllowManagementOS $false -NetAdapterName $TeamVSwitchName -MinimumBandwidthMode Weight -EnableIov $false #### END Modifiable lines #### $Switches = Get-VMSwitch foreach($VMSwitch in $Switches) { New-LogLine -Value ('Virtual switch "{0}" created.' -f $VMSwitch.Name) if($VMSwitch.SwitchType -eq 'External') { foreach ($TeamNIC in $TeamNICs) { if(($TeamNIC.InterfaceDescription -eq $VMSwitch.NetAdapterInterfaceDescription) -and $TeamNIC.Primary -eq $false) { # KNOWN ISSUE: can trigger multiple times; more work to prevent than the duplication justifies New-LogWarning -Value ('Team {0} hosts a virtual switch and multiple team NICs. This configuration is not supported by Microsoft and may lead to unpredictable QoS behavior.' -f $TeamNIC.Team) } } } } <# End virtual switch configuration #> <# Begin virtual adapter configuration #> New-LogSeparatorLine New-LogLine -Value 'Beginning management operating system virtual adapter configuration.' function New-VirtualAdapterDefinition { param( [Parameter(Mandatory=$true)][String]$Name, [Parameter()][String]$SwitchName, [Parameter()][String]$MacAddress, [Parameter()][String]$IPAddress = '', [Parameter()][Int32]$PrefixLength = 24, [Parameter()][String]$DefaultGateway = '', [Parameter()][String[]]$DNSServers = @(), [Parameter()][Switch]$RegisterInDns = $false, [Parameter()][Int32]$VlanId = 0, [Parameter()][UInt32]$VmqWeight = 100, [Parameter()][UInt32]$IovWeight = 0, [Parameter()][Int64]$MinimumBandwidth = 0, [Parameter()][Int64]$MaximumBandwidth = 0 ) $AdapterObject = New-Object PSObject Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'Name' -Value $Name Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'SwitchName' -Value $SwitchName Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'MacAddress' -Value $MacAddress Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'IPAddress' -Value $IPAddress Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'PrefixLength' -Value $PrefixLength Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'DefaultGateway' -Value $DefaultGateway Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'DNSServers' -Value $DNSServers Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'RegisterInDns' -Value $RegisterInDns Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'VlanId' -Value $VlanId Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'VmqWeight' -Value $VmqWeight Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'IovWeight' -Value $IovWeight Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'MinimumBandwidth' -Value $MinimumBandwidth Add-Member -InputObject $AdapterObject -MemberType NoteProperty -Name 'MaximumBandwidth' -Value $MaximumBandwidth $AdapterObject } $VirtualAdapterConfigurations = @() #### BEGIN Modifiable lines #### # Add/remove line groups to create and configure management operating system virtual adapters $VirtualAdapterConfigurations += New-VirtualAdapterDefinition -Name 'Cluster' -SwitchName $VirtualSwitchName -IPAddress '192.168.10.11' -VlanId 10 -MinimumBandwidth 5 $VirtualAdapterConfigurations += New-VirtualAdapterDefinition -Name 'LiveMigration' -SwitchName $VirtualSwitchName -IPAddress '192.168.15.11' -VlanId 15 -MinimumBandwidth 10 #Example with more parameters: $VirtualAdapters += New-VirtualAdapterDefinition -Name 'Management' -IPAddress '192.168.25.10' -DefaultGateway '192.168.25.1' -DNSServers @('192.168.25.5', '192.168.25.6') #### END Modifiable lines #### foreach($VirtualAdapterConfiguration in $VirtualAdapterConfigurations) { New-LogSeparatorLine New-LogLine -Value ('Configuring virtual adapter "{0}"' -f $VirtualAdapterConfiguration.Name) $AdapterParameters = @{} $AdapterParameters.Add('ManagementOS', $true) $AdapterParameters.Add('Name', $VirtualAdapterConfiguration.Name) $AdapterParameters.Add('SwitchName', $VirtualAdapterConfiguration.SwitchName) New-LogLine -Value ('Created on switch {0}' -f $VirtualAdapterConfiguration.SwitchName) if([String]::IsNullOrEmpty($VirtualAdapterConfiguration.MacAddress)) { $AdapterParameters.Add('DynamicMacAddress', $true) New-LogLine -Value 'Using dynamic MAC Address' } else { $AdapterParameters.Add('StaticMacAddress', $VirtualAdapterConfiguration.MacAddress) New-LogLine -Value ('Using static MAC Address' -f $VirtualAdapterConfiguration.MacAddress) } $AdapterParameters.Add('PassThru', $true) $AdditionalParameters = @{} foreach($Switch in $Switches) { if($Switch.Name -eq $VirtualAdapterConfiguration.SwitchName) { switch ($Switch.BandwidthReservationMode) { Weight { $QoSWeight = $VirtualAdapterConfiguration.MinimumBandwidth $AdditionalParameters.Add('MaximumBandwidth', $VirtualAdapterConfiguration.MaximumBandwidth) if($VirtualAdapterConfiguration.MinimumBandwidth -gt 100) { New-LogError -Value ('QoS weight cannot exceed 100%. Changing {0} to {1}' -f $QoSWeight, $MinimalQoSWeight) $QoSWeight = [UInt32]$MinimalQoSWeight } $AdditionalParameters.Add('MinimumBandwidthWeight', $QoSWeight) } Absolute { $AdditionalParameters.Add('MinimumBandwidthAbsolute', $VirtualAdapterConfiguration.MinimumBandwidth) } #default { } # everything is ignored otherwise } $AdditionalParameters.Add('VmqWeight', $VirtualAdapterConfiguration.VmqWeight) $AdditionalParameters.Add('IovWeight', $VirtualAdapterConfiguration.IovWeight) } } $VlanParameters = @{} $VlanParameters.Add('Access', $true) if($VirtualAdapterConfiguration.VlanId) { $VlanParameters.Add('VlanId', $VirtualAdapterConfiguration.VlanId) New-LogLine -Value ('Assigning to VLAN {0}' -f $VirtualAdapterConfiguration.VlanId) } else { $VlanParameters.Add('Untagged', $true) New-LogLine -Value 'Assigned to the default VLAN.' } try { $VirtualAdapter = Add-VMNetworkAdapter @AdapterParameters -ErrorAction Stop } catch { New-LogError -Value ('Unable to create virtual adapter. Message: ' -f $_.Exception.Message) continue } Set-VMNetworkAdapter -VMNetworkAdapter $VirtualAdapter @AdditionalParameters Set-VMNetworkAdapterVlan -VMNetworkAdapter $VirtualAdapter @VlanParameters $ManagementOSVNetAdapter = Get-NetAdapter | Where-Object -Property DeviceID -EQ $VirtualAdapter.DeviceId Configure-Adapter -NetAdapter $ManagementOSVNetAdapter -IPAddress $VirtualAdapterConfiguration.IPAddress -PrefixLength $VirtualAdapterConfiguration.PrefixLength -DefaultGateway $VirtualAdapterConfiguration.DefaultGateway -DNSServers $VirtualAdapterConfiguration.DNSServers -RegisterInDns $VirtualAdapterConfiguration.RegisterInDns } <# End virtual adapter configuration #> <# Begin final host configuration; no user serviceable parts inside #> if($DomainName) { $CredentialUser = '' if($DomainName.Contains(".")) { $CredentialUser = '{0}@{1}' -f $DomainUser, $DomainName } else { $CredentialUser = '{0}\{1}' -f $DomainName, $DomainUser } $CredentialPassword = ConvertTo-SecureString -String $DomainPassword -AsPlainText -Force $DomainJoinCredentials = New-Object -TypeName pscredential -ArgumentList ($CredentialUser, $CredentialPassword) $DomainJoinParameters = @{} $DomainJoinParameters.Add('DomainName', $DomainName) $DomainJoinParameters.Add('Credential', $DomainJoinCredentials) if(-not([String]::IsNullOrEmpty($DomainOU))) { $DomainJoinParameters.Add('OUPath', $DomainOU) } $DomainJoinSucceeded = $false try { Add-Computer @DomainJoinParameters -Force -ErrorAction Stop $DomainJoinSucceeded = $true } catch [InvalidOperationException] { if($_.FullyQualifiedErrorId -match 'FailToJoinDomainFromWorkgroup') { try { # if the account already exists then OUPath cannot be specified. unfortunately, there is no way to check in advance without loading the AD cmdlets $DomainJoinParameters.Remove('OUPath') Add-Computer @DomainJoinParameters -Force -ErrorAction Stop $DomainJoinSucceeded = $true } catch { New-LogError -Value ('Unable to join domain {0}: {1}' -f $DomainName, $_.Exception.Message) } } if(-not $DomainJoinSucceeded) { New-LogError -Value ('Unable to join domain {0}: {1}' -f $DomainName, $_.Exception.Message) } } catch { New-LogError -Value ('Unable to join domain {0}: {1}' -f $DomainName, $_.Exception.Message) } if($DomainJoinSucceeded) { New-LogLine -Value ('Joined domain {0}. Restarting to complete.' -f $DomainName) Restart-PartiallyRebuiltHost -NextAction Finish } } Complete-HostRebuild # this is a fall-through in case the domain join does not occur } function Complete-HostRebuild { if(Get-ScheduledJob | Where-Object -Property Name -Value 'RebuildHost' -EQ) { Unregister-ScheduledTask -TaskName 'RebuildHost' -Confirm:$false New-LogLine -Value 'Restart successful. Deleting scheduled job.' } @($DefaultVMPath, $DefaultVHDPath) | foreach { if(-not([String]::IsNullOrEmpty($_))) { if(-not(Test-Path $_)) { $IgnoreOutput = New-Item -Path $_ -ItemType Directory New-LogLine -Value ('Folder {0} created.' -f $_) } } } if($DefaultVMPath) { Set-VMHost -VirtualMachinePath $DefaultVMPath New-LogLine -Value ('Default virtual machine path set to "{0}"' -f $DefaultVMPath) } if($DefaultVHDPath) { Set-VMHost -VirtualHardDiskPath $DefaultVHDPath New-LogLine -Value ('Default virtual hard disk path set to "{0}"' -f $DefaultVHDPath) } <# Begin iSCSI configuration #> Set-Service -Name MSiSCSI -StartupType Automatic Start-Service -Name MSiSCSI $iSCSIError = $false #### BEGIN Modifiable lines #### $UseMPIOForiSCSI = $true # duplicate and/or reconfigure following lines as necessary # use caution if attempting to shorten script! authentication methods etc. may not duplicate well! ## set up all portals first; for multiple portals, will need to use variables other than $Portal as the connections will operate on individual portal objects try { $Portal = New-IscsiTargetPortal -TargetPortalAddress '192.168.25.12' -InitiatorPortalAddress '192.168.25.11' } catch { New-LogError -Value ('Unable to establish a connection to portal: {0}' -f $_.Exception.Message) $iSCSIError = $true } # leave the following 'if' block alone; skip past for connection configuration if($UseMPIO -and $UseMPIOForiSCSI -and -not $iSCSIError) { $Script:RestartRequired = Enable-MSDSMAutomaticClaim -BusType iSCSI } $DiscoveredTargets = @() # do not remove; this ensures that the foreach doesn't fail if(-not ($iSCSIError)) { try { $DiscoveredTargets = Get-IscsiTarget -IscsiTargetPortal $Portal } catch { New-LogError -Value ('Unable to retrieve iSCSI target(s) from portal {0}: {1}' -f $Portal.TargetPortalAddress) $iSCSIError = $true } } ## duplicate the following per portal, changing IPs and adding authentication information as necessary; will need to use variable names other than $Portal # TODO: this COULD be collapsed further by using custom PS objects to aggregate settings for splatting and looping, but with the vast number of iSCSI options, the outcome would not be significantly less script except in environments with lots of iSCSI portals foreach($DiscoveredTarget in $DiscoveredTargets) { # WARNING: any pre-existing session data is NOT cleared, due to the complexity of ensuring that it is done correctly ## set target and initiator IPs as necessary $Session1TargetIP = '192.168.50.100' $Session1InitiatorIP = '192.168.50.11' $Session2TargetIP = '192.168.51.100' $Session2InitiatorIP = '192.168.51.11' # if MPIO wasn't enabled, errors will be logged if more than one session is pointed to the same target try { $Session = Connect-IscsiTarget -NodeAddress $DiscoveredTarget.NodeAddress -TargetPortalAddress $Session1TargetIP -InitiatorPortalAddress $Session1InitiatorIP -IsMultipathEnabled ($UseMPIO -band $UseMPIOForiSCSI) -ErrorAction Stop $IgnoreOutput = Register-IscsiSession -InputObject $Session New-LogLine -Value ('iSCSI connection created to {0}' -f $DiscoveredTarget.NodeAddress) } catch { New-LogError -Value ('Cannot establish iSCSI connection to "{0}: {1}"' -f $DiscoveredTarget.NodeAddress, $_.Exception.Message) } try { $Session = Connect-IscsiTarget -NodeAddress $DiscoveredTarget.NodeAddress -TargetPortalAddress $Session2TargetIP -InitiatorPortalAddress $Session2InitiatorIP -IsMultipathEnabled ($UseMPIO -band $UseMPIOForiSCSI) -ErrorAction Stop $IgnoreOutput = Register-IscsiSession -InputObject $Session New-LogLine -Value ('iSCSI connection created to {0}' -f $DiscoveredTarget.NodeAddress) } catch { New-LogError -Value ('Cannot establish iSCSI connection to "{0}: {1}"' -f $DiscoveredTarget.NodeAddress, $_.Exception.Message) } } #### END Modifiable lines #### <# End iSCSI configuration #> #### BEGIN Modifiable lines #### # any other host customizations can go here #### END Modifiable lines #### <# End final host configuration #> New-LogLine -Value 'Configuration complete!' if($RestartRequired) { New-LogLine -Value 'Restarting for final cleanup.' Restart-Computer } } throw ("You didn't read the instructions, did you?") switch($Action) { 'Start' { Start-HostRebuild } 'Main' { Resume-HostRebuild } 'Finish' { Complete-HostRebuild } } |
This is a copy of the RebuildHost.log file from my testing:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
*********************************************************** Configuration began on 2/13/2016 12:58:43 PM *********************************************************** Time zone set to Central Standard Time *********************************************************** Beginning basic host configuration Computer renamed to "svhv2" Hyper-V installed. Built scheduled job to restart host and automatically resume script. Restarting computer... Restart successful. Deleting scheduled job. Any existing virtual switches removed. Any existing teams cleared. Any existing IP information cleared. Any existing DNS server address information cleared. Any existing default gateway information cleared. *********************************************************** Beginning configuration of physical adapters. *********************************************************** Configuring physical adapter with MAC address A0-B3-CC-E4-F5-5D Renamed to Onboard IP address set to 192.168.25.11 with a prefix length of 24 Default gateway set to 192.168.25.1 DNS Servers set to 192.168.25.5, 192.168.25.6 Adapter will register in DNS: True Preparing to set VMQ values. If no VMQ-related entries appear below, no VMQ-related settings were found on this adapter. *********************************************************** Configuring physical adapter with MAC address 00-0A-CD-20-DB-0C Renamed to PBR Adapter will register in DNS: False Preparing to set VMQ values. If no VMQ-related entries appear below, no VMQ-related settings were found on this adapter. *********************************************************** Configuring physical adapter with MAC address 00-0A-CD-20-DA-F6 Renamed to PBL Adapter will register in DNS: False Preparing to set VMQ values. If no VMQ-related entries appear below, no VMQ-related settings were found on this adapter. *********************************************************** Configuring physical adapter with MAC address 00-0A-CD-20-DB-0D Renamed to PTL IP address set to 192.168.50.11 with a prefix length of 24 Adapter will register in DNS: False Preparing to set VMQ values. If no VMQ-related entries appear below, no VMQ-related settings were found on this adapter. *********************************************************** Configuring physical adapter with MAC address 00-0A-CD-20-DA-F7 Renamed to PTR IP address set to 192.168.51.11 with a prefix length of 24 Adapter will register in DNS: False Preparing to set VMQ values. If no VMQ-related entries appear below, no VMQ-related settings were found on this adapter. *********************************************************** Beginning network teaming configuration. Adapter will register in DNS: False Preparing to set VMQ values. If no VMQ-related entries appear below, no VMQ-related settings were found on this adapter. Set property "Virtual Machine Queues" to 0 Set property "Virtual Machine Queues - Shared Memory" to 0 Set property "Virtual Machine Queues - VLAN Id Filtering" to 0 Team vSwitchTeam created from members Team NIC "vSwitchTeam" created on team "vSwitchTeam" *********************************************************** Beginning virtual switch configuration. Virtual switch "vSwitch" created. *********************************************************** Beginning management operating system virtual adapter configuration. *********************************************************** Configuring virtual adapter "Cluster" Created on switch vSwitch Using dynamic MAC Address Assigning to VLAN 10 Renamed to PTR IP address set to 192.168.10.11 with a prefix length of 24 Adapter will register in DNS: False Preparing to set VMQ values. If no VMQ-related entries appear below, no VMQ-related settings were found on this adapter. *********************************************************** Configuring virtual adapter "LiveMigration" Created on switch vSwitch Using dynamic MAC Address Assigning to VLAN 15 Renamed to PTR IP address set to 192.168.15.11 with a prefix length of 24 Adapter will register in DNS: False Preparing to set VMQ values. If no VMQ-related entries appear below, no VMQ-related settings were found on this adapter. Joined domain siron.int. Restarting to complete. Folder C:\LocalVMs created. Folder C:\LocalVMs\Virtual Hard Disks created. Default virtual machine path set to "C:\LocalVMs" Default virtual hard disk path set to "C:\LocalVMs\Virtual Hard Disks" iSCSI connection created to iqn.1991-05.com.microsoft:svstore-csvs-target iSCSI connection created to iqn.1991-05.com.microsoft:svstore-csvs-target iSCSI connection created to iqn.1991-05.com.microsoft:svstore-quorum-target iSCSI connection created to iqn.1991-05.com.microsoft:svstore-quorum-target Configuration complete! Restarting for final cleanup. |
Feedback
I’d like to know what you think of this. If you find it useful, please let me know. If there’s something that would be good to add for the community’s use, suggest it in the comments.






does this script works for Windows server 2019 too ?
kind regards, and keep the awesome work !
It doesn’t incorporate any new features, but yes it works.
Why is your avatar black when you’re a white guy?
Doesn’t look black on any of my screens or devices.