Skip to content

2025-03 Update #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Intune.HV.Tools/Intune.HV.Tools.psd1
Binary file not shown.
18 changes: 8 additions & 10 deletions Intune.HV.Tools/Intune.HV.Tools.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ $cfg = Get-Content "$env:USERPROFILE\.hvtoolscfgpath" -ErrorAction SilentlyConti
$script:tick = [char]0x221a

if ($cfg) {
$script:hvConfig = if (Get-Content -Path $cfg -raw -ErrorAction SilentlyContinue) {
Get-Content -Path $cfg -raw -ErrorAction SilentlyContinue | ConvertFrom-Json
}
else {
$script:hvConfig = if (Get-Content -Path $cfg -Raw -ErrorAction SilentlyContinue) {
Get-Content -Path $cfg -Raw -ErrorAction SilentlyContinue | ConvertFrom-Json
} else {
$script:hvConfig = $null
}
}
Expand All @@ -17,8 +16,7 @@ if ($cfg) {
foreach ($import in @($Public + $Private)) {
try {
. $import.FullName
}
catch {
} catch {
Write-Error -Message "Failed to import function $($import.FullName): $_"
}
}
Expand Down Expand Up @@ -64,7 +62,7 @@ $vLan = {
}
Register-ArgumentCompleter -CommandName Add-NetworkToConfig -ParameterName VSwitchName -ScriptBlock $vLan

$win10Builds = {
$winBuilds = {
param (
$commandName,
$parameterName,
Expand All @@ -82,6 +80,6 @@ $win10Builds = {
)
}
}
Register-ArgumentCompleter -CommandName Add-TenantToConfig -ParameterName ImageName -ScriptBlock $win10Builds
Register-ArgumentCompleter -CommandName New-ClientVM -ParameterName OSBuild -ScriptBlock $win10Builds
#endregion
Register-ArgumentCompleter -CommandName Add-TenantToConfig -ParameterName ImageName -ScriptBlock $winBuilds
Register-ArgumentCompleter -CommandName New-ClientVM -ParameterName OSBuild -ScriptBlock $winBuilds
#endregion
60 changes: 29 additions & 31 deletions Intune.HV.Tools/Private/Get-AutopilotPolicy.ps1
Original file line number Diff line number Diff line change
@@ -1,67 +1,65 @@
#requires -Modules @{ ModuleName="WindowsAutoPilotIntune"; ModuleVersion="4.3" }
#requires -Modules @{ ModuleName="Microsoft.Graph.Intune"; ModuleVersion="6.1907.1.0"}
#requires -Modules @{ ModuleName="WindowsAutoPilotIntune"; ModuleVersion="5.7" }
#requires -Modules @{ ModuleName="Microsoft.Graph.Identity.DirectoryManagement"; ModuleVersion="2.26.1"}
function Get-AutopilotPolicy {
[cmdletbinding()]
[CmdletBinding()]
param (
[parameter(Mandatory = $true)]
[System.IO.FileInfo]$FileDestination
)
try {
if (!(Test-Path "$FileDestination\AutopilotConfigurationFile.json" -ErrorAction SilentlyContinue)) {
if (-not(Test-Path "$FileDestination\AutopilotConfigurationFile.json" -ErrorAction SilentlyContinue)) {
Write-Host 'Autopilot Configuration file not found.' -ForegroundColor Yellow
Write-Host 'Grabbing Autopilot config...' -ForegroundColor Cyan
$modules = @(
"WindowsAutoPilotIntune",
"Microsoft.Graph.Intune"
'WindowsAutoPilotIntune',
'Microsoft.Graph.Identity.DirectoryManagement'
)
if ($PSVersionTable.PSVersion.Major -eq 7) {
$modules | ForEach-Object {
Import-Module $_ -UseWindowsPowerShell -ErrorAction SilentlyContinue 3>$null
}
}
else {
} else {
$modules | ForEach-Object {
Import-Module $_
}
}
#region Connect to Intune
Connect-MSGraph | Out-Null
#endregion Connect to Intune
#region Connect to Microsoft Graph
Connect-MgGraph -Scopes 'DeviceManagementServiceConfig.Read.All,Organization.Read.All' -NoWelcome -ClientTimeout 300
#endregion Connect to Microsoft Graph
#region Get policies
$apPolicies = Get-AutopilotProfile
if (!($apPolicies)) {
Write-Warning "No Autopilot policies found.."
}
else {
if ($apPolicies.count -gt 1) {
Write-Host "Multiple Autopilot policies found - select the correct one.." -ForegroundColor Cyan
$apPol = $apPolicies | Select-Object displayName | Out-GridView -passthru
}
else {
Write-Host "Policy found - saving to $FileDestination.." -ForegroundColor Cyan
if (-not($apPolicies)) {
Write-Warning 'No Autopilot policies found.'
} else {
if ($apPolicies.id.count -gt 1) {
Write-Host 'Multiple Autopilot policies found - Please select the correct one.' -ForegroundColor Yellow
$selectedAp = $apPolicies | Select-Object displayName | Out-GridView -PassThru
$apPol = $apPolicies | Where-Object { $_.displayName -eq $selectedAp.displayName }
} else {
Write-Host "Policy found - saving to $FileDestination..." -ForegroundColor Cyan
$apPol = $apPolicies
}
$apPol | ConvertTo-AutopilotConfigurationJSON | Out-File "$FileDestination\AutopilotConfigurationFile.json" -Encoding ascii -Force
Write-Host "Autopilot profile selected: $($apPol.displayName)" -ForegroundColor Green
Disconnect-MgGraph | Out-Null
}
#endregion Get policies
}
else {
} else {
Write-Host "Autopilot Configuration file found locally: $FileDestination\AutopilotConfigurationFile.json" -ForegroundColor Green
}
}
catch {
} catch {
$errorMsg = $_
}
finally {
} finally {
if ($PSVersionTable.PSVersion.Major -eq 7) {
$modules = @(
"WindowsAutoPilotIntune",
"Microsoft.Graph.Intune"
'WindowsAutoPilotIntune',
'Microsoft.Graph.Identity.DirectoryManagement'
) | ForEach-Object {
Remove-Module $_ -ErrorAction SilentlyContinue 3>$null
}
}
if ($errrorMsg) {
if ($errorMsg) {
Write-Warning $errorMsg
}
}
}
}
19 changes: 9 additions & 10 deletions Intune.HV.Tools/Private/Get-ImageIndexFromWim.ps1
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
function Get-ImageIndexFromWim {
[cmdletbinding()]
[CmdletBinding()]
param (
[parameter(Mandatory = $true)]
$wimPath
)
try {
Write-Verbose "Getting windows images from $wimPath"
Write-Host "Getting windows images from $wimPath..." -ForegroundColor Cyan
$images = Get-WindowsImage -ImagePath $wimPath
Write-Host "Select an Image from the below available options:" -ForegroundColor Cyan
$images | Select-Object ImageIndex, ImageName | Format-Table | Out-String | ForEach-Object { Write-Host $_ }
$rh = Read-Host "Select Image Index..($($images[0].ImageIndex)..$($images[-1].ImageIndex))"
Write-Host 'Select an Image from the below available options:' -ForegroundColor Cyan
$images | Select-Object ImageIndex, ImageName | Format-Table | Out-String | ForEach-Object { Write-Host $_ -ForegroundColor Cyan }
$rh = Read-Host "Select Image Index ($($images[0].ImageIndex) - $($images[-1].ImageIndex))"
while ($rh -notin $images.ImageIndex) {
$rh = Read-Host "Select Image Index..($($images[0].ImageIndex)..$($images[-1].ImageIndex))"
$rh = Read-Host "Select Image Index ($($images[0].ImageIndex) - $($images[-1].ImageIndex))"
}
Write-Host "Image $rh / $(($images | Where-Object {$_.ImageIndex -eq $rh}).ImageName) selected..`n" -ForegroundColor Green
Write-Host "Image $rh / $(($images | Where-Object {$_.ImageIndex -eq $rh}).ImageName) selected `n" -ForegroundColor Green
return ($images | Where-Object { $_.ImageIndex -eq $rh }).ImageIndex
}
catch {
} catch {
Write-Warning $_.Exception.Message
}
}
}
167 changes: 142 additions & 25 deletions Intune.HV.Tools/Private/New-ClientDevice.ps1
Original file line number Diff line number Diff line change
@@ -1,54 +1,171 @@
function New-ClientDevice {
[cmdletBinding(SupportsShouldProcess)]
param (
[parameter(Position = 1, Mandatory = $true)]
[parameter(Position = 0, Mandatory = $true)]
[string]$VMName,

[parameter(Position = 2, Mandatory = $true)]
[parameter(Position = 1, Mandatory = $true)]
[string]$ClientPath,

[parameter(Position = 3, Mandatory = $true)]
[parameter(Position = 2, Mandatory = $true)]
[string]$RefVHDX,

[parameter(Position = 4, Mandatory = $true)]
[parameter(Position = 3, Mandatory = $true)]
[string]$VSwitchName,

[parameter(Position = 5, Mandatory = $false)]
[parameter(Position = 4, Mandatory = $false)]
[string]$VLanId,

[parameter(Position = 6, Mandatory = $true)]
[parameter(Position = 5, Mandatory = $true)]
[string]$CPUCount,

[parameter(Position = 7, Mandatory = $true)]
[string]$VMMMemory,
[parameter(Position = 6, Mandatory = $true)]
[string]$VMMemory,

[parameter(Position = 7, Mandatory = $false)]
[switch]$DynamicMemory,

[parameter(Position = 8, Mandatory = $false)]
[switch]$skipAutoPilot
)
Copy-Item -path $RefVHDX -Destination "$ClientPath\$VMName.vhdx"
if (!($skipAutoPilot)) {

# Display all parameters for debugging
Write-Verbose 'New-ClientDevice Parameters:'
Write-Verbose "VMName: $VMName"
Write-Verbose "ClientPath: $ClientPath"
Write-Verbose "RefVHDX: $RefVHDX"
Write-Verbose "VSwitchName: $VSwitchName"
Write-Verbose "VLanId: $VLanId"
Write-Verbose "CPUCount: $CPUCount"
Write-Verbose "VMMemory: $VMMemory"
Write-Verbose "DynamicMemory: $DynamicMemory"
Write-Verbose "skipAutoPilot: $skipAutoPilot"

# Check if Hyper-V is running properly
Write-Host 'Checking Hyper-V service status...' -ForegroundColor Cyan
$hvService = Get-Service -Name 'vmms' -ErrorAction SilentlyContinue
if (-not $hvService -or $hvService.Status -ne 'Running') {
Write-Error 'Hyper-V Virtual Machine Management Service is not running. Please ensure Hyper-V is properly installed and the service is running.'
return
}

# Check Hyper-V configuration paths
try {
Write-Host 'Checking Hyper-V configuration paths...' -ForegroundColor Cyan
#$hyperVConfig = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization' -ErrorAction SilentlyContinue

# Get default VM and VHD paths
$defaultVMPath = (Get-VMHost).VirtualMachinePath
$defaultVHDPath = (Get-VMHost).VirtualHardDiskPath

Write-Verbose "Default VM Path: $defaultVMPath" -ForegroundColor Green
Write-Verbose "Default VHD Path: $defaultVHDPath" -ForegroundColor Green

# Check if these paths exist
if (-not (Test-Path -Path $defaultVMPath -PathType Container)) {
Write-Warning "Default VM path does not exist: $defaultVMPath"
Write-Host 'Creating default VM path...' -ForegroundColor Cyan
New-Item -Path $defaultVMPath -ItemType Directory -Force | Out-Null
}

if (-not (Test-Path -Path $defaultVHDPath -PathType Container)) {
Write-Warning "Default VHD path does not exist: $defaultVHDPath"
Write-Host 'Creating default VHD path...' -ForegroundColor Cyan
New-Item -Path $defaultVHDPath -ItemType Directory -Force | Out-Null
}
} catch {
Write-Warning "Could not verify Hyper-V configuration paths: $_"
}

$VHDXPath = "$ClientPath\$VMName.vhdx"
# Copy VHDX file
Write-Host "Copying VHDX from $RefVHDX to $VHDXPath..." -ForegroundColor Cyan
Copy-Item -Path $RefVHDX -Destination $VHDXPath -Force
# Publish AutoPilot config if needed
if (-not $skipAutoPilot) {
Write-Host 'Publishing AutoPilot configuration...' -ForegroundColor Cyan
Publish-AutoPilotConfig -vmName $VMName -clientPath $ClientPath
}

New-VM -Name $VMName -MemoryStartupBytes $VMMMemory -VHDPath "$ClientPath\$VMName.vhdx" -Generation 2 | Out-Null
Enable-VMIntegrationService -vmName $VMName -Name "Guest Service Interface"
Set-VM -name $VMName -CheckpointType Disabled
Set-VMProcessor -VMName $VMName -Count $CPUCount
Set-VMFirmware -VMName $VMName -EnableSecureBoot On
Get-VMNetworkAdapter -vmName $VMName | Connect-VMNetworkAdapter -SwitchName $VSwitchName | Set-VMNetworkAdapter -Name $VSwitchName -DeviceNaming On
# Verify virtual switch exists
if (-not (Get-VMSwitch -Name $VSwitchName -ErrorAction SilentlyContinue)) {
Write-Error "Virtual switch '$VSwitchName' does not exist. Please create it first."
return
}
# Check if VM already exists and remove it
$existingVM = Get-VM -Name $VMName -ErrorAction SilentlyContinue
if ($existingVM) {
Write-Host "VM '$VMName' already exists. Removing it." -ForegroundColor Yellow
Remove-VM -Name $VMName -Force
}

Write-Verbose "Using memory value: $VMMemory bytes"
# Create the VM with full path to VHDX
Write-Host "Creating VM: $VMName with VHDX: $VHDXPath..." -ForegroundColor Cyan

# Try creating VM with explicit configuration path
$vmConfigPath = Join-Path -Path $defaultVMPath -ChildPath $VMName
if (-not (Test-Path -Path $vmConfigPath -PathType Container)) {
New-Item -Path $vmConfigPath -ItemType Directory -Force | Out-Null
}

# Create VM with explicit paths
$vm = New-VM -Name $VMName -MemoryStartupBytes $VMMemory -VHDPath $VHDXPath -Generation 2 -Path $vmConfigPath -ErrorAction Stop

# Configure VM settings
Write-Host 'Configuring VM settings...' -ForegroundColor Cyan
Get-VMIntegrationService -VMName $VMName | Where-Object Name -Match 'Interface' | Enable-VMIntegrationService
Set-VM -Name $VMName -CheckpointType Disabled -AutomaticStartAction Nothing -AutomaticStopAction ShutDown -ErrorAction Stop
Set-VMProcessor -VMName $VMName -Count $CPUCount -ErrorAction Stop

# Enable Dynamic Memory if specified
If ($DynamicMemory) {
Write-Host 'Enabling dynamic memory...' -ForegroundColor Cyan
# Set dynamic memory parameters according to documented ranges:
# StartupBytes: Must be multiple of 2MB between 32MB and 65536MB (64GB)
# MinimumBytes: Must be between 32MB and StartupBytes
# MaximumBytes: Must be between StartupBytes and 1TB
# BufferPercent: Must be between 5-2000
Set-VM -Name $VMName -DynamicMemory -ErrorAction Stop
Set-VMMemory -VMName $VMName `
-DynamicMemoryEnabled $true `
-StartupBytes $VMMemory `
-MinimumBytes 512MB `
-MaximumBytes $VMMemory `
-Buffer 20 `
-ErrorAction Stop
}

# Configure firmware and networking
Set-VMFirmware -VMName $VMName -EnableSecureBoot On -ErrorAction Stop
Get-VMNetworkAdapter -VMName $VMName -ErrorAction Stop | Connect-VMNetworkAdapter -SwitchName $VSwitchName -ErrorAction Stop | Set-VMNetworkAdapter -Name $VSwitchName -DeviceNaming On -ErrorAction Stop

# Configure VLAN if specified
if ($VLanId) {
Set-VMNetworkAdapterVlan -Access -VMName $VMName -VlanId $VLanId
Write-Host "Setting VLAN ID: $VLanId..." -ForegroundColor Cyan
Set-VMNetworkAdapterVlan -VMName $VMName -Access -VlanId $VLanId -ErrorAction Stop
}

# Configure TPM
$owner = Get-HgsGuardian UntrustedGuardian -ErrorAction SilentlyContinue
If (!$owner) {
If (-not $owner) {
# Creating new UntrustedGuardian since it did not exist
$owner = New-HgsGuardian -Name UntrustedGuardian -GenerateCertificates
}
$kp = New-HgsKeyProtector -Owner $owner -AllowUntrustedRoot
Set-VMKeyProtector -VMName $VMName -KeyProtector $kp.RawData
Enable-VMTPM -VMName $VMName
Start-VM -Name $VMName
#Set VM Info with Serial number
$vmSerial = (Get-CimInstance -Namespace root\virtualization\v2 -class Msvm_VirtualSystemSettingData | Where-Object { ($_.VirtualSystemType -eq "Microsoft:Hyper-V:System:Realized") -and ($_.elementname -eq $VMName )}).BIOSSerialNumber
Get-VM -Name $VMname | Set-VM -Notes "Serial# $vmSerial"
}
Set-VMKeyProtector -VMName $VMName -KeyProtector $kp.RawData -ErrorAction Stop
Enable-VMTPM -VMName $VMName -ErrorAction Stop

# Start the VM
Write-Host "Starting VM: $VMName..." -ForegroundColor Cyan
Start-VM -Name $VMName -ErrorAction Stop

# Set VM Info with Serial number
$vmSerial = (Get-CimInstance -Namespace root\virtualization\v2 -class Msvm_VirtualSystemSettingData |
Where-Object { ($_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized') -and ($_.ElementName -eq $VMName) }).BIOSSerialNumber
if ($vmSerial) {
Get-VM -Name $VMName | Set-VM -Notes "Serial Number: $vmSerial"
}

Write-Host "VM '$VMName' created and started successfully!" -ForegroundColor Green
}
Loading