avatarJonathan Ingram

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

9109

Abstract

hljs-comment"># Create a VM</span> }</pre></div><p id="aace">There is a lot going on in the loop. Let’s go through it a couple of sections at a time.</p><p id="a6a6">First, we want to create a folder for the virtual machine. We store the path to that folder in <b>VMDirectory</b></p><div id="9756"><pre><span class="hljs-comment"># Create a directory for the VM</span> <span class="hljs-variable">VMDirectory</span> = <span class="hljs-string">"<span class="hljs-subst">(LabRootFolder)</span>$(<span class="hljs-variable">LabName</span>)\(<span class="hljs-variable">VMName</span>)"</span></pre></div><p id="f6d0">Now we need to check if that folder already exists. Mistakes happen. So we want to make sure we are starting with an empty folder to avoid having issues. If the folder for that Virtual Machine name already exists, we don’t do anything and move on to the next VM name. If the folder doesn’t exist, then we continue to create the folder and begin provisioning the VM.</p><div id="dfda"><pre><span class="hljs-comment"># Check if directory exists</span> <span class="hljs-keyword">if</span> (Test-Path <span class="hljs-variable">VMDirectory</span>) { Write-Host <span class="hljs-string">"VM folder already exists! Skipping to avoid losing data."</span> } <span class="hljs-keyword">else</span> { <span class="hljs-comment">#Provision VM</span> }</pre></div><p id="dce8">Let’s go over the section of code inside the else statement. First, we create the folder for the VM.</p><div id="47b4"><pre><span class="hljs-comment"># Create a directory for the VM</span> New-Item <span class="hljs-variable">VMDirectory</span> -ItemType <span class="hljs-built_in">Directory</span> Write-Host <span class="hljs-string">"VM folder created successfully, provistion VM: <span class="hljs-subst">VMName</span>"</span></pre></div><p id="598b">Next, we set some variables we will use in other portions of the script. The VM needs to know where to store the virtual machine itself, where to store its virtual hard drive, and where to store its page file. We are going to store all those things in the folder we created for the VM, VMDirectory.</p><div id="75f3"><pre><span class="hljs-variable">MachinePath</span> = <span class="hljs-variable">VMDirectory</span> <span class="hljs-variable">DiskPath</span> = <span class="hljs-variable">VMDirectory</span> <span class="hljs-variable">SmartPagingFilePath</span> = <span class="hljs-variable">VMDirectory</span></pre></div><p id="b3e8">The next section is another if/else statement. I want to create a server named DC-001 and then two desktop clients called Desktop-1 and Desktop-2. The if/else statement is checking the name. If the current name it’s looping through is “DC-001” it will use the server template we created in the last article. If the name is anything else, it will use a desktop template. I created the desktop template the same way we created our server template. The only difference is the ISO file and OS I used.</p><p id="172f">Let’s go through the first part of the if statement. We are using a few different PowerShell commandlets.</p><p id="8165">Write-Host — “Write some text to the console.”</p><p id="3eb2">Copy-Item — “Copy an item from one location to another.</p><p id="6bb2">New-VM — “Provisions a new virtual machine in your Hyper-V environment.</p><p id="affe">Add-VMNetworkAdapter — “Add the machine to the network specified.”</p><p id="e066">Set-VMKeyProtector — “Configures a key protector for the virtual machine.”</p><p id="a9d2">Enable-VMTPM — “Enables a virtual TPM chip for the virtual machine.”</p><p id="b4d4">Set-VM — “Configures the virtual machine.”</p><p id="9ddd">Checkpoint-VM — “Creates a restore point for the virtual machine.”</p><p id="f74a">(optional) Start-VM — “Powers on the virtual machine.”</p><div id="9508"><pre><span class="hljs-keyword">write</span>-host <span class="hljs-string">"Preparing domain controller: (VMName)"</span> Copy-Item ServerTemplate -Destination <span class="hljs-string">"(DiskPath)$(VMName).vhdx"</span> New-VM -Name VMName -Path MachinePath -Switch InternalSwitch -Generation <span class="hljs-number">2</span> -VHDPath <span class="hljs-string">"(DiskPath)$(VMName).vhdx"</span> -MemoryStartupBytes <span class="hljs-number">4</span>GB | Out-Null Add-VMNetworkAdapter -VMName VMName -Switch ExternalSwitch Set-VMKeyProtector -VMName VMName -NewLocalKeyProtector | Out-Null
Enable-VMTPM -VMName VMName | Out-Null Set-VM -Name VMName -AutomaticCheckpointsEnabled false -ProcessorCount <span class="hljs-number">2</span> -SmartPagingFilePath SmartPagingFilePath | Out-Null
Checkpoint-VM -Name VMName -SnapshotName <span class="hljs-string">"OOBE"</span> | Out-Null <span class="hljs-comment">#write-host "Starting "VMName </span> <span class="hljs-comment">#Start-VM -Name vmname | Out-Null </span></pre></div><p id="487f">Let’s go over these lines in greater detail.</p><p id="3d09">First, we copy the hard drive from our template machine and store it in the folder we created for our virtual machine. We name the copied file the name of the VM and add the “.vhdx” extension to it.</p><div id="c4ed"><pre>Copy-Item <span class="hljs-variable">ServerTemplate</span> -Destination <span class="hljs-string">"<span class="hljs-subst">(DiskPath)</span>$(<span class="hljs-variable">VMName</span>).vhdx"</span></pre></div><p id="98c4">Next, we create the new VM and set several properties.</p><ul><li>Name — The name of the virtual machine.</li><li>Path — The folder we created for the virtual machine.</li><li>Switch — The Internal Switch we created in the last article.</li><li>Generation — We want to create a 2nd generation VM since we are using newer Operating Systems.</li><li>VHDPath — The path to the hard drive we copied from our template.</li><li>MemoryStartupBytes — The amount of Memory we want to assign to the machine.</li></ul><blockquote id="9054"><p>What is the other section at the end of the line? “ | Out-Null” The | character is used to pipe the results of one command and send it to another. In this case Out-Null.</p></blockquote><p id="69b4">Out-Null — “Hides the output instead of sending it down the pipeline or displaying it.”</p><div id="2799"><pre>New-VM -Name <span class="hljs-variable">VMName</span> -Path <span class="hljs-variable">MachinePath</span> -Switch <span class="hljs-variable">InternalSwitch</span> -Generation 2 -VHDPath <span class="hljs-string">"<span class="hljs-subst">(DiskPath)</span>$(<span class="hljs-variable">VMName</span>).vhdx"</span> -MemoryStartupBytes 4GB | Out-Null</pre></div><p id="49be">For my server, I want to add a second network connection so I can enable my private network to have access to the internet via my server. To do that we use the line below. We tell the <b>Add-VMNetworkAdapter</b> the name of the machine we want to add the network connection to and what switch we want it to connect to. In this case, ExternalSwitch.</p><div id="6e61"><pre>Add-VMNetworkAdapter -VMName <span class="hljs-variable">VMName</span> -Switch <span class="hljs-variable">ExternalSwitch</span></pre></div><p id="1bd4">Then we create a key protector for our VM and add a virtual TPM chip.</p><div id="4879"><pre><span class="hljs-keyword">Set</span><span class="hljs-operator">-</span>VMKeyProtector <span class="hljs-operator">-</span>VMName VMName <span class="hljs-operator">-</span>NewLocalKeyProtector <span class="hljs-operator">|</span> <span class="hljs-keyword">Out</span><span class="hljs-operator">-</span><span class="hljs-keyword">Null</span> Enable<span class="hljs-operator">-</span>VMTPM <span class="hljs-operator">-</span>VMName VMName <span class="hljs-operator">|</span> <span class="hljs-keyword">Out</span><span class="hljs-operator">-</span><span class="hljs-keyword">Null</span></pre></div><p id="2621">Next, we configure the new VM.</p><ul><li>Name — The name of the virtual machine.</li><li>AutomaticCheckpointsEnabled — We disable automatic restore point creations by setting this to false.</li><li>ProcessorCount — We give the machine two virtual CPUs also known as vCPUs.</li><li>SmartPagingFilePath — We set the location for the VM’s page file.</li></ul><div id="8c30"><pre><span class="hljs-keyword">Set</span><span class="hljs-operator">-</span>VM <span class="hljs-operator">-</span>Name VMName <span class="hljs-operator">-</span>AutomaticCheckpointsEnabled <span class="hljs-literal">false</span> <span class="hljs-operator">-</span>ProcessorCount <span class="hljs-number">2</span> <span class="hljs-operator">-</span>SmartPagingFilePath $SmartPagingFilePath <span class="hljs-operator">|</span> <span class="hljs-keyword">Out</span><span class="hljs-operator">-</span><span class="hljs-keyword">Null</span></pre></div><p id="f22d">Then we create a restore point before we interact with the VM. This way we can roll back all our changes if we want to start over within the VM.</p><p id="98b3">OOBE — “Out of Box Experience”</p><div id="1d99"><pre>Checkpoint<span class="hljs-operator">-</s

