Public/OSDCloud/OSDCloudVM.ps1

function Get-OSDCloudVMDefaults {
    <#
    .SYNOPSIS
    Gets the OSDCloudVM Module defaults from $Global:OSDModuleResource.NewOSDCloudVM
 
    .DESCRIPTION
    Gets the OSDCloudVM Module defaults from $Global:OSDModuleResource.NewOSDCloudVM
 
    .EXAMPLE
    Get-OSDCloudVMDefaults
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-09 - Standardized comment-based help metadata and links.
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
    #>


    [CmdletBinding()]
    param ()

    # OSDCloudVM Module Defaults
    $Results = [ordered]@{
        CheckpointVM    = [System.Boolean]$Global:OSDModuleResource.NewOSDCloudVM.CheckpointVM
        Generation      = [System.Int16]$Global:OSDModuleResource.NewOSDCloudVM.Generation
        MemoryStartupGB = [System.Int64]$Global:OSDModuleResource.NewOSDCloudVM.MemoryStartupGB
        NamePrefix      = [System.String]$Global:OSDModuleResource.NewOSDCloudVM.NamePrefix
        ProcessorCount  = [System.Int64]$Global:OSDModuleResource.NewOSDCloudVM.ProcessorCount
        StartVM         = [System.Boolean]$Global:OSDModuleResource.NewOSDCloudVM.StartVM
        SwitchName      = [System.String]$Global:OSDModuleResource.NewOSDCloudVM.SwitchName
        VHDSizeGB       = [System.Int64]$Global:OSDModuleResource.NewOSDCloudVM.VHDSizeGB
    }
    $Results
}
function Get-OSDCloudVMSettings {
    <#
    .SYNOPSIS
    Gets information returned by Get-OSDCloudVMSettings.
 
    .DESCRIPTION
    Provides the implementation for Get-OSDCloudVMSettings.
 
    .EXAMPLE
    Get-OSDCloudVMSettings
    Runs Get-OSDCloudVMSettings with common parameters.
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-09 - Updated comment-based help
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
    #>

    [CmdletBinding()]
    param ()

    # OSDCloudVM Module Defaults
    $OSDCloudVMSettings = Get-OSDCloudVMDefaults

    # Create Template Logs Directory
    $TemplateLogs = "$env:ProgramData\OSDCloud\Logs"
    if (-NOT (Test-Path $TemplateLogs)) {
        $null = New-Item -Path $TemplateLogs -ItemType Directory -Force | Out-Null
    }

    # Import Template Defaults
    $TemplateConfigurationJson = "$env:ProgramData\OSDCloud\Logs\NewOSDCloudVM.json"
    if (Test-Path $TemplateConfigurationJson) {
        Write-Host -ForegroundColor DarkGray "Importing OSDCloudVM Template settings at $TemplateConfigurationJson"
        $TemplateConfiguration = Get-Content -Path $TemplateConfigurationJson -Raw | ConvertFrom-Json -ErrorAction "Stop" | ConvertTo-Hashtable
        if ($TemplateConfiguration) {
            foreach ($Key in $TemplateConfiguration.Keys) {
                $OSDCloudVMSettings.$Key = $TemplateConfiguration.$Key
            }
        }
    }

    # Import Workspace Defaults
    $WorkspaceConfigurationJson = Join-Path (Get-OSDCloudWorkspace) 'Logs\NewOSDCloudVM.json'
    if (Test-Path $WorkspaceConfigurationJson) {
        Write-Host -ForegroundColor DarkGray "Importing OSDCloudVM Workspace settings at $WorkspaceConfigurationJson"
        $WorkspaceConfiguration = Get-Content -Path $WorkspaceConfigurationJson -Raw | ConvertFrom-Json -ErrorAction "Stop" | ConvertTo-Hashtable
        if ($WorkspaceConfiguration) {
            foreach ($Key in $WorkspaceConfiguration.Keys) {
                $OSDCloudVMSettings.$Key = $WorkspaceConfiguration.$Key
            }
        }
    }

    # Display the updated settings
    Return $OSDCloudVMSettings
}
function New-OSDCloudVM {
    <#
    .SYNOPSIS
    Creates resources by using New-OSDCloudVM.
 
    .DESCRIPTION
    Provides the implementation for New-OSDCloudVM.
 
    .PARAMETER CheckpointVM
    Specifies the value for CheckpointVM.
 
    .PARAMETER Generation
    Specifies the value for Generation.
 
    .PARAMETER MemoryStartupGB
    Specifies the value for MemoryStartupGB.
 
    .PARAMETER NamePrefix
    Specifies the value for NamePrefix.
 
    .PARAMETER ProcessorCount
    Specifies the value for ProcessorCount.
 
    .PARAMETER StartVM
    Specifies the value for StartVM.
 
    .PARAMETER SwitchName
    Specifies the value for SwitchName.
 
    .PARAMETER VHDSizeGB
    Specifies the value for VHDSizeGB.
 
    .EXAMPLE
    New-OSDCloudVM -CheckpointVM <CheckpointVM> -Generation <Generation>
    Runs New-OSDCloudVM with common parameters.
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-09 - Updated comment-based help
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
    #>

    [CmdletBinding()]
    param (
        [System.Boolean]
        $CheckpointVM,

        [ValidateSet('1','2')]
        [System.UInt16]
        $Generation,

        [ValidateRange(2, 64)]
        [System.UInt16]
        $MemoryStartupGB,

        [System.String]
        $NamePrefix,

        [ValidateRange(2, 64)]
        [System.UInt16]
        $ProcessorCount,

        [System.Boolean]
        $StartVM,

        [System.String]
        $SwitchName,

        [ValidateRange(8, 128)]
        [System.UInt16]
        $VHDSizeGB
    )

    # OSDCloudVM
    $Global:NewOSDCloudVM = Get-OSDCloudVMSettings

    # Update Configuration from Parameters
    if ($PSBoundParameters.ContainsKey('CheckpointVM')) {$Global:NewOSDCloudVM.CheckpointVM = $CheckpointVM}
    if ($PSBoundParameters.ContainsKey('Generation')) {$Global:NewOSDCloudVM.Generation = $Generation}
    if ($PSBoundParameters.ContainsKey('MemoryStartupGB')) {$Global:NewOSDCloudVM.MemoryStartupGB = $MemoryStartupGB}
    if ($PSBoundParameters.ContainsKey('NamePrefix')) {$Global:NewOSDCloudVM.NamePrefix = $NamePrefix}
    if ($PSBoundParameters.ContainsKey('ProcessorCount')) {$Global:NewOSDCloudVM.ProcessorCount = $ProcessorCount}
    if ($PSBoundParameters.ContainsKey('StartVM')) {$Global:NewOSDCloudVM.StartVM = $StartVM}
    if ($PSBoundParameters.ContainsKey('SwitchName')) {$Global:NewOSDCloudVM.SwitchName = $SwitchName}
    if ($PSBoundParameters.ContainsKey('VHDSizeGB')) {$Global:NewOSDCloudVM.VHDSizeGB = $VHDSizeGB}

    # Validate SwitchName
    if ($Global:NewOSDCloudVM.SwitchName) {
        $ValidateSwitch = Get-VMSwitch -Name $Global:NewOSDCloudVM.SwitchName -ErrorAction SilentlyContinue

        if (-not ($ValidateSwitch)) {
            Write-Warning "SwitchName value '$($Global:NewOSDCloudVM.SwitchName)' is not valid and will be set to Not connected."
            $Global:NewOSDCloudVM.SwitchName = $null
        }
    }

    # Export the updated configuration
    $WorkspaceConfigurationJson = Join-Path (Get-OSDCloudWorkspace) 'Logs\NewOSDCloudVM.json'
    Write-Host -ForegroundColor DarkGray "Exporting updated configuration to $WorkspaceConfigurationJson"
    $Global:NewOSDCloudVM | ConvertTo-Json -Depth 10 | Out-File -FilePath $WorkspaceConfigurationJson -Force

    if ($Reset) {
        $Global:OSDCloudVM = $null
        $Global:OSDCloudVM = [ordered]@{
            CheckpointVM                = $NewOSDCloudVM.CheckpointVM
            Generation                  = $NewOSDCloudVM.Generation
            MemoryStartupBytes          = ($NewOSDCloudVM.MemoryStartupGB * 1GB)
            MemoryStartupGB             = $NewOSDCloudVM.MemoryStartupGB
            NamePrefix                  = $NewOSDCloudVM.NamePrefix
            ProcessorCount              = $NewOSDCloudVM.ProcessorCount
            StartVM                     = $NewOSDCloudVM.StartVM
            SwitchName                  = $NewOSDCloudVM.SwitchName
            VHDSizeGB                   = [System.Int64]$Global:NewOSDCloudVM.VHDSizeGB
        }

        # Display the current configuration
        Write-Output $Global:OSDCloudVM
    }
    else {
        # Get Hyper-V Defaults
        #$VMManagementService = Get-WmiObject -Namespace root\virtualization\v2 Msvm_VirtualSystemManagementService
        $VMManagementServiceSettingData = Get-WmiObject -Namespace root\virtualization\v2 Msvm_VirtualSystemManagementServiceSettingData

        # Set VM Name
        $VmName = "$($NewOSDCloudVM.NamePrefix)$((Get-Date).ToString('yyMMddHHmmss'))"

        if (($Global:OSDKBootMedia) -and (Test-Path $($Global:OSDKBootMedia.BootMediaPath) -ErrorAction SilentlyContinue)) {
            $DvdDrivePath = Join-Path $($Global:OSDKBootMedia.BootMediaPath) 'OSDKBoot_NoPrompt.iso'
        }
        elseif (Test-Path (Join-Path $(Get-OSDCloudWorkspace) 'OSDeploy_NoPrompt.iso')) {
            $DvdDrivePath = Join-Path $(Get-OSDCloudWorkspace) 'OSDeploy_NoPrompt.iso'
        }
        elseif (Test-Path (Join-Path $(Get-OSDCloudWorkspace) 'OSDCloud_NoPrompt.iso')) {
            $DvdDrivePath = Join-Path $(Get-OSDCloudWorkspace) 'OSDCloud_NoPrompt.iso'
        }
        elseif (Test-Path (Join-Path $(Get-OSDCloudWorkspace) 'OSDKBoot_NoPrompt.iso')) {
            $DvdDrivePath = Join-Path $(Get-OSDCloudWorkspace) 'OSDKBoot_NoPrompt.iso'
        }
        elseif (Test-Path (Join-Path $(Get-OSDCloudWorkspace) 'BootMedia_NoPrompt.iso')) {
            $DvdDrivePath = Join-Path $(Get-OSDCloudWorkspace) 'BootMedia_NoPrompt.iso'
        }
        elseif (Test-Path (Join-Path $(Get-OSDCloudWorkspace) 'WinRE_NoPrompt.iso')) {
            $DvdDrivePath = Join-Path $(Get-OSDCloudWorkspace) 'WinRE_NoPrompt.iso'
        }

        # Build Final Configuration
        $Global:OSDCloudVM = $null
        $Global:OSDCloudVM = [ordered]@{
            CheckpointVM                = $NewOSDCloudVM.CheckpointVM
            DvdDrivePath                = $DvdDrivePath
            Generation                  = $NewOSDCloudVM.Generation
            MemoryStartupBytes          = ($NewOSDCloudVM.MemoryStartupGB * 1GB)
            MemoryStartupGB             = $NewOSDCloudVM.MemoryStartupGB
            Name                        = $VmName
            NamePrefix                  = $NewOSDCloudVM.NamePrefix
            ProcessorCount              = $NewOSDCloudVM.ProcessorCount
            StartVM                     = $NewOSDCloudVM.StartVM
            SwitchName                  = $NewOSDCloudVM.SwitchName
            VHDPath                     = [System.String](Join-Path $VMManagementServiceSettingData.DefaultVirtualHardDiskPath "$VmName.vhdx")
            VHDSizeBytes                = ($NewOSDCloudVM.VHDSizeGB * 1GB)
            VHDSizeGB                   = [System.Int64]$Global:NewOSDCloudVM.VHDSizeGB
        }

        # Display the current configuration
        Write-Output $Global:OSDCloudVM

        # Create VM VHD
        if ($OSDCloudVM.SwitchName) {
            $vm = New-VM -Name $OSDCloudVM.Name -Generation $OSDCloudVM.Generation -MemoryStartupBytes $OSDCloudVM.MemoryStartupBytes -NewVHDPath $OSDCloudVM.VHDPath -NewVHDSizeBytes $OSDCloudVM.VHDSizeBytes -SwitchName $OSDCloudVM.SwitchName -Verbose -ErrorAction Stop
        }
        else {
            $vm = New-VM -Name $OSDCloudVM.Name -Generation $OSDCloudVM.Generation -MemoryStartupBytes $OSDCloudVM.MemoryStartupBytes -NewVHDPath $OSDCloudVM.VHDPath -NewVHDSizeBytes $OSDCloudVM.VHDSizeBytes -Verbose -ErrorAction Stop
        }

        # Create DVD
        $OSDCloudVM.DvdDrive = $vm | Add-VMDvdDrive -Path $OSDCloudVM.DvdDrivePath -Passthru -Verbose
        $OSDCloudVM.HardDiskDrive = $vm | Get-VMHardDiskDrive
        $OSDCloudVM.NetworkAdapter = $vm | Get-VMNetworkAdapter

        if ($OSDCloudVM.Generation -eq 2) {
            # First Boot Device
            $vm | Set-VMFirmware -FirstBootDevice $OSDCloudVM.DvdDrive

            # Firmware
            #$vm | Set-VMFirmware -BootOrder $OSDCloudVM.DvdDrive, $vmHardDiskDrive, $vmNetworkAdapter -Verbose

            # Security
            $vm | Set-VMFirmware -EnableSecureBoot On -Verbose
            if ((Get-TPM).TpmPresent -eq $true -and (Get-TPM).TpmReady -eq $true) {
                $vm | Set-VMSecurity -VirtualizationBasedSecurityOptOut:$false -Verbose
                $vm | Set-VMKeyProtector -NewLocalKeyProtector -Verbose
                $vm | Enable-VMTPM -Verbose
            }
        }

        # Memory
        $vm | Set-VMMemory -DynamicMemoryEnabled $false -Verbose

        # Processor
        $vm | Set-VMProcessor -Count $OSDCloudVM.ProcessorCount -Verbose

        # Integration Services
        # Thanks Andreas Landry
        $IntegrationService = Get-VMIntegrationService -VMName $vm.Name | Where-Object { $_ -match "Microsoft:[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}\\6C09BB55-D683-4DA0-8931-C9BF705F6480" }
        $vm | Get-VMIntegrationService -Name $IntegrationService.Name | Enable-VMIntegrationService -Verbose

        # Checkpoints Start Stop
        $vm | Set-VM -AutomaticCheckpointsEnabled $false -AutomaticStartAction Nothing -AutomaticStartDelay 3 -AutomaticStopAction Shutdown -Verbose

        #Export Final Configuration
        $Global:OSDCloudVM.VM = $vm
        Write-Verbose "Exporting current configuration to $env:Temp\OSDCloudVM.json"
        $Global:OSDCloudVM | ConvertTo-Json -Depth 2 | Out-File -FilePath "$env:TEMP\OSDCloudVM.json" -Force

        if ($Global:OSDCloudVM.CheckpointVM -eq $true) {
            $vm | Checkpoint-VM -SnapshotName 'New-VM' -Verbose
        }

        if ($Global:OSDCloudVM.StartVM -eq $true) {
            vmconnect.exe $env:ComputerName $OSDCloudVM.Name
            Start-Sleep -Seconds 3
            $vm | Start-VM -Verbose
        }
    }
}
Register-ArgumentCompleter -CommandName New-OSDCloudVM -ParameterName 'SwitchName' -ScriptBlock {Get-VMSwitch | Select-Object -ExpandProperty Name | ForEach-Object {if ($_.Contains(' ')) {"'$_'"} else {$_}}}
function Reset-OSDCloudVMSettings {
    <#
    .SYNOPSIS
    Resets configuration by using Reset-OSDCloudVMSettings.
 
    .DESCRIPTION
    Provides the implementation for Reset-OSDCloudVMSettings.
 
    .EXAMPLE
    Reset-OSDCloudVMSettings
    Runs Reset-OSDCloudVMSettings with common parameters.
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-09 - Updated comment-based help
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
    #>

    [CmdletBinding()]
    param ()

    $TemplateConfigurationJson = "$env:ProgramData\OSDCloud\Logs\NewOSDCloudVM.json"
    if (Test-Path $TemplateConfigurationJson) {
        Write-Warning "Removing OSDCloudVM Template configuration at $TemplateConfigurationJson"
        Remove-Item -Path $TemplateConfigurationJson -Force -ErrorAction SilentlyContinue | Out-Null
    }

    $WorkspaceConfigurationJson = Join-Path (Get-OSDCloudWorkspace) 'Logs\NewOSDCloudVM.json'
    if (Test-Path $WorkspaceConfigurationJson) {
        Write-Warning "Removing OSDCloudVM Workspace configuration at $WorkspaceConfigurationJson"
        Remove-Item -Path $WorkspaceConfigurationJson -Force -ErrorAction SilentlyContinue | Out-Null
    }

    Get-OSDCloudVMSettings
}
function Set-OSDCloudVMSettings {
    <#
    .SYNOPSIS
    Sets configuration values by using Set-OSDCloudVMSettings.
 
    .DESCRIPTION
    Provides the implementation for Set-OSDCloudVMSettings.
 
    .PARAMETER CheckpointVM
    Specifies the value for CheckpointVM.
 
    .PARAMETER Generation
    Specifies the value for Generation.
 
    .PARAMETER MemoryStartupGB
    Specifies the value for MemoryStartupGB.
 
    .PARAMETER NamePrefix
    Specifies the value for NamePrefix.
 
    .PARAMETER ProcessorCount
    Specifies the value for ProcessorCount.
 
    .PARAMETER StartVM
    Specifies the value for StartVM.
 
    .PARAMETER SwitchName
    Specifies the value for SwitchName.
 
    .PARAMETER VHDSizeGB
    Specifies the value for VHDSizeGB.
 
    .EXAMPLE
    Set-OSDCloudVMSettings -CheckpointVM <CheckpointVM> -Generation <Generation>
    Runs Set-OSDCloudVMSettings with common parameters.
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-09 - Updated comment-based help
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
    #>

    [CmdletBinding()]
    param (
        [System.Boolean]
        $CheckpointVM,

        [ValidateSet('1','2')]
        [System.UInt16]
        $Generation,

        [ValidateRange(2, 64)]
        [System.UInt16]
        $MemoryStartupGB,

        [System.String]
        $NamePrefix,

        [ValidateRange(2, 64)]
        [System.UInt16]
        $ProcessorCount,

        [System.Boolean]
        $StartVM,

        [System.String]
        $SwitchName,

        [ValidateRange(8, 128)]
        [System.UInt16]
        $VHDSizeGB
    )

    # OSDCloudVM Module Defaults
    $OSDCloudVMSettings = Get-OSDCloudVMDefaults

    # Create Template Logs Directory
    $TemplateLogs = "$env:ProgramData\OSDCloud\Logs"
    if (-NOT (Test-Path $TemplateLogs)) {
        $null = New-Item -Path $TemplateLogs -ItemType Directory -Force | Out-Null
    }

    # Import Template Defaults
    $TemplateConfigurationJson = "$env:ProgramData\OSDCloud\Logs\NewOSDCloudVM.json"
    if (Test-Path $TemplateConfigurationJson) {
        Write-Host -ForegroundColor DarkGray "Importing OSDCloudVM Template settings at $TemplateConfigurationJson"
        $TemplateConfiguration = Get-Content -Path $TemplateConfigurationJson -Raw | ConvertFrom-Json -ErrorAction "Stop" | ConvertTo-Hashtable
        if ($TemplateConfiguration) {
            foreach ($Key in $TemplateConfiguration.Keys) {
                $OSDCloudVMSettings.$Key = $TemplateConfiguration.$Key
            }
        }
    }

    # Update Configuration from Parameters
    if ($PSBoundParameters.ContainsKey('CheckpointVM')) {$OSDCloudVMSettings.CheckpointVM = $CheckpointVM}
    if ($PSBoundParameters.ContainsKey('Generation')) {$OSDCloudVMSettings.Generation = $Generation}
    if ($PSBoundParameters.ContainsKey('MemoryStartupGB')) {$OSDCloudVMSettings.MemoryStartupGB = $MemoryStartupGB}
    if ($PSBoundParameters.ContainsKey('NamePrefix')) {$OSDCloudVMSettings.NamePrefix = $NamePrefix}
    if ($PSBoundParameters.ContainsKey('ProcessorCount')) {$OSDCloudVMSettings.ProcessorCount = $ProcessorCount}
    if ($PSBoundParameters.ContainsKey('StartVM')) {$OSDCloudVMSettings.StartVM = $StartVM}
    if ($PSBoundParameters.ContainsKey('SwitchName')) {$OSDCloudVMSettings.SwitchName = $SwitchName}
    if ($PSBoundParameters.ContainsKey('VHDSizeGB')) {$OSDCloudVMSettings.VHDSizeGB = $VHDSizeGB}

    # Validate SwitchName
    if ($OSDCloudVMSettings.SwitchName) {
        $ValidateSwitch = Get-VMSwitch -Name $OSDCloudVMSettings.SwitchName -ErrorAction SilentlyContinue

        if (-not ($ValidateSwitch)) {
            Write-Warning "SwitchName value '$($OSDCloudVMSettings.SwitchName)' is not valid and will be set to Not connected."
            $OSDCloudVMSettings.SwitchName = $null
        }
    }

    # Export the updated configuration
    Write-Host -ForegroundColor Cyan "Exporting updated configuration to $TemplateConfigurationJson"
    $OSDCloudVMSettings | ConvertTo-Json -Depth 10 | Out-File -FilePath $TemplateConfigurationJson -Force

    # Display the updated settings
    Return $OSDCloudVMSettings
}
Register-ArgumentCompleter -CommandName Set-OSDCloudVMSettings -ParameterName 'SwitchName' -ScriptBlock {Get-VMSwitch | Select-Object -ExpandProperty Name | ForEach-Object {if ($_.Contains(' ')) {"'$_'"} else {$_}}}