PowerShell Script: Deploy VMs, and Configure the Guest OS in one Go on Hyper-V

I’ve been prepping for a lot of different speaking engagements coming up in the next few months and a very hot topic these days is the use of PowerShell and automation, when it comes to Hyper-V. With this in mind, I’ve prepped the below script for some of these upcoming discussions, and wanted to share it with the community so that it’ll be of help to some people.

The Story

Awhile back, I spoke at a VMware VMUG event where I talked about advanced VM provisioning using PowerCLI and templates. In that talk, I taught the group how to provision a virtual machine using the shell, and then reach into the VM and execute a number of commands against the guest OS to achieve even greater amounts of automation and followed up by posting the script HERE.

Well, I wanted to take the goal from that script and apply it to a Hyper-V environment using PowerShell Direct. For those that aren’t aware, PowerShell Direct is a new feature in the Windows Server 2016 feature set, that allows us to inject PowerShell commands into the guest OS of a running VM, without the network for networking. Without getting too into the specifics, it does this via the VMbus on the Hyper-V host, and it works VERY well.

NOTE: You must have either Windows Server 2016 TP5 or newer and/or Windows 10 running as BOTH the host and the guest in order for PowerShell Direct to work.

Let’s cover the workflow and the goal of this script first.

Technical Goals of the Script

Must be FULLY automated from the moment you hit the enter key to start the script. Must provide verbose information for the administrators viewing if desired. Must Deploy 2 VMs from a pre-existing Sysprepped VHDX with an embedded unattend.xml answer file. Must be able to define static IP addresses. Must configure AD on one of the new VMs and provision a new AD Forest. Must create a new custom administrative account within the domain. 2nd VM must be automatically joined to the newly defined domain. File Services must be installed on the second VM A new SMB share must be defined on the file server VM, once the file services role is present.

This is certainly a lot to get done with a single press of the enter key, but it is doable. Also, this is likely just the beginnings of a new environment, but the script could very easily be modified to deploy more than just the two VMs.

Community Goals of the Script

Just like the PowerCLI/VMware script, My communities goals with THIS script remain the same. My goal from a teaching aspect with this script was to be the follow:

Well Documented and Heavily Commented VERY sequential ordering of the commands, as to be easy to follow for beginners Best Practices definition of variables at the top of the script. Well segmented so that certain sections can be copied and duplicated if desired

The Secret Sauce

So what are the important bits that make this script work? With every script there are key points that really stand out, and I always like to point them out.

Pre-Prepared VHDX – While not strictly script related, this certainly makes the job much easier. The script assumes you have a VHDX file that already has Server 2016 installed and patched. It also assumes that you’ve sysprepped that image, and you’ve used the Windows ADK toolset to create an answer file. This takes care of the bulk of the OS customization upon deployment of the new VMs.

While Loop – If you look at the script, I have a small chunk of code in there that is from Ben Armstrong on the Hyper-V product group team. It essentially attempts to write a string of text to the console of the guest VM using PowerShell Direct, until it’s successful. What this allows you to do in your script execution is have it wait until PowerShell direct is functional before moving on. Very useful for sequential operations.

Invoke-Command – While not a new ground breaking cmdlet, this script wouldn’t be possible without it. The invoke-command cmdlet is the one cmdlet in this script that is doing the bulk of the work. You’ll see it used with the -VMName parameter extensively throughout the script. When used with the -VMName parameter, it lets PowerShell know that it’s running the mentioned script block with PowerShell direct and NOT standard PowerShell Remoting. See the script for examples.

The Script

So here is the part you’ve been waiting for. Below is the script. Take it! Use it. Learn from it! Hopefully it will be of some use!

Again the script is heavily self-documented and commented, so you should be able to read through it and set the needed variables as needed. Note, all of the user-definable variables are at the top of the script.