Options

pan>VM <span class="hljs-operator">-</span>Name VMName <span class="hljs-operator">-</span>SnapshotName "OOBE" <span class="hljs-operator">|</span> <span class="hljs-keyword">Out</span><span class="hljs-operator">-</span><span class="hljs-keyword">Null</span></pre></div><p id="434f">Optionally we can start the VM after it has been created and configured with the line below. I have that line commented out so I can start them manually when I’m ready.</p><div id="7f88"><pre><span class="hljs-keyword">Start</span><span class="hljs-operator">-</span>VM <span class="hljs-operator">-</span>Name vmname <span class="hljs-operator">|</span> <span class="hljs-keyword">Out</span><span class="hljs-operator">-</span><span class="hljs-keyword">Null</span></pre></div><p id="204a">The else part of the if/else statement does the same thing. The only difference is we copy a different template hard drive.</p><div id="198c"><pre>Copy-Item <span class="hljs-variable">DesktopTemplate</span> -Destination <span class="hljs-string">"<span class="hljs-subst">(DiskPath)</span>\(<span class="hljs-variable">$VMName</span>).vhdx"</span></pre></div><p id="7502">Finally, we show a table of all the VM’s we have with the line below.</p><div id="5f6c"><pre># <span class="hljs-keyword">Show</span> a list <span class="hljs-keyword">of</span> VMs <span class="hljs-keyword">Get</span><span class="hljs-operator">-</span>VM <span class="hljs-operator">|</span> Format<span class="hljs-operator">-</span><span class="hljs-keyword">Table</span></pre></div><p id="882a">Get-VM — List all the VM’s in our Hyper-V environment.</p><p id="ee56">Format-Table — Formats the output of another command into a table for easier reading.</p><p id="757e">This line is a good example of using the | character to tell PowerShell to take the results of Get-VM and send it as input to the Format-Table commandlet.</p><p id="9e67">Here is the script in its entirety. The most up-to-date version will be in my GitHub Repo linked at the bottom of this article.</p><div id="32a8"><pre><span class="hljs-comment"># =============================================================</span> <span class="hljs-comment"># Script for provisioning Virtual Machines in Hyper-V via</span> <span class="hljs-comment"># template VMs.</span> <span class="hljs-comment"># =============================================================</span>

