Last week I had the distinct pleasure of getting to speak at the VMUG UserCon events in both Seattle and South Florida. My goal in both of these speaking slots was not to do the typical vendor sales pitch, but provide some good solid information to the community that could be taken back to attendees’ locations and be put to good use. The topic I chose is a popular one in the industry right now. I wanted to talk about automation, with one PowerCLI Script I put together in mind.

The Story

In a previous job, I was in charge of managing a fairly large vSphere implementation that housed the hosted-private-cloud practice of a managed services provider and hoster. This environment, while it ran good, always challenged me to find ways to further optimize deployment processes. Each on-boarded customer had a couple of things in common. They all wanted AD, and they all needed file hosting. I wanted to incorporate those needs into the VM deployment process to ultimately save on time. Sadly, time was in short supply to come up with a script at the time. Now that I work for Altaro, and one of my primary duties is coming up with community focused content, I finally had the chance to come back to this issue, and tackle it. My goal was to solve this issue, and share the solution with the community.

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 pre-existing templates and customization specs. Must customize each virtual machine according to the specified customization spec. 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

I’ve always enjoyed giving back to the IT community that has helped me so much throughout my career, and I really wanted to that with this script. 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.

While the script was very well received at the VMUG events, I was asked by some of the more tenured PowerShell folks as to why I didn’t use any functions or more advanced formatting. The main reason was number 2 above. I’ve found that IT Pros have a very sequential line of thinking when it comes to completing tasks. I wanted to script to be done in a very…. step 1…. then step 2…. then step 3, type of fashion. This way it’s easier for a PowerShell beginner to see how the script works, and take the bits and pieces that they need for their own scripts. Functions and some of the more advanced techniques can be more difficult to follow if you’re not used to PowerShell.

The Secret Sauce

The first half of the script simply consists of setting variables to be used later in the script’s execution. Once the actual task completing commands start being called, there are a number of basic cmdlets being called to complete most of the functions. If I had to pick some of the most important cmdlets for this script they would be the 3 below

Wait-Tools: While nothing really special, this is an immensely helpful cmdlet. I call this cmdlet several times throughout the script. What Wait-Tools does, is it targets a VM, and waits until VMware Tools within that Guest VM is in a responsive state. This is especially useful for reboots, where you don’t want to script to continue until VMware Tools within the guest is working. In most cases all other services in the VM will be up and running once tools is functioning, however there were a couple of times in the script where I had to follow this up with a Start-Sleep cmdlet to give the OS just a little extra time to come online before continuing.

Get-VIEvent: Like the above, Get-VIEvent is not overly special by itself. However, When paired with other commands and some filtering options, it can become quite powerful. I used this cmdlet in a loop fashion in the script to scan the vCenter logs to look for various VM customization events. This logic allows the script to wait until the guest customization of the first VM is completed, prior to trying to make any changes to the OS contained within the VM.

Invoke-VMScript: If you can only take away one useful command from this post, choose this one. This cmdlet, in a word, is awesome. This single cmdlet allows your script to reach into the guest operating system, and execute a block of code. This is how all of the OS level changes are done throughout the script. It takes care of things like the installation of AD, and the configuration of the new file share on the file server. It completes this task by pushing the codeblock to the VM via VMware Tools. So the only thing that is needed for this is for tools to be in a running state inside of the target VM.

See it in Action

I’ve annotated the below video to show the script in action. Be advised, that the script actually takes 20 to 30 minutes to run in the below state. This video has had quite a bit of the wait periods removed so you can see the script function in it’s entirety.

The Script