# DISCLAIMER: There are no warranties or support provided for this script. Use at you're own discretion. Andy Syrewicze and/or Altaro Software are not liable for any # damage or problems that misuse of this script may cause. # Script is Written by Andy Syrewicze - Tech. Evangelist with Altaro Software and is free to use as needed within your organization. # This Script is a deployment script, it will first copy a pre-prepared VHDX, and create a new VM with it based on the settings below. Then the script deploys Active # Directory to then VM and then provisions a new forest. Once complete, another new VM is created in the same manner and it is provisioned as a file server and a new # Share is created. # Assumptions # Your Hyper-V host is running Windows Server 2016 TP5, Windows 10 or newer # Your Target Guest OS is running Windows Server 2016 TP5, Windows 10 or newer # You have a pre-created "Golden Image" VHDX that has been syspreped with the /generalize /oobe and /mode:VM switches. # You have created an answer file using the Windows SIM (System Image Manager) which is part of the Windows 10 ADK # https://developer.microsoft.com/en-us/windows/hardware/windows-assessment-deployment-kit#winADK # You have placed your answer file inside of the VHDX by mounting it in Windows 10 and then placing the unattend.xml file in the path # C:\Windows\Panther # First we set some targeting variables for the Host environment # The $vSwitch Variable is the Name of the vSwitch you'd like the new VMs to use for network connectivity # $VHDXPath is the root path of your VHDX storage $vSwitch = "Deployment Test" $VHDXPath = "C:\users\Public\Documents\Hyper-V\Virtual hard disks" # Global IP Settings Below # NOTE: The cmdlets below reference the subnet mask bit for configuration of the IP Settings. Currently this script is configured for a standard # Class C /24 subnet. $SubMaskBit = "24" # Domain Controller Settings Below # NOTE: As of the time of this writing, with 2016 TP5, the Domain Mode and Forest Mode are called "WinThreshold" I Expect this to change # Once GA for 2016 is released. It will likely follow the previous naming scheme and be something like "win2016" $DCVMName = "TEST-DC01" $DCIP = "10.0.50.15" $DomainMode = "WinThreshold"; $ForestMode = "WinThreshold"; $DomainName = "TestDomain.lcl"; $DSRMPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $NewAdminUserName = "TestAdmin" $NewAdminUserPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force # File Server Settings Below $FSVMName = "TEST-FS01" $FSIP = "10.0.50.30" $SharePath = "C:\ShareTest" $FolderName = "Public" $ShareName = "Public" # Then we setup some credentials to be called throughout the script # NOTE: These are the credentials used within the guest VM. # NOTE: If you only have a single image, you can likely get by with a single set of local credentials, instead of one for each # workload. $DCLocalUser = "$DCVMName\Administrator" $DCLocalPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $DCLocalCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $DCLocalUser, $DCLocalPWord # Below Credentials are used by the File Server VM for first login to be able to add the machine to the new Domain. $FSLocalUser = "$FSVMName\administrator" $FSLocalPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $FSLocalCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $FSLocalUser, $FSLocalPWord # The below credentials are used by operations below once the domain controller virtual machine and the new domain are in place. These credentials should match the credentials # used during the provisioning of the new domain. $DomainUser = "$DomainName\administrator" $DomainPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $DomainCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $DomainUser, $DomainPWord ############################# Command Execution Starts Below ################################### # The below section Provisions and Configures the Domain Controller with the Variables defined above # First we make a copy of the sysprepped "Gold Image" VHDX file. Also, note that a Unattend.XML file has been placed within the image as well. Write-Verbose "Copying Master VHDX and Deploying new VM with name [$DCVMName]" -Verbose Copy-Item "$VHDXPath\MASTER.vhdx" "$VHDXPath\$DCVMNAME.vhdx" Write-Verbose "VHDX Copied, Building VM...." -Verbose New-VM -Name $DCVMName -MemoryStartupBytes 1GB -VHDPath "$VHDXPath\$DCVMName.vhdx" -Generation 2 -SwitchName $vSwitch Write-Verbose "VM Creation Completed. Starting VM [$DCVMName]" -Verbose Start-VM -Name $DCVMName # After the inital provisioning, we wait until PowerShell Direct is functional and working within the guest VM before moving on. # Big thanks to Ben Armstrong for the below useful Wait code Write-Verbose “Waiting for PowerShell Direct to start on VM [$DCVMName]” -Verbose while ((icm -VMName $DCVMName -Credential $DCLocalCredential {“Test”} -ea SilentlyContinue) -ne “Test”) {Sleep -Seconds 1} Write-Verbose "PowerShell Direct responding on VM [$DCVMName]. Moving On...." -Verbose # Next we configure the networking for the new DC VM. # NOTE: that the host variables are passed through by making use of the param command along with the -ArgumentList Paramater at the end of # the ScriptBlock. # NOTE: The InterfaceAlias value may be different for your gold image, so adjust accordingly. # NOTE: InterfaceAlias can be found by making use of the Get-NetIPAddress Cmdlet Invoke-Command -VMName $DCVMName -Credential $DCLocalCredential -ScriptBlock { param ($DCVMName, $DCIP, $SubMaskBit, $DFGW) New-NetIPAddress -IPAddress "$DCIP" -InterfaceAlias "Ethernet 2" -PrefixLength "$SubMaskBit" | Out-Null $DCEffectiveIP = Get-NetIPAddress -InterfaceAlias "Ethernet 2" | Select-Object IPAddress Write-Verbose "Assigned IPv4 and IPv6 IPs for VM [$DCVMName] are as follows" -Verbose Write-Host $DCEffectiveIP | Format-List Write-Verbose "Updating Hostname for VM [$DCVMName]" -Verbose Rename-Computer -NewName "$DCVMName" } -ArgumentList $DCVMName, $DCIP, $SubMaskBit, $DFGW Write-Verbose "Rebooting VM [$DCVMName] for hostname change to take effect" -Verbose Stop-VM -Name $DCVMName Start-VM -Name $DCVMName Write-Verbose “Waiting for PowerShell Direct to start on VM [$DCVMName]” -Verbose while ((icm -VMName $DCVMName -Credential $DomainCredential {“Test”} -ea SilentlyContinue) -ne “Test”) {Sleep -Seconds 1} Write-Verbose "PowerShell Direct responding on VM [$DCVMName]. Moving On...." -Verbose # Next we'll proceed by installing the Active Directory Role and then configuring the machine as a new DC in a new AD Forest Invoke-Command -VMName $DCVMName -Credential $DCLocalCredential -ScriptBlock { param ($DCVMName, $DomainMode, $ForestMode, $DomainName, $DSRMPWord) Write-Verbose "Installing Active Directory Services on VM [$DCVMName]" -Verbose Install-WindowsFeature -Name "AD-Domain-Services" -IncludeManagementTools Write-Verbose "Configuring New Domain with Name [$DomainName] on VM [$DCVMName]" -Verbose Install-ADDSForest -ForestMode $ForestMode -DomainMode $DomainMode -DomainName $DomainName -InstallDns -NoDNSonNetwork -SafeModeAdministratorPassword $DSRMPWord -Force -NoRebootOnCompletion } -ArgumentList $DCVMName, $DomainMode, $ForestMode, $DomainName, $DSRMPWord Write-Verbose "Rebooting VM [$DCVMName] to complete installation of new AD Forest" -Verbose Stop-VM -Name $DCVMName Start-VM -Name $DCVMName Write-Verbose “Waiting for PowerShell Direct to start on VM [$DCVMName]” -Verbose while ((icm -VMName $DCVMName -Credential $DomainCredential {“Test”} -ea SilentlyContinue) -ne “Test”) {Sleep -Seconds 1} Write-Verbose "PowerShell Direct responding on VM [$DCVMName]. Moving On...." -Verbose Write-Verbose "DC Provisioning Complete!!!!" -Verbose # We're going to setup an AD Administrative user based on the variables above that will have access to the file share we create below # Note that we do this in a loop as it will take some time for AD to be ready inside of the new DC VM. As such this command will execute # Until it is successful, and as a result, we know that AD is ready for the rest of the script. Write-Verbose "Creating new Administrative User within Domain [$DomainName] That will have access to Share [$ShareName] on VM [$FSVMName]" -Verbose Invoke-Command -VMName $DCVMName -Credential $DomainCredential -ScriptBlock { param ($NewAdminUserName, $NewAdminUserPWord) Write-Verbose "Waiting for AD Web Services to be in a running state" -Verbose $ADWebSvc = Get-Service ADWS | Select-Object * while($ADWebSvc.Status -ne 'Running') { Start-Sleep -Seconds 1 } Do { Start-Sleep -Seconds 30 Write-Verbose "Waiting for AD to be Ready for User Creation" -Verbose New-ADUser -Name "$NewAdminUserName" -AccountPassword $NewAdminUserPWord Enable-ADAccount -Identity "$NewAdminUserName" $ADReadyCheck = Get-ADUser -Identity $NewAdminUserName } Until ($ADReadyCheck.Enabled -eq "True") Add-ADGroupMember -Identity "Domain Admins" -Members "$NewAdminUserName" } -ArgumentList $NewAdminUserName, $NewAdminUserPWord Write-Verbose "User [$NewAdminUserName] Created." -Verbose # The below section is used to Provision a new file server VM, add it to the new domain, and configure a basic share. # First we make a copy of the sysprepped "Gold Image" VHDX file. Also, note that a Unattend.XML file has been placed within the image as well. Write-Verbose "Copying Master VHDX and Deploying new VM with name [$FSVMName]" -Verbose Copy-Item "$VHDXPath\MASTER.vhdx" "$VHDXPath\$FSVMNAME.vhdx" Write-Verbose "VHDX Copied, Building VM...." -Verbose New-VM -Name $FSVMName -MemoryStartupBytes 1GB -VHDPath "$VHDXPath\$FSVMName.vhdx" -Generation 2 -SwitchName $vSwitch Write-Verbose "VM Creation Completed. Starting VM [$FSVMName]" -Verbose Start-VM -Name $FSVMName # After the inital provisioning, we wait until the PowerShell Direct is functional and working within the guest VM before moving on. # Big thanks to Ben Armstrong for the below useful Wait code Write-Verbose “Waiting for PowerShell Direct to start on VM [$FSVMName]” -Verbose while ((icm -VMName $FSVMName -Credential $FSLocalCredential {“Test”} -ea SilentlyContinue) -ne “Test”) {Sleep -Seconds 1} Write-Verbose "PowerShell Direct responding on VM [$FSVMName]. Moving On...." -Verbose # Next we configure the networking for the new FS VM. # NOTE: that the host variables are passed through by makinguse of the param command along with the -ArgumentList Paramater at the end of # the ScriptBlock. # NOTE: The InterfaceAlias value may be different for your gold image, so adjust accordingly. Invoke-Command -VMName $FSVMName -Credential $FSLocalCredential -ScriptBlock { param ($FSVMName, $FSIP, $SubMaskBit, $DFGW, $DCVMName, $DCIP) New-NetIPAddress -IPAddress "$FSIP" -InterfaceAlias "Ethernet 2" -PrefixLength "$SubMaskBit" | Out-Null $FSEffectiveIP = Get-NetIPAddress -InterfaceAlias "Ethernet 2" | Select-Object IPAddress Write-Verbose "Assigned IPv4 and IPv6 IPs for VM [$FSVMName] are as follows" -Verbose Write-Host $FSEffectiveIP | Format-List Write-Verbose "Setting DNS Source to [$DCVMName] with IP [$DCIP]" -Verbose Set-DnsClientServerAddress -InterfaceAlias "Ethernet 2" -ServerAddresses "$DCIP" Write-Verbose "Updating Hostname for VM [$FSVMName]" -Verbose Rename-Computer -NewName "$FSVMName" } -ArgumentList $FSVMName, $FSIP, $SubMaskBit, $DFGW, $DCVMName, $DCIP Write-Verbose "Rebooting VM [$FSVMName] for hostname change to take effect" -Verbose Stop-VM -Name $FSVMName Start-VM -Name $FSVMName Write-Verbose “Waiting for PowerShell Direct to start on VM [$FSVMName]” -Verbose while ((icm -VMName $FSVMName -Credential $FSLocalCredential {“Test”} -ea SilentlyContinue) -ne “Test”) {Sleep -Seconds 1} Write-Verbose "PowerShell Direct responding on VM [$FSVMName]. Moving On...." -Verbose # The below Adds the File Server VM to the newly Created Domain. Write-Verbose "Adding VM [$FSVMName] to domain [$DomainName]" -Verbose Invoke-Command -VMName $FSVMName -Credential $FSLocalCredential -ScriptBlock { param ($DomainName, $DomainCredential) Add-Computer -DomainName $DomainName -Credential $DomainCredential } -ArgumentList $DomainName, $DomainCredential Write-Verbose "Initiating Reboot of VM [$FSVMName] to complete domain join to domain [$DomainName]" -Verbose Stop-VM -Name $FSVMName Start-VM -Name $FSVMName Write-Verbose “Waiting for PowerShell Direct to start on VM [$FSVMName]” -Verbose while ((icm -VMName $FSVMName -Credential $DomainCredential {“Test”} -ea SilentlyContinue) -ne “Test”) {Sleep -Seconds 1} Write-Verbose "PowerShell Direct responding on VM [$FSVMName]. Moving On...." -Verbose # Now we install the File Server Role and Create the Share Write-Verbose "Installing File-Server Role on VM [$FSVMName]." -Verbose Invoke-Command -VMName $FSVMName -Credential $DomainCredential -ScriptBlock { param ($SharePath, $FolderName, $ShareName, $DomainName, $NewAdminUserName) Install-WindowsFeature -Name "FS-FileServer" -IncludeManagementTools Write-Verbose "Creating File Share [$ShareName] at path [$SharePath\$Foldername]." -Verbose New-Item -Path $SharePath -Name $FolderName -ItemType "Directory"; New-SmbShare -Name "$ShareName" -Path "$SharePath\$FolderName" -FullAccess "$DomainName\$NewAdminUserName" } -ArgumentList $SharePath, $FolderName, $ShareName, $DomainName, $NewAdminUserName Write-Verbose "Environment Setup Complete. End of Script" -Verbose # END OF SCRIPT 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 # DISCLAIMER: There are no warranties or support provided for this script. Use at you're own discretion. Andy Syrewicze and/or Altaro Software are not liable for any # damage or problems that misuse of this script may cause. # Script is Written by Andy Syrewicze - Tech. Evangelist with Altaro Software and is free to use as needed within your organization. # This Script is a deployment script, it will first copy a pre-prepared VHDX, and create a new VM with it based on the settings below. Then the script deploys Active # Directory to then VM and then provisions a new forest. Once complete, another new VM is created in the same manner and it is provisioned as a file server and a new # Share is created. # Assumptions # Your Hyper-V host is running Windows Server 2016 TP5, Windows 10 or newer # Your Target Guest OS is running Windows Server 2016 TP5, Windows 10 or newer # You have a pre-created "Golden Image" VHDX that has been syspreped with the /generalize /oobe and /mode:VM switches. # You have created an answer file using the Windows SIM (System Image Manager) which is part of the Windows 10 ADK # https://developer.microsoft.com/en-us/windows/hardware/windows-assessment-deployment-kit#winADK # You have placed your answer file inside of the VHDX by mounting it in Windows 10 and then placing the unattend.xml file in the path # C:\Windows\Panther # First we set some targeting variables for the Host environment # The $vSwitch Variable is the Name of the vSwitch you'd like the new VMs to use for network connectivity # $VHDXPath is the root path of your VHDX storage $vSwitch = "Deployment Test" $VHDXPath = "C:\users\Public\Documents\Hyper-V\Virtual hard disks" # Global IP Settings Below # NOTE: The cmdlets below reference the subnet mask bit for configuration of the IP Settings. Currently this script is configured for a standard # Class C /24 subnet. $SubMaskBit = "24" # Domain Controller Settings Below # NOTE: As of the time of this writing, with 2016 TP5, the Domain Mode and Forest Mode are called "WinThreshold" I Expect this to change # Once GA for 2016 is released. It will likely follow the previous naming scheme and be something like "win2016" $DCVMName = "TEST-DC01" $DCIP = "10.0.50.15" $DomainMode = "WinThreshold" ; $ForestMode = "WinThreshold" ; $DomainName = "TestDomain.lcl" ; $DSRMPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $NewAdminUserName = "TestAdmin" $NewAdminUserPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force # File Server Settings Below $FSVMName = "TEST-FS01" $FSIP = "10.0.50.30" $SharePath = "C:\ShareTest" $FolderName = "Public" $ShareName = "Public" # Then we setup some credentials to be called throughout the script # NOTE: These are the credentials used within the guest VM. # NOTE: If you only have a single image, you can likely get by with a single set of local credentials, instead of one for each # workload. $DCLocalUser = "$DCVMName\Administrator" $DCLocalPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $DCLocalCredential = New-Object -TypeName System . Management . Automation . PSCredential -ArgumentList $DCLocalUser , $DCLocalPWord # Below Credentials are used by the File Server VM for first login to be able to add the machine to the new Domain. $FSLocalUser = "$FSVMName\administrator" $FSLocalPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $FSLocalCredential = New-Object -TypeName System . Management . Automation . PSCredential -ArgumentList $FSLocalUser , $FSLocalPWord # The below credentials are used by operations below once the domain controller virtual machine and the new domain are in place. These credentials should match the credentials # used during the provisioning of the new domain. $DomainUser = "$DomainName\administrator" $DomainPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $DomainCredential = New-Object -TypeName System . Management . Automation . PSCredential -ArgumentList $DomainUser , $DomainPWord ############################# Command Execution Starts Below ################################### # The below section Provisions and Configures the Domain Controller with the Variables defined above # First we make a copy of the sysprepped "Gold Image" VHDX file. Also, note that a Unattend.XML file has been placed within the image as well. Write-Verbose "Copying Master VHDX and Deploying new VM with name [$DCVMName]" -Verbose Copy-Item "$VHDXPath\MASTER.vhdx" "$VHDXPath\$DCVMNAME.vhdx" Write-Verbose "VHDX Copied, Building VM...." -Verbose New-VM -Name $DCVMName -MemoryStartupBytes 1GB -VHDPath "$VHDXPath\$DCVMName.vhdx" -Generation 2 -SwitchName $vSwitch Write-Verbose "VM Creation Completed. Starting VM [$DCVMName]" -Verbose Start-VM -Name $DCVMName # After the inital provisioning, we wait until PowerShell Direct is functional and working within the guest VM before moving on. # Big thanks to Ben Armstrong for the below useful Wait code Write-Verbose “ Waiting for PowerShell Direct to start on VM [ $DCVMName ] ” -Verbose while ( ( icm -VMName $DCVMName -Credential $DCLocalCredential { “ Test ” } -ea SilentlyContinue ) -ne “ Test ” ) { Sleep -Seconds 1 } Write-Verbose "PowerShell Direct responding on VM [$DCVMName]. Moving On...." -Verbose # Next we configure the networking for the new DC VM. # NOTE: that the host variables are passed through by making use of the param command along with the -ArgumentList Paramater at the end of # the ScriptBlock. # NOTE: The InterfaceAlias value may be different for your gold image, so adjust accordingly. # NOTE: InterfaceAlias can be found by making use of the Get-NetIPAddress Cmdlet Invoke-Command -VMName $DCVMName -Credential $DCLocalCredential -ScriptBlock { param ( $DCVMName , $DCIP , $SubMaskBit , $DFGW ) New-NetIPAddress -IPAddress "$DCIP" -InterfaceAlias "Ethernet 2" -PrefixLength "$SubMaskBit" | Out-Null $DCEffectiveIP = Get-NetIPAddress -InterfaceAlias "Ethernet 2" | Select-Object IPAddress Write-Verbose "Assigned IPv4 and IPv6 IPs for VM [$DCVMName] are as follows" -Verbose Write-Host $DCEffectiveIP | Format-List Write-Verbose "Updating Hostname for VM [$DCVMName]" -Verbose Rename-Computer -NewName "$DCVMName" } -ArgumentList $DCVMName , $DCIP , $SubMaskBit , $DFGW Write-Verbose "Rebooting VM [$DCVMName] for hostname change to take effect" -Verbose Stop-VM -Name $DCVMName Start-VM -Name $DCVMName Write-Verbose “ Waiting for PowerShell Direct to start on VM [ $DCVMName ] ” -Verbose while ( ( icm -VMName $DCVMName -Credential $DomainCredential { “ Test ” } -ea SilentlyContinue ) -ne “ Test ” ) { Sleep -Seconds 1 } Write-Verbose "PowerShell Direct responding on VM [$DCVMName]. Moving On...." -Verbose # Next we'll proceed by installing the Active Directory Role and then configuring the machine as a new DC in a new AD Forest Invoke-Command -VMName $DCVMName -Credential $DCLocalCredential -ScriptBlock { param ( $DCVMName , $DomainMode , $ForestMode , $DomainName , $DSRMPWord ) Write-Verbose "Installing Active Directory Services on VM [$DCVMName]" -Verbose Install-WindowsFeature -Name "AD-Domain-Services" -IncludeManagementTools Write-Verbose "Configuring New Domain with Name [$DomainName] on VM [$DCVMName]" -Verbose Install-ADDSForest -ForestMode $ForestMode -DomainMode $DomainMode -DomainName $DomainName -InstallDns -NoDNSonNetwork -SafeModeAdministratorPassword $DSRMPWord -Force -NoRebootOnCompletion } -ArgumentList $DCVMName , $DomainMode , $ForestMode , $DomainName , $DSRMPWord Write-Verbose "Rebooting VM [$DCVMName] to complete installation of new AD Forest" -Verbose Stop-VM -Name $DCVMName Start-VM -Name $DCVMName Write-Verbose “ Waiting for PowerShell Direct to start on VM [ $DCVMName ] ” -Verbose while ( ( icm -VMName $DCVMName -Credential $DomainCredential { “ Test ” } -ea SilentlyContinue ) -ne “ Test ” ) { Sleep -Seconds 1 } Write-Verbose "PowerShell Direct responding on VM [$DCVMName]. Moving On...." -Verbose Write-Verbose "DC Provisioning Complete!!!!" -Verbose # We're going to setup an AD Administrative user based on the variables above that will have access to the file share we create below # Note that we do this in a loop as it will take some time for AD to be ready inside of the new DC VM. As such this command will execute # Until it is successful, and as a result, we know that AD is ready for the rest of the script. Write-Verbose "Creating new Administrative User within Domain [$DomainName] That will have access to Share [$ShareName] on VM [$FSVMName]" -Verbose Invoke-Command -VMName $DCVMName -Credential $DomainCredential -ScriptBlock { param ( $NewAdminUserName , $NewAdminUserPWord ) Write-Verbose "Waiting for AD Web Services to be in a running state" -Verbose $ADWebSvc = Get-Service ADWS | Select-Object * while ( $ADWebSvc . Status -ne 'Running' ) { Start-Sleep -Seconds 1 } Do { Start-Sleep -Seconds 30 Write-Verbose "Waiting for AD to be Ready for User Creation" -Verbose New-ADUser -Name "$NewAdminUserName" -AccountPassword $NewAdminUserPWord Enable-ADAccount -Identity "$NewAdminUserName" $ADReadyCheck = Get-ADUser -Identity $NewAdminUserName } Until ( $ADReadyCheck . Enabled -eq "True" ) Add-ADGroupMember -Identity "Domain Admins" -Members "$NewAdminUserName" } -ArgumentList $NewAdminUserName , $NewAdminUserPWord Write-Verbose "User [$NewAdminUserName] Created." -Verbose # The below section is used to Provision a new file server VM, add it to the new domain, and configure a basic share. # First we make a copy of the sysprepped "Gold Image" VHDX file. Also, note that a Unattend.XML file has been placed within the image as well. Write-Verbose "Copying Master VHDX and Deploying new VM with name [$FSVMName]" -Verbose Copy-Item "$VHDXPath\MASTER.vhdx" "$VHDXPath\$FSVMNAME.vhdx" Write-Verbose "VHDX Copied, Building VM...." -Verbose New-VM -Name $FSVMName -MemoryStartupBytes 1GB -VHDPath "$VHDXPath\$FSVMName.vhdx" -Generation 2 -SwitchName $vSwitch Write-Verbose "VM Creation Completed. Starting VM [$FSVMName]" -Verbose Start-VM -Name $FSVMName # After the inital provisioning, we wait until the PowerShell Direct is functional and working within the guest VM before moving on. # Big thanks to Ben Armstrong for the below useful Wait code Write-Verbose “ Waiting for PowerShell Direct to start on VM [ $FSVMName ] ” -Verbose while ( ( icm -VMName $FSVMName -Credential $FSLocalCredential { “ Test ” } -ea SilentlyContinue ) -ne “ Test ” ) { Sleep -Seconds 1 } Write-Verbose "PowerShell Direct responding on VM [$FSVMName]. Moving On...." -Verbose # Next we configure the networking for the new FS VM. # NOTE: that the host variables are passed through by makinguse of the param command along with the -ArgumentList Paramater at the end of # the ScriptBlock. # NOTE: The InterfaceAlias value may be different for your gold image, so adjust accordingly. Invoke-Command -VMName $FSVMName -Credential $FSLocalCredential -ScriptBlock { param ( $FSVMName , $FSIP , $SubMaskBit , $DFGW , $DCVMName , $DCIP ) New-NetIPAddress -IPAddress "$FSIP" -InterfaceAlias "Ethernet 2" -PrefixLength "$SubMaskBit" | Out-Null $FSEffectiveIP = Get-NetIPAddress -InterfaceAlias "Ethernet 2" | Select-Object IPAddress Write-Verbose "Assigned IPv4 and IPv6 IPs for VM [$FSVMName] are as follows" -Verbose Write-Host $FSEffectiveIP | Format-List Write-Verbose "Setting DNS Source to [$DCVMName] with IP [$DCIP]" -Verbose Set -DnsClientServerAddress -InterfaceAlias "Ethernet 2" -ServerAddresses "$DCIP" Write-Verbose "Updating Hostname for VM [$FSVMName]" -Verbose Rename-Computer -NewName "$FSVMName" } -ArgumentList $FSVMName , $FSIP , $SubMaskBit , $DFGW , $DCVMName , $DCIP Write-Verbose "Rebooting VM [$FSVMName] for hostname change to take effect" -Verbose Stop-VM -Name $FSVMName Start-VM -Name $FSVMName Write-Verbose “ Waiting for PowerShell Direct to start on VM [ $FSVMName ] ” -Verbose while ( ( icm -VMName $FSVMName -Credential $FSLocalCredential { “ Test ” } -ea SilentlyContinue ) -ne “ Test ” ) { Sleep -Seconds 1 } Write-Verbose "PowerShell Direct responding on VM [$FSVMName]. Moving On...." -Verbose # The below Adds the File Server VM to the newly Created Domain. Write-Verbose "Adding VM [$FSVMName] to domain [$DomainName]" -Verbose Invoke-Command -VMName $FSVMName -Credential $FSLocalCredential -ScriptBlock { param ( $DomainName , $DomainCredential ) Add-Computer -DomainName $DomainName -Credential $DomainCredential } -ArgumentList $DomainName , $DomainCredential Write-Verbose "Initiating Reboot of VM [$FSVMName] to complete domain join to domain [$DomainName]" -Verbose Stop-VM -Name $FSVMName Start-VM -Name $FSVMName Write-Verbose “ Waiting for PowerShell Direct to start on VM [ $FSVMName ] ” -Verbose while ( ( icm -VMName $FSVMName -Credential $DomainCredential { “ Test ” } -ea SilentlyContinue ) -ne “ Test ” ) { Sleep -Seconds 1 } Write-Verbose "PowerShell Direct responding on VM [$FSVMName]. Moving On...." -Verbose # Now we install the File Server Role and Create the Share Write-Verbose "Installing File-Server Role on VM [$FSVMName]." -Verbose Invoke-Command -VMName $FSVMName -Credential $DomainCredential -ScriptBlock { param ( $SharePath , $FolderName , $ShareName , $DomainName , $NewAdminUserName ) Install-WindowsFeature -Name "FS-FileServer" -IncludeManagementTools Write-Verbose "Creating File Share [$ShareName] at path [$SharePath\$Foldername]." -Verbose New-Item -Path $SharePath -Name $FolderName -ItemType "Directory" ; New-SmbShare -Name "$ShareName" -Path "$SharePath\$FolderName" -FullAccess "$DomainName\$NewAdminUserName" } -ArgumentList $SharePath , $FolderName , $ShareName , $DomainName , $NewAdminUserName Write-Verbose "Environment Setup Complete. End of Script" -Verbose # END OF SCRIPT

Summary

There you have it! It is my hope that this will be of some use to you, whether that be helping you get a task done at your job, or helping you learn how to use PowerShell and automate.

If you have questions, feel free to use the comment form below, and I’m looking forward to seeing if this script was useful to you.

Enjoy!