<span class="hljs-comment"># =============================================================</span> <span class="hljs-comment"># Lab Information</span> <span class="hljs-comment"># =============================================================</span>

LabRootFolder = <span class="hljs-string">"H:\IT-Homelabs\Hyper-V"</span> LabName = <span class="hljs-string">"Work-Lab"</span> LabFolder = <span class="hljs-string">"(LabRootFolder)\($LabName)"</span>

<span class="hljs-comment"># =============================================================</span> <span class="hljs-comment"># Lab Network Information (Created ahead of time.)</span> <span class="hljs-comment"># =============================================================</span> ExternalSwitch = <span class="hljs-string">"Default Switch"</span> InternalSwitch = <span class="hljs-string">"Internal Switch"</span>

<span class="hljs-comment"># =============================================================</span> <span class="hljs-comment"># Template Information</span> <span class="hljs-comment"># =============================================================</span> DesktopTemplate = <span class="hljs-string">"H:\IT-Homelabs\Hyper-V\Templates\Windows-10-Client\Template - Windows 10.vhdx"</span> ServerTemplate = <span class="hljs-string">"H:\IT-Homelabs\Hyper-V\Templates\Windows-Server-2022\Template - Windows Server 2022\Virtual Hard Disks\Template - Windows Server 2022.vhdx"</span>