So here is the part you’ve been waiting for. Below is the script. Take it! Use it. Learn from it. This is simply one of my humble contributions back to the IT community.

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 Primarily a Deployment Script. It will first deploy a template from your vSphere environment, apply a customization specification, and then install and configure Active # Directory Domain Services on one VM, and the File Services on another. Additionally the file server will be a member of the newly deployed VM, a new administrative user account will # be created and a file share will be provisioned on the new file server. # Script was designed for PowerShell/PowerCLI Beginners who have not yet begun to work with functions and more advanced PowerShell features. # The below script first lists all user definable varibles and then executes the script's actions in an easy to follow sequential order. # Assumptions # - You Have vCenter inside your environment # - You have pre-configured templates and customization specifications inside of vCenter # - Your PowerShell execution policy allows the execution of this script # ------vCenter Targeting Varibles and Connection Commands Below------ # This section insures that the PowerCLI PowerShell Modules are currently active. The pipe to Out-Null can be removed if you desire additional # Console output. Get-Module -ListAvailable VMware* | Import-Module | Out-Null # ------vSphere Targeting Variables tracked below------ $vCenterInstance = "VCENTER FQDN HERE" $vCenterUser = "VCENTER USER ACCOUNT HERE" $vCenterPass = "VCENTER PASSWORD HERE" # This section logs on to the defined vCenter instance above Connect-VIServer $vCenterInstance -User $vCenterUser -Password $vCenterPass -WarningAction SilentlyContinue ######################################################-User-Definable Variables-In-This-Section-########################################################################################## # ------Virtual Machine Targeting Variables tracked below------ # The Below Variables define the names of the virtual machines upon deployment, the target cluster, and the source template and customization specification inside of vCenter to use during # the deployment of the VMs. $DomainControllerVMName = "DESIRED DC NAME HERE" $FSVMName = "DESIRED FS NAME HERE" $TargetCluster = Get-Cluster -Name "TARGET CLUSTER IN VCENTER" $SourceVMTemplate = Get-Template -Name "SOURCE TEMPLATE IN VCENTER" $SourceCustomSpec = Get-OSCustomizationSpec -Name "SOURCE CUSTOMIZATION SPEC IN VCENTER" # ------This section contains the commands for defining the IP and networking settings for the new virtual machines------ # NOTE: The below IPs and Interface Names need to be updated for your environment. # Domain Controller VM IPs Below # NOTE: Insert IP info in IP SubnetMask Gateway Order # NOTE: For the purposes of this script we do not define static DNS settings for this single DC VM as it will point to itself for DNS after provisioning of the new domain. # You could add an additional netsh line below to assign static DNS settings in the event you need to do so. See the File Server Section below for further details. $DCNetworkSettings = 'netsh interface ip set address "Ethernet0" static x.x.x.x 255.255.255.0 x.x.x.x' # FS VM IPs Below # NOTE: Insert IP info in IP SubnetMask Gateway Order $FSNetworkSettings = 'netsh interface ip set address "Ethernet0" static x.x.x.x 255.255.255.0 x.x.x.x' # NOTE: DNS Server IP below should be the same IP as given to the domain controller in the $DCNetworkSettings Variable $FSDNSSettings = 'netsh interface ip set dnsservers name="Ethernet0" static x.x.x.x primary' # ------This Section Sets the Credentials to be used to connect to Guest VMs that are NOT part of a Domain------ # NOTE - Make sure you input the local credentials for your domain controller virtual machines below. This is used for logins prior to them being promoted to DCs. # This should be the same local credentials as defined within the template that you are using for the domain controller VM. $DCLocalUser = "$DomainControllerVMName\DC LOCAL USER NAME HERE" $DCLocalPWord = ConvertTo-SecureString -String "DC LOCAL PASSWORD HERE*" -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. # This should be the same local credentials as defined within the template that you are using for the file server VM. $FSLocalUser = "$FSVMName\FS LOCAL USER NAME HERE" $FSLocalPWord = ConvertTo-SecureString -String "FS LOCAL PASSWORD HERE" -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 machines and the new domain are in place. These credentials should match the credentials # used during the provisioning of the new domain. $DomainUser = "TESTDOMAIN\administrator" $DomainPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $DomainCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $DomainUser, $DomainPWord # ------This Section Contains the Scripts to be executed against new VMs Regardless of Role # This Scriptblock is used to add new VMs to the newly created domain by first defining the domain creds on the machine and then using Add-Computer $JoinNewDomain = '$DomainUser = "TESTDOMAIN\Administrator"; $DomainPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force; $DomainCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $DomainUser, $DomainPWord; Add-Computer -DomainName TestDomain.lcl -Credential $DomainCredential; Start-Sleep -Seconds 20; Shutdown /r /t 0' # ------This Section Contains the Scripts to be executed against New Domain Controller VMs------ # This Command will Install the AD Role on the target virtual machine. $InstallADRole = 'Install-WindowsFeature -Name "AD-Domain-Services" -Restart' # This Scriptblock will define settings for a new AD Forest and then provision it with said settings. # NOTE - Make sure to define the DSRM Password below in the line below that defines the $DSRMPWord Variable!!!! $ConfigureNewDomain = 'Write-Verbose -Message "Configuring Active Directory" -Verbose; $DomainMode = "Win2012R2"; $ForestMode = "Win2012R2"; $DomainName = "TestDomain.lcl"; $DSRMPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force; Install-ADDSForest -ForestMode $ForestMode -DomainMode $DomainMode -DomainName $DomainName -InstallDns -SafeModeAdministratorPassword $DSRMPWord -Force' # This scriptblock creates a new administrative user account inside of the new domain # NOTE - Be Sure to set the password using the $AdminUserPWord Below! $NewAdminUser = '$AdminUserPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force; New-ADUser -Name "TestAdmin" -AccountPassword $AdminUserPWord; Add-ADGroupMember -Identity "Domain Admins" -Members "TestAdmin"; Enable-ADAccount -Identity "TestAdmin"' # ------This Section Contains the Scripts to be executed against file server VMs------ $InstallFSRole = 'Install-WindowsFeature -Name "FS-FileServer"' # The below block of code first creates a new folder and then creates a new SMB Share with rights given to the defined user or group. $NewFileShare = '$ShareLocation = "C:\ShareTest"; $FolderName = "Public"; New-Item -Path $ShareLocation -Name $FolderName -ItemType "Directory"; New-SmbShare -Name "Public" -Path "$ShareLocation\$FolderName" -FullAccess "TestDomain\TestAdmin"' ######################################################################################################################################################################################### # Script Execution Occurs from this point down # ------This Section Deploys the new VM(s) using a pre-built template and then applies a customization specification to it. It then waits for Provisioning To Finish------ Write-Verbose -Message "Deploying Virtual Machine with Name: [$DomainControllerVMName] using Template: [$SourceVMTemplate] and Customization Specification: [$SourceCustomSpec] on Cluster: [$TargetCluster] and waiting for completion" -Verbose New-VM -Name $DomainControllerVMName -Template $SourceVMTemplate -ResourcePool $TargetCluster -OSCustomizationSpec $SourceCustomSpec Write-Verbose -Message "Virtual Machine $DomainControllerVMName Deployed. Powering On" -Verbose Start-VM -VM $DomainControllerVMName Write-Verbose -Message "Deploying Virtual Machine with Name: [$FSVMName] using Template: [$SourceVMTemplate] and Customization Specification: [$SourceCustomSpec] on Cluster: [$TargetCluster] and waiting for completion" -Verbose New-VM -Name $FSVMName -Template $SourceVMTemplate -ResourcePool $TargetCluster -OSCustomizationSpec $SourceCustomSpec Write-Verbose -Message "Virtual Machine $FSVMName Deployed. Powering On" -Verbose Start-VM -VM $FSVMName # ------This Section Targets and Executes the Scripts on the New Domain Controller Guest VM------ # We first verify that the guest customization has finished on on the new DC VM by using the below loops to look for the relevant events within vCenter. Write-Verbose -Message "Verifying that Customization for VM $DomainControllerVMName has started ..." -Verbose while($True) { $DCvmEvents = Get-VIEvent -Entity $DomainControllerVMName $DCstartedEvent = $DCvmEvents | Where { $_.GetType().Name -eq "CustomizationStartedEvent" } if ($DCstartedEvent) { break } else { Start-Sleep -Seconds 5 } } Write-Verbose -Message "Customization of VM $DomainControllerVMName has started. Checking for Completed Status......." -Verbose while($True) { $DCvmEvents = Get-VIEvent -Entity $DomainControllerVMName $DCSucceededEvent = $DCvmEvents | Where { $_.GetType().Name -eq "CustomizationSucceeded" } $DCFailureEvent = $DCvmEvents | Where { $_.GetType().Name -eq "CustomizationFailed" } if ($DCFailureEvent) { Write-Warning -Message "Customization of VM $DomainControllerVMName failed" -Verbose return $False } if ($DCSucceededEvent) { break } Start-Sleep -Seconds 5 } Write-Verbose -Message "Customization of VM $DomainControllerVMName Completed Successfully!" -Verbose # NOTE - The below Sleep command is to help prevent situations where the post customization reboot is delayed slightly causing # the Wait-Tools command to think everything is fine and carrying on with the script before all services are ready. Value can be adjusted for your environment. Start-Sleep -Seconds 30 Write-Verbose -Message "Waiting for VM $DomainControllerVMName to complete post-customization reboot." -Verbose Wait-Tools -VM $DomainControllerVMName -TimeoutSeconds 300 # NOTE - Another short sleep here to make sure that other services have time to come up after VMware Tools are ready. Start-Sleep -Seconds 30 # After Customization Verification is done we change the IP of the VM to the value defined near the top of the script Write-Verbose -Message "Getting ready to change IP Settings on VM $DomainControllerVMName." -Verbose Invoke-VMScript -ScriptText $DCNetworkSettings -VM $DomainControllerVMName -GuestCredential $DCLocalCredential # NOTE - The Below Sleep Command is due to it taking a few seconds for VMware Tools to read the IP Change so that we can return the below output. # This is strctly informational and can be commented out if needed, but it's helpful when you want to verify that the settings defined above have been # applied successfully within the VM. We use the Get-VM command to return the reported IP information from Tools at the Hypervisor Layer. Start-Sleep 30 $DCEffectiveAddress = (Get-VM $DomainControllerVMName).guest.ipaddress[0] Write-Verbose -Message "Assigned IP for VM [$DomainControllerVMName] is [$DCEffectiveAddress]" -Verbose # Then we Actually install the AD Role and configure the new domain Write-Verbose -Message "Getting Ready to Install Active Directory Services on $DomainControllerVMName" -Verbose Invoke-VMScript -ScriptText $InstallADRole -VM $DomainControllerVMName -GuestCredential $DCLocalCredential Write-Verbose -Message "Configuring New AD Forest on $DomainControllerVMName" -Verbose Invoke-VMScript -ScriptText $ConfigureNewDomain -VM $DomainControllerVMName -GuestCredential $DCLocalCredential # Script Block for configuration of AD automatically reboots the machine after provisioning Write-Verbose -Message "Rebooting $DomainControllerVMName to Complete Forest Provisioning" -Verbose # Below sleep command is in place as the reboot needed from the above command doesn't always happen before the wait-tools command is run Start-Sleep -Seconds 60 Wait-Tools -VM $DomainControllerVMName -TimeoutSeconds 300 Write-Verbose -Message "Installation of Domain Services and Forest Provisioning on $DomainControllerVMName Complete" -Verbose Write-Verbose -Message "Adding new administative user account to domain" -Verbose Invoke-VMScript -ScriptText $NewAdminUser -VM $DomainControllerVMName -GuestCredential $DomainCredential # ------This Section Targets and Executes the Scripts on the New FS VM. # Just like the DC VM, we have to first modify the IP Settings of the VM Write-Verbose -Message "Getting ready to change IP Settings on VM $FSVMName." -Verbose Invoke-VMScript -ScriptText $FSNetworkSettings -VM $FSVMName -GuestCredential $FSLocalCredential Invoke-VMScript -ScriptText $FSDNSSettings -VM $FSVMName -GuestCredential $FSLocalCredential # NOTE - The Below Sleep Command is due to it taking a few seconds for VMware Tools to read the IP Change so that we can return the below output. # This is strctly informational and can be commented out if needed, but it's helpful when you want to verify that the settings defined above have been # applied successfully within the VM. We use the Get-VM command to return the reported IP information from Tools at the Hypervisor Layer. Start-Sleep 30 $FSEffectiveAddress = (Get-VM $FSVMName).guest.ipaddress[0] Write-Verbose -Message "Assigned IP for VM [$FSVMName] is [$FSEffectiveAddress]" -Verbose # The Below Cmdlets actually add the VM to the newly deployed domain. Invoke-VMScript -ScriptText $JoinNewDomain -VM $FSVMName -GuestCredential $FSLocalCredential # Below sleep command is in place as the reboot needed from the above command doesn't always happen before the wait-tools command is run Start-Sleep -Seconds 60 Wait-Tools -VM $FSVMName -TimeoutSeconds 300 Write-Verbose -Message "VM $FSVMName Added to Domain and Successfully Rebooted." -Verbose Write-Verbose -Message "Installing File Server Role and Creating File Share on $FSVMName." -Verbose # The below commands actually execute the script blocks defined above to install the file server role and then configure the new file share. Invoke-VMScript -ScriptText $InstallFSRole -VM $FSVMName -GuestCredential $DomainCredential Invoke-VMScript -ScriptText $NewFileShare -VM $FSVMName -GuestCredential $DomainCredential Write-Verbose -Message "Environment Setup Complete" -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 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 # 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 Primarily a Deployment Script. It will first deploy a template from your vSphere environment, apply a customization specification, and then install and configure Active # Directory Domain Services on one VM, and the File Services on another. Additionally the file server will be a member of the newly deployed VM, a new administrative user account will # be created and a file share will be provisioned on the new file server. # Script was designed for PowerShell/PowerCLI Beginners who have not yet begun to work with functions and more advanced PowerShell features. # The below script first lists all user definable varibles and then executes the script's actions in an easy to follow sequential order. # Assumptions # - You Have vCenter inside your environment # - You have pre-configured templates and customization specifications inside of vCenter # - Your PowerShell execution policy allows the execution of this script # ------vCenter Targeting Varibles and Connection Commands Below------ # This section insures that the PowerCLI PowerShell Modules are currently active. The pipe to Out-Null can be removed if you desire additional # Console output. Get-Module -ListAvailable VMware * | Import-Module | Out-Null # ------vSphere Targeting Variables tracked below------ $vCenterInstance = "VCENTER FQDN HERE" $vCenterUser = "VCENTER USER ACCOUNT HERE" $vCenterPass = "VCENTER PASSWORD HERE" # This section logs on to the defined vCenter instance above Connect-VIServer $vCenterInstance -User $vCenterUser -Password $vCenterPass -WarningAction SilentlyContinue ######################################################-User-Definable Variables-In-This-Section-########################################################################################## # ------Virtual Machine Targeting Variables tracked below------ # The Below Variables define the names of the virtual machines upon deployment, the target cluster, and the source template and customization specification inside of vCenter to use during # the deployment of the VMs. $DomainControllerVMName = "DESIRED DC NAME HERE" $FSVMName = "DESIRED FS NAME HERE" $TargetCluster = Get-Cluster -Name "TARGET CLUSTER IN VCENTER" $SourceVMTemplate = Get-Template -Name "SOURCE TEMPLATE IN VCENTER" $SourceCustomSpec = Get-OSCustomizationSpec -Name "SOURCE CUSTOMIZATION SPEC IN VCENTER" # ------This section contains the commands for defining the IP and networking settings for the new virtual machines------ # NOTE: The below IPs and Interface Names need to be updated for your environment. # Domain Controller VM IPs Below # NOTE: Insert IP info in IP SubnetMask Gateway Order # NOTE: For the purposes of this script we do not define static DNS settings for this single DC VM as it will point to itself for DNS after provisioning of the new domain. # You could add an additional netsh line below to assign static DNS settings in the event you need to do so. See the File Server Section below for further details. $DCNetworkSettings = 'netsh interface ip set address "Ethernet0" static x.x.x.x 255.255.255.0 x.x.x.x' # FS VM IPs Below # NOTE: Insert IP info in IP SubnetMask Gateway Order $FSNetworkSettings = 'netsh interface ip set address "Ethernet0" static x.x.x.x 255.255.255.0 x.x.x.x' # NOTE: DNS Server IP below should be the same IP as given to the domain controller in the $DCNetworkSettings Variable $FSDNSSettings = 'netsh interface ip set dnsservers name="Ethernet0" static x.x.x.x primary' # ------This Section Sets the Credentials to be used to connect to Guest VMs that are NOT part of a Domain------ # NOTE - Make sure you input the local credentials for your domain controller virtual machines below. This is used for logins prior to them being promoted to DCs. # This should be the same local credentials as defined within the template that you are using for the domain controller VM. $DCLocalUser = "$DomainControllerVMName\DC LOCAL USER NAME HERE" $DCLocalPWord = ConvertTo-SecureString -String "DC LOCAL PASSWORD HERE*" -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. # This should be the same local credentials as defined within the template that you are using for the file server VM. $FSLocalUser = "$FSVMName\FS LOCAL USER NAME HERE" $FSLocalPWord = ConvertTo-SecureString -String "FS LOCAL PASSWORD HERE" -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 machines and the new domain are in place. These credentials should match the credentials # used during the provisioning of the new domain. $DomainUser = "TESTDOMAIN\administrator" $DomainPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force $DomainCredential = New-Object -TypeName System . Management . Automation . PSCredential -ArgumentList $DomainUser , $DomainPWord # ------This Section Contains the Scripts to be executed against new VMs Regardless of Role # This Scriptblock is used to add new VMs to the newly created domain by first defining the domain creds on the machine and then using Add-Computer $JoinNewDomain = '$DomainUser = "TESTDOMAIN\Administrator"; $DomainPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force; $DomainCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $DomainUser, $DomainPWord; Add-Computer -DomainName TestDomain.lcl -Credential $DomainCredential; Start-Sleep -Seconds 20; Shutdown /r /t 0' # ------This Section Contains the Scripts to be executed against New Domain Controller VMs------ # This Command will Install the AD Role on the target virtual machine. $InstallADRole = 'Install-WindowsFeature -Name "AD-Domain-Services" -Restart' # This Scriptblock will define settings for a new AD Forest and then provision it with said settings. # NOTE - Make sure to define the DSRM Password below in the line below that defines the $DSRMPWord Variable!!!! $ConfigureNewDomain = 'Write-Verbose -Message "Configuring Active Directory" -Verbose; $DomainMode = "Win2012R2"; $ForestMode = "Win2012R2"; $DomainName = "TestDomain.lcl"; $DSRMPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force; Install-ADDSForest -ForestMode $ForestMode -DomainMode $DomainMode -DomainName $DomainName -InstallDns -SafeModeAdministratorPassword $DSRMPWord -Force' # This scriptblock creates a new administrative user account inside of the new domain # NOTE - Be Sure to set the password using the $AdminUserPWord Below! $NewAdminUser = '$AdminUserPWord = ConvertTo-SecureString -String "Password01" -AsPlainText -Force; New-ADUser -Name "TestAdmin" -AccountPassword $AdminUserPWord; Add-ADGroupMember -Identity "Domain Admins" -Members "TestAdmin"; Enable-ADAccount -Identity "TestAdmin"' # ------This Section Contains the Scripts to be executed against file server VMs------ $InstallFSRole = 'Install-WindowsFeature -Name "FS-FileServer"' # The below block of code first creates a new folder and then creates a new SMB Share with rights given to the defined user or group. $NewFileShare = '$ShareLocation = "C:\ShareTest"; $FolderName = "Public"; New-Item -Path $ShareLocation -Name $FolderName -ItemType "Directory"; New-SmbShare -Name "Public" -Path "$ShareLocation\$FolderName" -FullAccess "TestDomain\TestAdmin"' ######################################################################################################################################################################################### # Script Execution Occurs from this point down # ------This Section Deploys the new VM(s) using a pre-built template and then applies a customization specification to it. It then waits for Provisioning To Finish------ Write-Verbose -Message "Deploying Virtual Machine with Name: [$DomainControllerVMName] using Template: [$SourceVMTemplate] and Customization Specification: [$SourceCustomSpec] on Cluster: [$TargetCluster] and waiting for completion" -Verbose New-VM -Name $DomainControllerVMName -Template $SourceVMTemplate -ResourcePool $TargetCluster -OSCustomizationSpec $SourceCustomSpec Write-Verbose -Message "Virtual Machine $DomainControllerVMName Deployed. Powering On" -Verbose Start-VM -VM $DomainControllerVMName Write-Verbose -Message "Deploying Virtual Machine with Name: [$FSVMName] using Template: [$SourceVMTemplate] and Customization Specification: [$SourceCustomSpec] on Cluster: [$TargetCluster] and waiting for completion" -Verbose New-VM -Name $FSVMName -Template $SourceVMTemplate -ResourcePool $TargetCluster -OSCustomizationSpec $SourceCustomSpec Write-Verbose -Message "Virtual Machine $FSVMName Deployed. Powering On" -Verbose Start-VM -VM $FSVMName # ------This Section Targets and Executes the Scripts on the New Domain Controller Guest VM------ # We first verify that the guest customization has finished on on the new DC VM by using the below loops to look for the relevant events within vCenter. Write-Verbose -Message "Verifying that Customization for VM $DomainControllerVMName has started ..." -Verbose while ( $True ) { $DCvmEvents = Get-VIEvent -Entity $DomainControllerVMName $DCstartedEvent = $DCvmEvents | Where { $_ . GetType ( ) . Name -eq "CustomizationStartedEvent" } if ( $DCstartedEvent ) { break } else { Start-Sleep -Seconds 5 } } Write-Verbose -Message "Customization of VM $DomainControllerVMName has started. Checking for Completed Status......." -Verbose while ( $True ) { $DCvmEvents = Get-VIEvent -Entity $DomainControllerVMName $DCSucceededEvent = $DCvmEvents | Where { $_ . GetType ( ) . Name -eq "CustomizationSucceeded" } $DCFailureEvent = $DCvmEvents | Where { $_ . GetType ( ) . Name -eq "CustomizationFailed" } if ( $DCFailureEvent ) { Write-Warning -Message "Customization of VM $DomainControllerVMName failed" -Verbose return $False } if ( $DCSucceededEvent ) { break } Start-Sleep -Seconds 5 } Write-Verbose -Message "Customization of VM $DomainControllerVMName Completed Successfully!" -Verbose # NOTE - The below Sleep command is to help prevent situations where the post customization reboot is delayed slightly causing # the Wait-Tools command to think everything is fine and carrying on with the script before all services are ready. Value can be adjusted for your environment. Start-Sleep -Seconds 30 Write-Verbose -Message "Waiting for VM $DomainControllerVMName to complete post-customization reboot." -Verbose Wait-Tools -VM $DomainControllerVMName -TimeoutSeconds 300 # NOTE - Another short sleep here to make sure that other services have time to come up after VMware Tools are ready. Start-Sleep -Seconds 30 # After Customization Verification is done we change the IP of the VM to the value defined near the top of the script Write-Verbose -Message "Getting ready to change IP Settings on VM $DomainControllerVMName." -Verbose Invoke-VMScript -ScriptText $DCNetworkSettings -VM $DomainControllerVMName -GuestCredential $DCLocalCredential # NOTE - The Below Sleep Command is due to it taking a few seconds for VMware Tools to read the IP Change so that we can return the below output. # This is strctly informational and can be commented out if needed, but it's helpful when you want to verify that the settings defined above have been # applied successfully within the VM. We use the Get-VM command to return the reported IP information from Tools at the Hypervisor Layer. Start-Sleep 30 $DCEffectiveAddress = ( Get-VM $DomainControllerVMName ) . guest . ipaddress [ 0 ] Write-Verbose -Message "Assigned IP for VM [$DomainControllerVMName] is [$DCEffectiveAddress]" -Verbose # Then we Actually install the AD Role and configure the new domain Write-Verbose -Message "Getting Ready to Install Active Directory Services on $DomainControllerVMName" -Verbose Invoke-VMScript -ScriptText $InstallADRole -VM $DomainControllerVMName -GuestCredential $DCLocalCredential Write-Verbose -Message "Configuring New AD Forest on $DomainControllerVMName" -Verbose Invoke-VMScript -ScriptText $ConfigureNewDomain -VM $DomainControllerVMName -GuestCredential $DCLocalCredential # Script Block for configuration of AD automatically reboots the machine after provisioning Write-Verbose -Message "Rebooting $DomainControllerVMName to Complete Forest Provisioning" -Verbose # Below sleep command is in place as the reboot needed from the above command doesn't always happen before the wait-tools command is run Start-Sleep -Seconds 60 Wait-Tools -VM $DomainControllerVMName -TimeoutSeconds 300 Write-Verbose -Message "Installation of Domain Services and Forest Provisioning on $DomainControllerVMName Complete" -Verbose Write-Verbose -Message "Adding new administative user account to domain" -Verbose Invoke-VMScript -ScriptText $NewAdminUser -VM $DomainControllerVMName -GuestCredential $DomainCredential # ------This Section Targets and Executes the Scripts on the New FS VM. # Just like the DC VM, we have to first modify the IP Settings of the VM Write-Verbose -Message "Getting ready to change IP Settings on VM $FSVMName." -Verbose Invoke-VMScript -ScriptText $FSNetworkSettings -VM $FSVMName -GuestCredential $FSLocalCredential Invoke-VMScript -ScriptText $FSDNSSettings -VM $FSVMName -GuestCredential $FSLocalCredential # NOTE - The Below Sleep Command is due to it taking a few seconds for VMware Tools to read the IP Change so that we can return the below output. # This is strctly informational and can be commented out if needed, but it's helpful when you want to verify that the settings defined above have been # applied successfully within the VM. We use the Get-VM command to return the reported IP information from Tools at the Hypervisor Layer. Start-Sleep 30 $FSEffectiveAddress = ( Get-VM $FSVMName ) . guest . ipaddress [ 0 ] Write-Verbose -Message "Assigned IP for VM [$FSVMName] is [$FSEffectiveAddress]" -Verbose # The Below Cmdlets actually add the VM to the newly deployed domain. Invoke-VMScript -ScriptText $JoinNewDomain -VM $FSVMName -GuestCredential $FSLocalCredential # Below sleep command is in place as the reboot needed from the above command doesn't always happen before the wait-tools command is run Start-Sleep -Seconds 60 Wait-Tools -VM $FSVMName -TimeoutSeconds 300 Write-Verbose -Message "VM $FSVMName Added to Domain and Successfully Rebooted." -Verbose Write-Verbose -Message "Installing File Server Role and Creating File Share on $FSVMName." -Verbose # The below commands actually execute the script blocks defined above to install the file server role and then configure the new file share. Invoke-VMScript -ScriptText $InstallFSRole -VM $FSVMName -GuestCredential $DomainCredential Invoke-VMScript -ScriptText $NewFileShare -VM $FSVMName -GuestCredential $DomainCredential Write-Verbose -Message "Environment Setup Complete" -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!