<span class="hljs-comment"># =============================================================</span> <span class="hljs-comment"># VM Information</span> <span class="hljs-comment"># =============================================================</span>

$VMNames = <span class="hljs-string">"DC-001"</span>, <span class="hljs-string">"Desktop-1"</span>, <span class="hljs-string">"Desktop-2"</span>

<span class="hljs-comment"># =============================================================</span> <span class="hljs-comment"># Provision VMs</span> <span class="hljs-comment"># =============================================================</span>

<span class="hljs-comment"># Check for lab folder</span> <span class="hljs-keyword">if</span> (Test-Path LabFolder) { Write-Host <span class="hljs-string">"Folder exists, adding VM's..."</span> } <span class="hljs-keyword">else</span> { <span class="hljs-comment">#Create directory if it does not exist</span> New-Item LabFolder -ItemType Directory Write-Host <span class="hljs-string">"Lab folder created successfully, creating VM's..."</span> }

<span class="hljs-comment"># Loop through VMNames and provision a VM for each</span> <span class="hljs-keyword">foreach</span> (VMName in VMNames) { <span class="hljs-comment"># Create a directory for the VM</span> VMDirectory = <span class="hljs-string">"(LabRootFolder)$(LabName)\($VMName)"</span>

<span class="hljs-comment"># Check if directory exists</span>
<span class="hljs-keyword">if</span> (Test-Path $VMDirectory) {
    Write-Host <span class="hljs-string">"VM folder already exists! Skipping to avoid losing data."</span>
}
<span class="hljs-keyword">else</span> {
    <span class="hljs-comment"># Create a directory for the VM</span>
    New-Item $VMDirectory -ItemType Directory
    Write-Host <span class="hljs-string">"VM folder created successfully, provistion VM: $VMName"</span>

    $MachinePath = $VMDirectory
    $DiskPath = $VMDirectory
    $SmartPagingFilePath = $VMDirectory
    
    <span class="hljs-comment"># If the machine is named DC-001, provistion a server with the server template $ServerTemplate</span>
    <span class="hljs-keyword">if</span> ($VMName -eq <span class="hljs-string">"DC-001"</span>) {
        <span class="hljs-keyword">write</span>-host <span class="hljs-string">"Preparing domain controller: $($VMName)"</span>     
        Copy-Item $ServerTemplate -Destination <span class="hljs-string">"$($DiskPath)\$($VMName).vhdx"</span>     
        New-VM -Name $VMName -Path $MachinePath -Switch $InternalSwitch -Generation <span class="hljs-number">2</span> -VHDPath <span class="hljs-string">"$($DiskPath)\$($VMName).vhdx"</span> -MemoryStartupBytes <span class="hljs-number">4</span>GB | Out-Null    
        Add-VMNetworkAdapter -VMName $VMName -Switch $ExternalSwitch 
        Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector | Out-Null    
        Enable-VMTPM -VMName $VMName | Out-Null    
        Set-VM -Name $VMName -AutomaticCheckpointsEnabled $false -ProcessorCount <span class="hljs-number">2</span> -SmartPagingFilePath $SmartPagingFilePath | Out-Null    
        Checkpoint-VM -Name $VMName -SnapshotName <span class="hljs-string">"OOBE"</span> | Out-Null  
        <span class="hljs-comment">#write-host "Starting  "$VMName   </span>
        <span class="hljs-comment">#Start-VM -Name $vmname | Out-Null    </span>
    }
    <span class="hljs-keyword">else</span> {
        <span class="hljs-comment"># Otherwise, provision a desktop with the desktop client template $DesktopTemplate</span>
        <span class="hljs-keyword">write</span>-host <span class="hljs-string">"Preparing "</span>$VMName     
        Copy-Item $DesktopTemplate -Destination <span class="hljs-string">"$($DiskPath)\$($VMName).vhdx"</span>    
        New-VM -Name $VMName -Path $MachinePath -Switch $InternalSwitch -Generation <span class="hljs-number">2</span> -VHDPath <span class="hljs-string">"$($DiskPath)\$($VMName).vhdx"</span> -MemoryStartupBytes <span class="hljs-number">4</span>GB | Out-Null   
        Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector | Out-Null    
        Enable-VMTPM -VMName $VMName | Out-Null    
        Set-VM -Name $VMName -AutomaticCheckpointsEnabled $false -ProcessorCount <span class="hljs-number">2</span> -SmartPagingFilePath $SmartPagingFilePath | Out-Null    
        Checkpoint-VM -Name $VMName -SnapshotName <span class="hljs-string">"OOBE"</span> | Out-Null    
        <span class="hljs-comment">#write-host "Starting  "$VMName </span>
        <span class="hljs-comment">#Start-VM -Name $vmname | Out-Null    </span>
    }
}  

}

<span class="hljs-comment"># Show a list of VMs</span> Get-VM | Format-Table</pre></div><p id="0bd3">GitHub Link: <a href="https://github.com/jsingram/IT-Homelabs/blob/main/PowerShell-Scripts/Hyper-V-Section/create-work-lab.ps1">IT-Homelabs/create-work-lab.ps1 · jsingram/IT-Homelabs (github.com)</a></p><p id="7aa7">There you have it! In the next article, I’ll describe how to set up an environment in Proxmox. Thank you for reading!</p></article></body>

Provisioning VMs In Hyper-V with PowerShell

In this article, we will learn how to provision VMs in your Hyper-V environment with PowerShell. If you haven’t set up your Hyper-V Lab yet, check out the article below.

Setting Up an IT Homelab Environment | Hyper-V | by Jonathan Ingram | IT Homelabs

Create a Virtual Machine Template in Hyper-V | by Jonathan Ingram | IT Homelabs

Every time you need a new VM in your lab, you could go through the motions of manually creating it. It’s good to do it a few times to practice and learn the UI. After that, though you can save huge amounts of time provisioning your VMs with a PowerShell script.

In my Hyper-V lab, I’m going to need one server that will be my Active Directory Domain Controller and two desktop clients that I will join to the domain.

Servers: 1

Server Name: DC-001

Desktops: 2

Desktop Names: Desktop-1, Desktop-2

If you haven’t worked with PowerShell before, don’t worry! I’ll go over each part in an easy-to-understand way. Later in the series, I’ll go over some other PowerShell tips every IT professional should have in their toolbox.

Let’s go over each section of the script. I’ll add a GitHub link at the bottom of this article so you can get the latest version as time goes on.

PowerShell Comments

Throughout the script, you will see lines that begin with the # character. In PowerShell, any line that begins with that character lets the computer know it is a comment and not actual code. Below is an example from my script.

# =================================================================
# Script for provisioning Virtual Machines in Hyper-V via template
# VMs.
# =================================================================

PowerShell Variables

The next part of the script shows an example of variables. Every variable in PowerShell begins with a $ sign. In the section below, I’m setting up some variables for my lab.

$LabRootFolder contains the main location of my lab. I’m keeping my lab on a hard drive labeled “H:” inside a folder called “IT-Homelabs”. Inside that folder I’m storing my Hyper-V environment in a folder called “Hyper-V”.

$LabName contains the name of the folder I want to store this lab in. I could have several Hyper-V labs, so I like to keep everything organized in their own folders.

$LabFolder has a lot going on. In this variable, I’m constructing a new path by adding the variables together. If I were to print out $LabFolder in the console, it would show me: H:\IT-Homelabs\Hyper-V\Work-Lab

To use a variable inside of a string, you must surround the variable with $().

$myString = "This is a string with a variable: $($myVariable)."
Here is what the section looks like in the script.
# =============================================================
# Lab Information
# =============================================================

$LabRootFolder = "H:\IT-Homelabs\Hyper-V"
$LabName = "Work-Lab"
$LabFolder = "$($LabRootFolder)\$($LabName)"

In the next section, I’m storing the name of my virtual network switches. Default Switch is created when you enable Hyper-V on your computer. I created another switch called Internal Switch that my lab computers will connect to.

# =============================================================
# Lab Network Information (Created ahead of time.)
# =============================================================
$ExternalSwitch = "Default Switch"
$InternalSwitch = "Internal Switch"

The next section contains variables that tell Hyper-V where my virtual machine templates are. The .vhdx extension notes we are looking at the virtual machine’s hard drive.

# =============================================================
# Template Information
# =============================================================
$DesktopTemplate = "H:\IT-Homelabs\Hyper-V\Templates\Windows-10-Client\Template - Windows 10.vhdx"
$ServerTemplate = "H:\IT-Homelabs\Hyper-V\Templates\Windows-Server-2022\Template - Windows Server 2022\Virtual Hard Disks\Template - Windows Server 2022.vhdx"

The next section has a variable type we haven’t seen yet. The previous examples were String variables. You know they are string variables because the value is surrounded by double quotes.

$example = "My String"

The next variable is called a String Array. You can think of it like a bucket that contains several Strings. In this case, I’m creating an array that contains my Virtual Machine names. Each string value is separated by a comma.

# =============================================================
# VM Information
# =============================================================

$VMNames = "DC-001", "Desktop-1", "Desktop-2"

PowerShell If/Else Statements

Now the real fun beings! In the next section, the script checks if our lab folder exists. If it doesn’t, it creates it for us. This is done with an if/else statement.

if(condition is true) {
    Do something.
} else {
    Do something else.
}

You’ll notice three special commands in this section of the script:

Write-Host — “Writes something to the console.”

Test-Path — “Test if a file or folder exists. If it does, it returns true. Otherwise, it returns false.

New-Item — “Creates something. In this case a folder/directory. This is noted by the -ItemType flag.

# Check for lab folder
if (Test-Path $LabFolder) {
    Write-Host "Folder exists, adding VM's..."
}
else {
    #Create directory if it does not exist
    New-Item $LabFolder -ItemType Directory
    Write-Host "Lab folder created successfully, creating VM's..."
}

PowerShell ForEach Loop

In the next section, we are going to use a ForEach loop to go through each computer name in our $VMNames variable and do something with it.

# Example
foreach ($VMName in $VMNames) {
  # Create a VM
}

There is a lot going on in the loop. Let’s go through it a couple of sections at a time.

First, we want to create a folder for the virtual machine. We store the path to that folder in $VMDirectory

# Create a directory for the VM
$VMDirectory = "$($LabRootFolder)\$($LabName)\$($VMName)"

Now we need to check if that folder already exists. Mistakes happen. So we want to make sure we are starting with an empty folder to avoid having issues. If the folder for that Virtual Machine name already exists, we don’t do anything and move on to the next VM name. If the folder doesn’t exist, then we continue to create the folder and begin provisioning the VM.

# Check if directory exists
if (Test-Path $VMDirectory) {
  Write-Host "VM folder already exists! Skipping to avoid losing data."
}
else {
  #Provision VM
}

Let’s go over the section of code inside the else statement. First, we create the folder for the VM.

# Create a directory for the VM
New-Item $VMDirectory -ItemType Directory
Write-Host "VM folder created successfully, provistion VM: $VMName"

Next, we set some variables we will use in other portions of the script. The VM needs to know where to store the virtual machine itself, where to store its virtual hard drive, and where to store its page file. We are going to store all those things in the folder we created for the VM, $VMDirectory.

$MachinePath = $VMDirectory
$DiskPath = $VMDirectory
$SmartPagingFilePath = $VMDirectory

The next section is another if/else statement. I want to create a server named DC-001 and then two desktop clients called Desktop-1 and Desktop-2. The if/else statement is checking the name. If the current name it’s looping through is “DC-001” it will use the server template we created in the last article. If the name is anything else, it will use a desktop template. I created the desktop template the same way we created our server template. The only difference is the ISO file and OS I used.

Let’s go through the first part of the if statement. We are using a few different PowerShell commandlets.

Write-Host — “Write some text to the console.”

Copy-Item — “Copy an item from one location to another.

New-VM — “Provisions a new virtual machine in your Hyper-V environment.

Add-VMNetworkAdapter — “Add the machine to the network specified.”

Set-VMKeyProtector — “Configures a key protector for the virtual machine.”

Enable-VMTPM — “Enables a virtual TPM chip for the virtual machine.”

Set-VM — “Configures the virtual machine.”

Checkpoint-VM — “Creates a restore point for the virtual machine.”

(optional) Start-VM — “Powers on the virtual machine.”

write-host "Preparing domain controller: $($VMName)"     
Copy-Item $ServerTemplate -Destination "$($DiskPath)\$($VMName).vhdx"     
New-VM -Name $VMName -Path $MachinePath -Switch $InternalSwitch -Generation 2 -VHDPath "$($DiskPath)\$($VMName).vhdx" -MemoryStartupBytes 4GB | Out-Null    
Add-VMNetworkAdapter -VMName $VMName -Switch $ExternalSwitch 
Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector | Out-Null    
Enable-VMTPM -VMName $VMName | Out-Null    
Set-VM -Name $VMName -AutomaticCheckpointsEnabled $false -ProcessorCount 2 -SmartPagingFilePath $SmartPagingFilePath | Out-Null    
Checkpoint-VM -Name $VMName -SnapshotName "OOBE" | Out-Null  
#write-host "Starting  "$VMName   
#Start-VM -Name $vmname | Out-Null    

Let’s go over these lines in greater detail.

First, we copy the hard drive from our template machine and store it in the folder we created for our virtual machine. We name the copied file the name of the VM and add the “.vhdx” extension to it.

Copy-Item $ServerTemplate -Destination "$($DiskPath)\$($VMName).vhdx"

Next, we create the new VM and set several properties.

  • Name — The name of the virtual machine.
  • Path — The folder we created for the virtual machine.
  • Switch — The Internal Switch we created in the last article.
  • Generation — We want to create a 2nd generation VM since we are using newer Operating Systems.
  • VHDPath — The path to the hard drive we copied from our template.
  • MemoryStartupBytes — The amount of Memory we want to assign to the machine.

What is the other section at the end of the line? “ | Out-Null” The | character is used to pipe the results of one command and send it to another. In this case Out-Null.

Out-Null — “Hides the output instead of sending it down the pipeline or displaying it.”

New-VM -Name $VMName -Path $MachinePath -Switch $InternalSwitch -Generation 2 -VHDPath "$($DiskPath)\$($VMName).vhdx" -MemoryStartupBytes 4GB | Out-Null

For my server, I want to add a second network connection so I can enable my private network to have access to the internet via my server. To do that we use the line below. We tell the Add-VMNetworkAdapter the name of the machine we want to add the network connection to and what switch we want it to connect to. In this case, $ExternalSwitch.

Add-VMNetworkAdapter -VMName $VMName -Switch $ExternalSwitch

Then we create a key protector for our VM and add a virtual TPM chip.

Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector | Out-Null    
Enable-VMTPM -VMName $VMName | Out-Null

Next, we configure the new VM.

  • Name — The name of the virtual machine.
  • AutomaticCheckpointsEnabled — We disable automatic restore point creations by setting this to false.
  • ProcessorCount — We give the machine two virtual CPUs also known as vCPUs.
  • SmartPagingFilePath — We set the location for the VM’s page file.
Set-VM -Name $VMName -AutomaticCheckpointsEnabled $false -ProcessorCount 2 -SmartPagingFilePath $SmartPagingFilePath | Out-Null

Then we create a restore point before we interact with the VM. This way we can roll back all our changes if we want to start over within the VM.

OOBE — “Out of Box Experience”

Checkpoint-VM -Name $VMName -SnapshotName "OOBE" | Out-Null

Optionally we can start the VM after it has been created and configured with the line below. I have that line commented out so I can start them manually when I’m ready.

Start-VM -Name $vmname | Out-Null

The else part of the if/else statement does the same thing. The only difference is we copy a different template hard drive.

Copy-Item $DesktopTemplate -Destination "$($DiskPath)\$($VMName).vhdx"

Finally, we show a table of all the VM’s we have with the line below.

# Show a list of VMs
Get-VM | Format-Table

Get-VM — List all the VM’s in our Hyper-V environment.

Format-Table — Formats the output of another command into a table for easier reading.

This line is a good example of using the | character to tell PowerShell to take the results of Get-VM and send it as input to the Format-Table commandlet.

Here is the script in its entirety. The most up-to-date version will be in my GitHub Repo linked at the bottom of this article.

# =============================================================
# Script for provisioning Virtual Machines in Hyper-V via
# template VMs.
# =============================================================

# =============================================================
# Lab Information
# =============================================================

$LabRootFolder = "H:\IT-Homelabs\Hyper-V"
$LabName = "Work-Lab"
$LabFolder = "$($LabRootFolder)\$($LabName)"

# =============================================================
# Lab Network Information (Created ahead of time.)
# =============================================================
$ExternalSwitch = "Default Switch"
$InternalSwitch = "Internal Switch"

# =============================================================
# Template Information
# =============================================================
$DesktopTemplate = "H:\IT-Homelabs\Hyper-V\Templates\Windows-10-Client\Template - Windows 10.vhdx"
$ServerTemplate = "H:\IT-Homelabs\Hyper-V\Templates\Windows-Server-2022\Template - Windows Server 2022\Virtual Hard Disks\Template - Windows Server 2022.vhdx"

# =============================================================
# VM Information
# =============================================================

$VMNames = "DC-001", "Desktop-1", "Desktop-2"

# =============================================================
# Provision VMs
# =============================================================

# Check for lab folder
if (Test-Path $LabFolder) {
    Write-Host "Folder exists, adding VM's..."
}
else {
    #Create directory if it does not exist
    New-Item $LabFolder -ItemType Directory
    Write-Host "Lab folder created successfully, creating VM's..."
}
 
# Loop through $VMNames and provision a VM for each
foreach ($VMName in $VMNames) {
    # Create a directory for the VM
    $VMDirectory = "$($LabRootFolder)\$($LabName)\$($VMName)"

    # Check if directory exists
    if (Test-Path $VMDirectory) {
        Write-Host "VM folder already exists! Skipping to avoid losing data."
    }
    else {
        # Create a directory for the VM
        New-Item $VMDirectory -ItemType Directory
        Write-Host "VM folder created successfully, provistion VM: $VMName"

        $MachinePath = $VMDirectory
        $DiskPath = $VMDirectory
        $SmartPagingFilePath = $VMDirectory
        
        # If the machine is named DC-001, provistion a server with the server template $ServerTemplate
        if ($VMName -eq "DC-001") {
            write-host "Preparing domain controller: $($VMName)"     
            Copy-Item $ServerTemplate -Destination "$($DiskPath)\$($VMName).vhdx"     
            New-VM -Name $VMName -Path $MachinePath -Switch $InternalSwitch -Generation 2 -VHDPath "$($DiskPath)\$($VMName).vhdx" -MemoryStartupBytes 4GB | Out-Null    
            Add-VMNetworkAdapter -VMName $VMName -Switch $ExternalSwitch 
            Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector | Out-Null    
            Enable-VMTPM -VMName $VMName | Out-Null    
            Set-VM -Name $VMName -AutomaticCheckpointsEnabled $false -ProcessorCount 2 -SmartPagingFilePath $SmartPagingFilePath | Out-Null    
            Checkpoint-VM -Name $VMName -SnapshotName "OOBE" | Out-Null  
            #write-host "Starting  "$VMName   
            #Start-VM -Name $vmname | Out-Null    
        }
        else {
            # Otherwise, provision a desktop with the desktop client template $DesktopTemplate
            write-host "Preparing "$VMName     
            Copy-Item $DesktopTemplate -Destination "$($DiskPath)\$($VMName).vhdx"    
            New-VM -Name $VMName -Path $MachinePath -Switch $InternalSwitch -Generation 2 -VHDPath "$($DiskPath)\$($VMName).vhdx" -MemoryStartupBytes 4GB | Out-Null   
            Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector | Out-Null    
            Enable-VMTPM -VMName $VMName | Out-Null    
            Set-VM -Name $VMName -AutomaticCheckpointsEnabled $false -ProcessorCount 2 -SmartPagingFilePath $SmartPagingFilePath | Out-Null    
            Checkpoint-VM -Name $VMName -SnapshotName "OOBE" | Out-Null    
            #write-host "Starting  "$VMName 
            #Start-VM -Name $vmname | Out-Null    
        }
    }  

}  

# Show a list of VMs
Get-VM | Format-Table

GitHub Link: IT-Homelabs/create-work-lab.ps1 · jsingram/IT-Homelabs (github.com)

There you have it! In the next article, I’ll describe how to set up an environment in Proxmox. Thank you for reading!

Homelab
Technology
Hyper V
Virtualization
Powershell
Recommended from ReadMedium