function Get-USBDrive() {
    $USBDriveLetter = (Get-Volume | Where-Object { $_.DriveType -eq 'Removable' -and $_.FileSystemType -eq 'NTFS' }).DriveLetter
    if ($null -eq $USBDriveLetter) {
        #Must be using a fixed USB drive - difficult to grab drive letter from win32_diskdrive. Assume user followed instructions and used Deploy as the friendly name for partition
        $USBDriveLetter = (Get-Volume | Where-Object { $_.DriveType -eq 'Fixed' -and $_.FileSystemType -eq 'NTFS' -and $_.FileSystemLabel -eq 'Deploy' }).DriveLetter
        #If we didn't get the drive letter, stop the script.
        if ($null -eq $USBDriveLetter) {
            $errorMessage = 'Cannot find USB drive letter. If using a fixed USB drive, name the deployment partition "Deploy".'
            WriteLog ($errorMessage + ' Exiting.')
            Stop-Script -Message $errorMessage
        }

    }
    $USBDriveLetter = $USBDriveLetter + ":\"
    return $USBDriveLetter
}

function Get-HardDrive() {
    $systemInfo = Get-CimInstance -Class 'Win32_ComputerSystem'
    $manufacturer = $systemInfo.Manufacturer
    $model = $systemInfo.Model
    WriteLog 'Getting Hard Drive info'
    if ($manufacturer -eq 'Microsoft Corporation' -and $model -eq 'Virtual Machine') {
        WriteLog 'Running in a Hyper-V VM. Getting virtual disk on Index 0 and SCSILogicalUnit 0'
        $diskDriveCandidates = @(Get-CimInstance -Class 'Win32_DiskDrive' | Where-Object { $_.MediaType -eq 'Fixed hard disk media' `
                    -and $_.Model -eq 'Microsoft Virtual Disk' `
                    -and $_.Index -eq 0 `
                    -and $_.SCSILogicalUnit -eq 0
            })
    }
    else {
        WriteLog 'Not running in a VM. Getting physical disk drive'
        $diskDriveCandidates = @(Get-CimInstance -Class 'Win32_DiskDrive' | Where-Object { $_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk' })
    }

    # Return the array of candidates for selection in main script
    return $diskDriveCandidates
}

function WriteLog($LogText) { 
    Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText"
}

function Read-MenuSelection {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Prompt,

        [Parameter(Mandatory = $true)]
        [string]$InvalidInputMessage,

        [Parameter()]
        [int[]]$ValidSelections,

        [Parameter()]
        [int]$Minimum = [int]::MinValue,

        [Parameter()]
        [int]$Maximum = [int]::MaxValue,

        [Parameter()]
        [switch]$AllowSkip
    )

    do {
        $userInput = Read-Host $Prompt
        if ([string]::IsNullOrWhiteSpace($userInput)) {
            Write-Host $InvalidInputMessage
            continue
        }

        $selection = 0
        if (-not [int]::TryParse($userInput, [ref]$selection)) {
            Write-Host $InvalidInputMessage
            continue
        }

        if ($AllowSkip -and $selection -eq 0) {
            return 0
        }

        if ($PSBoundParameters.ContainsKey('ValidSelections')) {
            if ($ValidSelections -notcontains $selection) {
                Write-Host $InvalidInputMessage
                continue
            }
        }
        elseif ($selection -lt $Minimum -or $selection -gt $Maximum) {
            Write-Host $InvalidInputMessage
            continue
        }

        return $selection
    } until ($false)
}

function Set-DiskpartAnswerFiles($DiskpartFile, $DiskID) {
    (Get-Content $DiskpartFile).Replace('disk 0', "disk $DiskID") | Set-Content -Path $DiskpartFile
}

function Set-Computername($computername) {
    [xml]$xml = Get-Content $UnattendFile
    $components = $xml.unattend.settings.component
    $found = $false
    foreach ($component in $components) {
        if ($component.ComputerName) {
            $component.ComputerName = $computername
            $found = $true
            break
        }
    }
    if (-not $found) {
        WriteLog 'ComputerName element not found in unattend.xml.'
        throw 'ComputerName element not found in unattend.xml.'
    }
    $xml.Save($UnattendFile)
    return $computername
}

function Get-UnattendComputerNameValue {
    if ($null -eq $UnattendFile) {
        return $null
    }

    [xml]$xml = Get-Content $UnattendFile
    foreach ($component in $xml.unattend.settings.component) {
        if ($component.ComputerName) {
            return [string]$component.ComputerName
        }
    }

    return $null
}

function Get-FFUDataPartitionDriveLetterDeploymentContext {
    param(
        [Parameter(Mandatory = $true)]
        [string]$WindowsPartitionRoot
    )

    $runtimeDirectory = Join-Path -Path $WindowsPartitionRoot -ChildPath 'Windows\Setup\Scripts\FFUDL'
    $runtimeScriptPath = Join-Path -Path $runtimeDirectory -ChildPath 'Apply.ps1'
    $manifestPath = Join-Path -Path $runtimeDirectory -ChildPath 'Manifest.json'
    if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
        if (Test-Path -LiteralPath $runtimeDirectory -PathType Container) {
            throw "Data partition drive-letter runtime directory exists without a manifest at $manifestPath."
        }
        return $null
    }

    if (-not (Test-Path -LiteralPath $runtimeScriptPath -PathType Leaf)) {
        throw "Data partition drive-letter manifest exists without its runtime script at $runtimeScriptPath."
    }

    $manifest = Get-Content -LiteralPath $manifestPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
    if ([int]$manifest.SchemaVersion -ne 1) {
        throw "Unsupported data partition drive-letter manifest schema version '$($manifest.SchemaVersion)'."
    }
    if (@($manifest.Partitions).Count -eq 0) {
        throw 'Data partition drive-letter manifest does not contain any partitions.'
    }

    $processorArchitecture = ([string]$manifest.ProcessorArchitecture).Trim().ToLowerInvariant()
    if ($processorArchitecture -notin @('amd64', 'arm64')) {
        throw "Unsupported data partition drive-letter processor architecture '$processorArchitecture'."
    }

    WriteLog "Found data partition drive-letter assignment manifest with $(@($manifest.Partitions).Count) partition(s)."
    return [pscustomobject]@{
        RuntimeDirectory      = $runtimeDirectory
        RuntimeScriptPath     = $runtimeScriptPath
        ManifestPath          = $manifestPath
        ProcessorArchitecture = $processorArchitecture
    }
}

function Add-FFUDataPartitionDriveLetterCommandToAppliedUnattend {
    param(
        [Parameter(Mandatory = $true)]
        [string]$UnattendPath,
        [Parameter(Mandatory = $true)]
        [ValidateSet('amd64', 'arm64')]
        [string]$ProcessorArchitecture
    )

    $unattendNamespace = 'urn:schemas-microsoft-com:unattend'
    $wcmNamespace = 'http://schemas.microsoft.com/WMIConfig/2002/State'
    $unattendXml = New-Object System.Xml.XmlDocument
    $unattendXml.PreserveWhitespace = $true
    if (-not (Test-Path -LiteralPath $UnattendPath -PathType Leaf)) {
        throw "Deployment unattend was not found at the expected path $UnattendPath."
    }
    $unattendXml.Load($UnattendPath)

    $unattendRoot = $unattendXml.DocumentElement
    if ($null -eq $unattendRoot -or $unattendRoot.LocalName -ne 'unattend' -or $unattendRoot.NamespaceURI -ne $unattendNamespace) {
        throw "Unattend XML at $UnattendPath does not use the supported unattend root namespace."
    }

    $namespaceManager = New-Object System.Xml.XmlNamespaceManager($unattendXml.NameTable)
    $namespaceManager.AddNamespace('un', $unattendNamespace)
    $specializeSettings = $unattendRoot.SelectSingleNode("un:settings[@pass='specialize']", $namespaceManager)
    if ($null -eq $specializeSettings) {
        $specializeSettings = $unattendXml.CreateElement('settings', $unattendNamespace)
        $null = $specializeSettings.SetAttribute('pass', 'specialize')
        $firstSettingsNode = $unattendRoot.SelectSingleNode('un:settings', $namespaceManager)
        if ($null -ne $firstSettingsNode) {
            $null = $unattendRoot.InsertBefore($specializeSettings, $firstSettingsNode)
        }
        else {
            $null = $unattendRoot.AppendChild($specializeSettings)
        }
    }

    $deploymentComponents = @($specializeSettings.SelectNodes("un:component[@name='Microsoft-Windows-Deployment']", $namespaceManager) |
        Where-Object { $_.GetAttribute('processorArchitecture') -ieq $ProcessorArchitecture })
    if ($deploymentComponents.Count -gt 1) {
        throw "Unattend XML at $UnattendPath contains multiple Microsoft-Windows-Deployment components for $ProcessorArchitecture."
    }
    if ($deploymentComponents.Count -eq 0) {
        $deploymentComponent = $unattendXml.CreateElement('component', $unattendNamespace)
        $null = $deploymentComponent.SetAttribute('name', 'Microsoft-Windows-Deployment')
        $null = $deploymentComponent.SetAttribute('processorArchitecture', $ProcessorArchitecture)
        $null = $deploymentComponent.SetAttribute('publicKeyToken', '31bf3856ad364e35')
        $null = $deploymentComponent.SetAttribute('language', 'neutral')
        $null = $deploymentComponent.SetAttribute('versionScope', 'nonSxS')
        $null = $specializeSettings.AppendChild($deploymentComponent)
    }
    else {
        $deploymentComponent = $deploymentComponents[0]
    }

    $runSynchronous = $deploymentComponent.SelectSingleNode('un:RunSynchronous', $namespaceManager)
    if ($null -eq $runSynchronous) {
        $runSynchronous = $unattendXml.CreateElement('RunSynchronous', $unattendNamespace)
        $null = $deploymentComponent.AppendChild($runSynchronous)
    }

    $existingCommands = @($runSynchronous.SelectNodes('un:RunSynchronousCommand', $namespaceManager))
    $orderedExistingCommands = [System.Collections.Generic.List[pscustomobject]]::new()
    $commandIndex = 0
    foreach ($existingCommand in $existingCommands) {
        $pathNode = $existingCommand.SelectSingleNode('un:Path', $namespaceManager)
        if ($null -ne $pathNode -and $pathNode.InnerText -match '(?i)\\FFUDL\\Apply\.ps1') {
            $null = $runSynchronous.RemoveChild($existingCommand)
            continue
        }

        $orderValue = [int]::MaxValue
        $orderNode = $existingCommand.SelectSingleNode('un:Order', $namespaceManager)
        if ($null -ne $orderNode) {
            $parsedOrder = 0
            if ([int]::TryParse($orderNode.InnerText, [ref]$parsedOrder)) {
                $orderValue = $parsedOrder
            }
        }
        $orderedExistingCommands.Add([pscustomobject]@{ Node = $existingCommand; Order = $orderValue; Index = $commandIndex })
        $commandIndex++
    }

    $nextOrder = 2
    foreach ($existingCommandInfo in @($orderedExistingCommands | Sort-Object -Property Order, Index)) {
        $orderNode = $existingCommandInfo.Node.SelectSingleNode('un:Order', $namespaceManager)
        if ($null -eq $orderNode) {
            $orderNode = $unattendXml.CreateElement('Order', $unattendNamespace)
            $null = $existingCommandInfo.Node.PrependChild($orderNode)
        }
        $orderNode.InnerText = [string]$nextOrder
        $nextOrder++
    }

    $newCommand = $unattendXml.CreateElement('RunSynchronousCommand', $unattendNamespace)
    $actionAttribute = $unattendXml.CreateAttribute('wcm', 'action', $wcmNamespace)
    $actionAttribute.Value = 'add'
    $null = $newCommand.Attributes.Append($actionAttribute)
    $orderElement = $unattendXml.CreateElement('Order', $unattendNamespace)
    $orderElement.InnerText = '1'
    $null = $newCommand.AppendChild($orderElement)
    $descriptionElement = $unattendXml.CreateElement('Description', $unattendNamespace)
    $descriptionElement.InnerText = 'Apply FFU data partition drive letters'
    $null = $newCommand.AppendChild($descriptionElement)
    $pathElement = $unattendXml.CreateElement('Path', $unattendNamespace)
    $specializeCommand = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\Windows\Setup\Scripts\FFUDL\Apply.ps1" -ManifestPath "C:\Windows\Setup\Scripts\FFUDL\Manifest.json" -Phase Specialize'
    if ($specializeCommand.Length -gt 259) {
        throw "Data partition drive-letter specialize command exceeds the 259-character unattend Path limit. Length: $($specializeCommand.Length)."
    }
    $pathElement.InnerText = $specializeCommand
    $null = $newCommand.AppendChild($pathElement)
    $willRebootElement = $unattendXml.CreateElement('WillReboot', $unattendNamespace)
    $willRebootElement.InnerText = 'OnRequest'
    $null = $newCommand.AppendChild($willRebootElement)
    if ($null -ne $runSynchronous.FirstChild) {
        $null = $runSynchronous.InsertBefore($newCommand, $runSynchronous.FirstChild)
    }
    else {
        $null = $runSynchronous.AppendChild($newCommand)
    }

    $unattendXml.Save($UnattendPath)
    WriteLog "Merged data partition drive-letter specialize command into $UnattendPath."
}

function Test-FFUDataPartitionDriveLetterCommandInAppliedUnattend {
    param(
        [Parameter(Mandatory = $true)]
        [string]$UnattendPath,
        [Parameter(Mandatory = $true)]
        [ValidateSet('amd64', 'arm64')]
        [string]$ProcessorArchitecture
    )

    if (-not (Test-Path -LiteralPath $UnattendPath -PathType Leaf)) {
        throw "Data partition drive-letter deployment unattend was not found at $UnattendPath."
    }

    $unattendNamespace = 'urn:schemas-microsoft-com:unattend'
    $wcmNamespace = 'http://schemas.microsoft.com/WMIConfig/2002/State'
    $unattendXml = New-Object System.Xml.XmlDocument
    $unattendXml.Load($UnattendPath)
    if ($null -eq $unattendXml.DocumentElement -or $unattendXml.DocumentElement.NamespaceURI -ne $unattendNamespace) {
        throw "Unattend XML at $UnattendPath does not use the supported unattend root namespace."
    }

    $namespaceManager = New-Object System.Xml.XmlNamespaceManager($unattendXml.NameTable)
    $namespaceManager.AddNamespace('un', $unattendNamespace)
    $deploymentComponents = @($unattendXml.SelectNodes("//un:settings[@pass='specialize']/un:component[@name='Microsoft-Windows-Deployment']", $namespaceManager) |
        Where-Object { $_.GetAttribute('processorArchitecture') -ieq $ProcessorArchitecture })
    if ($deploymentComponents.Count -ne 1) {
        throw "Expected exactly one specialize Microsoft-Windows-Deployment component for $ProcessorArchitecture in $UnattendPath."
    }

    $commands = @($deploymentComponents[0].SelectNodes('un:RunSynchronous/un:RunSynchronousCommand', $namespaceManager))
    $driveLetterCommands = @($commands | Where-Object {
            $pathNode = $_.SelectSingleNode('un:Path', $namespaceManager)
            $null -ne $pathNode -and $pathNode.InnerText -match '(?i)\\FFUDL\\Apply\.ps1'
        })
    if ($driveLetterCommands.Count -ne 1) {
        throw "Expected exactly one data partition drive-letter specialize command in $UnattendPath."
    }

    $driveLetterCommand = $driveLetterCommands[0]
    $pathNode = $driveLetterCommand.SelectSingleNode('un:Path', $namespaceManager)
    $orderNode = $driveLetterCommand.SelectSingleNode('un:Order', $namespaceManager)
    $willRebootNode = $driveLetterCommand.SelectSingleNode('un:WillReboot', $namespaceManager)
    if ($null -eq $pathNode -or $pathNode.InnerText -notmatch '(?i)-WindowStyle\s+Hidden\s+-File(?:\s|$)') {
        throw "Data partition drive-letter specialize command does not use a hidden PowerShell window in $UnattendPath."
    }
    if ($null -eq $orderNode -or $orderNode.InnerText -ne '1') {
        throw "Data partition drive-letter specialize command is not first in $UnattendPath."
    }
    if ($null -eq $willRebootNode -or $willRebootNode.InnerText -ne 'OnRequest') {
        throw "Data partition drive-letter specialize command does not use WillReboot=OnRequest in $UnattendPath."
    }
    if ($driveLetterCommand.GetAttribute('action', $wcmNamespace) -ne 'add') {
        throw "Data partition drive-letter specialize command does not use wcm:action=add in $UnattendPath."
    }
    foreach ($otherCommand in @($commands | Where-Object { $_ -ne $driveLetterCommand })) {
        $otherOrderNode = $otherCommand.SelectSingleNode('un:Order', $namespaceManager)
        $otherOrder = 0
        if ($null -eq $otherOrderNode -or -not [int]::TryParse($otherOrderNode.InnerText, [ref]$otherOrder) -or $otherOrder -le 1) {
            throw "Another specialize command conflicts with first order in $UnattendPath."
        }
    }

    WriteLog "Verified data partition drive-letter specialize command in $UnattendPath."
}

function Test-LegacyPromptComputerName($computername) {
    if ([string]::IsNullOrWhiteSpace($computername)) {
        return $false
    }

    $normalizedName = $computername.Trim().ToLowerInvariant()
    return $normalizedName -in @('mycomputer', 'default')
}

function Get-NormalizedComputerName($computername) {
    if ([string]::IsNullOrWhiteSpace($computername)) {
        throw 'Computer name cannot be empty.'
    }

    $normalizedName = ($computername -replace "\s", '').Trim()
    if ([string]::IsNullOrWhiteSpace($normalizedName)) {
        throw 'Computer name cannot be empty after removing spaces.'
    }

    if ($normalizedName.Length -gt 15) {
        $normalizedName = $normalizedName.Substring(0, 15)
    }

    return $normalizedName
}

function Resolve-ComputerNameTemplate($computerNameTemplate, $serialNumber) {
    if ([string]::IsNullOrWhiteSpace($computerNameTemplate)) {
        throw 'Computer name template cannot be empty.'
    }

    $resolvedName = $computerNameTemplate -replace '(?i)%serial%', $serialNumber
    if ($resolvedName -match '%') {
        throw 'Unsupported device name variable found. Only %serial% is supported.'
    }

    return Get-NormalizedComputerName($resolvedName)
}

function Set-ConfiguredComputerName($computername) {
    $normalizedName = Get-NormalizedComputerName($computername)
    $normalizedName = Set-Computername($normalizedName)
    Writelog "Computer name will be set to $normalizedName"
    Write-Host "Computer name will be set to $normalizedName"
    return $normalizedName
}

function Invoke-Process {
    [CmdletBinding(SupportsShouldProcess)]
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$FilePath,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$ArgumentList,

        [Parameter()]
        [switch]$IgnoreExitCode,

        [Parameter()]
        [switch]$PassThruExitCode
    )

    $ErrorActionPreference = 'Stop'

    try {
        $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)"
        $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)"

        $startProcessParams = @{
            FilePath               = $FilePath
            ArgumentList           = $ArgumentList
            RedirectStandardError  = $stdErrTempFile
            RedirectStandardOutput = $stdOutTempFile
            Wait                   = $true;
            PassThru               = $true;
            NoNewWindow            = $true;
        }
        if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) {
            $cmd = Start-Process @startProcessParams
            $cmdOutput = Get-Content -Path $stdOutTempFile -Raw
            $cmdError = Get-Content -Path $stdErrTempFile -Raw

            if ($cmd.ExitCode -ne 0) {
                # Non-terminating mode: capture output to Scriptlog and continue
                if ($IgnoreExitCode) {
                    if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) {
                        WriteLog $cmdOutput
                    }
                    if ([string]::IsNullOrEmpty($cmdError) -eq $false) {
                        WriteLog $cmdError
                    }
                    if ($PassThruExitCode) {
                        return $cmd.ExitCode
                    }
                    return
                }

                if ($cmdError) {
                    throw $cmdError.Trim()
                }
                if ($cmdOutput) {
                    throw $cmdOutput.Trim()
                }
                throw "Process failed. ExitCode = $($cmd.ExitCode)."
            }
            else {
                if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) {
                    WriteLog $cmdOutput
                }
            }

            if ($PassThruExitCode) {
                return $cmd.ExitCode
            }
        }
    }
    catch {
        #$PSCmdlet.ThrowTerminatingError($_)
        WriteLog $_
        Write-Host 'Script failed - check scriptlog.txt on the USB drive for more info'
        throw $_

    }
    finally {
        Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore
        
    }
    
}

function Write-SectionHeader($Title) {
    $width = 51
    $leftPad = [math]::Floor(($width - $Title.Length) / 2)
    $rightPad = $width - $Title.Length - $leftPad
    $centeredTitle = (' ' * $leftPad) + $Title + (' ' * $rightPad)

    Write-Host "`n" # Add a newline for spacing
    Write-Host ('-' * $width) -ForegroundColor Yellow
    Write-Host $centeredTitle -ForegroundColor Yellow
    Write-Host ('-' * $width) -ForegroundColor Yellow
}

function Get-NormalizedManufacturer {
    param(
        [string]$Manufacturer
    )

    if ([string]::IsNullOrWhiteSpace($Manufacturer)) {
        return $null
    }

    $normalized = $Manufacturer.Trim().ToUpperInvariant()
    if ($normalized -like '*DELL*') {
        return 'Dell'
    }
    elseif ($normalized -like '*HP*' -or $normalized -like '*HEWLETT*') {
        return 'HP'
    }
    elseif ($normalized -like '*LENOVO*') {
        return 'Lenovo'
    }
    elseif ($normalized -like '*MICROSOFT*' -or $normalized -like '*SURFACE*') {
        return 'Microsoft'
    }
    elseif ($normalized -like '*PANASONIC*') {
        return 'Panasonic Corporation'
    }
    elseif ($normalized -like '*VIGLEN*') {
        return 'Viglen'
    }
    elseif ($normalized -like '*AZW*') {
        return 'AZW'
    }
    elseif ($normalized -like '*FUJITSU*') {
        return 'Fujitsu'
    }
    elseif ($normalized -like '*GETAC*') {
        return 'Getac'
    }
    elseif ($normalized -like '*BYTESPEED*') {
        return 'ByteSpeed'
    }
    elseif ($normalized -like '*INTEL*') {
        return 'Intel'
    }

    return $Manufacturer.Trim()
}

function Get-SystemIdentityMetadata {
    param(
        [Parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]$ComputerSystem,
        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance]$ComputerSystemProduct,
        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance]$MsSystemInformation
    )

    # Consolidate manufacturer normalization so UI and driver mapping share the same identifiers.
    $normalizedManufacturer = Get-NormalizedManufacturer -Manufacturer $ComputerSystem.Manufacturer
    if (-not $ComputerSystemProduct) {
        $ComputerSystemProduct = Get-CimInstance -Class Win32_ComputerSystemProduct -ErrorAction SilentlyContinue
    }
    $baseBoardInfo = Get-CimInstance -Class Win32_BaseBoard -ErrorAction SilentlyContinue | Select-Object -First 1
    $baseBoardSku = if ($baseBoardInfo -and -not [string]::IsNullOrWhiteSpace($baseBoardInfo.SKU)) { $baseBoardInfo.SKU.Trim() } else { $null }
    $msBaseBoardProduct = if ($MsSystemInformation -and $MsSystemInformation.BaseBoardProduct) { $MsSystemInformation.BaseBoardProduct.Trim() } else { $null }

    $modelCandidate = if ($normalizedManufacturer -eq 'Lenovo' -and $ComputerSystemProduct -and -not [string]::IsNullOrWhiteSpace($ComputerSystemProduct.Version)) {
        $ComputerSystemProduct.Version
    }
    else {
        $ComputerSystem.Model
    }
    if ([string]::IsNullOrWhiteSpace($modelCandidate)) {
        $modelCandidate = $ComputerSystem.Model
    }
    if ($modelCandidate) {
        $modelCandidate = $modelCandidate.Trim()
    }

    $identity = [pscustomobject]@{
        ManufacturerOriginal   = $ComputerSystem.Manufacturer
        ManufacturerNormalized = if ($normalizedManufacturer) { $normalizedManufacturer } else { $ComputerSystem.Manufacturer }
        ModelOriginal          = $modelCandidate
        ModelNormalized        = ConvertTo-ComparableModelName -Text $modelCandidate
        SystemSkuNormalized    = $null
        FallbackSkuNormalized  = $null
        MachineTypeNormalized  = $null
        IdentifierLabel        = 'System ID'
        IdentifierValue        = $null
    }

    if ($MsSystemInformation -and $MsSystemInformation.SystemSku) {
        $identity.SystemSkuNormalized = $MsSystemInformation.SystemSku.Trim().ToUpperInvariant()
    }

    switch ($identity.ManufacturerNormalized) {
        'Dell' {
            if ($MsSystemInformation -and $MsSystemInformation.SystemSku) {
                $identity.SystemSkuNormalized = $MsSystemInformation.SystemSku.Trim().ToUpperInvariant()
            }
            $oemStringArray = $ComputerSystem | Select-Object -ExpandProperty OEMStringArray -ErrorAction SilentlyContinue
            if ($oemStringArray) {
                $joinedOemString = ($oemStringArray -join ' ')
                $fallbackMatches = [regex]::Matches($joinedOemString, '\[\S*]')
                if ($fallbackMatches.Count -gt 0) {
                    $identity.FallbackSkuNormalized = $fallbackMatches[0].Value.TrimStart('[').TrimEnd(']').Trim().ToUpperInvariant()
                }
            }
            if ($identity.FallbackSkuNormalized) {
                $identity.IdentifierValue = $identity.FallbackSkuNormalized
            }
            elseif ($identity.SystemSkuNormalized) {
                $identity.IdentifierValue = $identity.SystemSkuNormalized
            }
            break
        }
        'HP' {
            if ($msBaseBoardProduct) {
                $identity.SystemSkuNormalized = $msBaseBoardProduct.ToUpperInvariant()
            }
            break
        }
        'Lenovo' {
            $modelValue = $ComputerSystem.Model
            if (-not [string]::IsNullOrWhiteSpace($modelValue) -and $modelValue.Length -ge 4) {
                $identity.MachineTypeNormalized = $modelValue.Substring(0, 4).Trim().ToUpperInvariant()
            }
            $identity.IdentifierLabel = 'Machine Type'
            if ($identity.MachineTypeNormalized) {
                $identity.IdentifierValue = $identity.MachineTypeNormalized
            }
            break
        }
        'Panasonic Corporation' {
            $identity.IdentifierLabel = 'System ID'
            if ($msBaseBoardProduct) {
                $identity.SystemSkuNormalized = $msBaseBoardProduct.ToUpperInvariant()
                $identity.IdentifierValue = $msBaseBoardProduct
            }
            break
        }
        'Viglen' {
            $identity.IdentifierLabel = 'System ID'
            if ($baseBoardSku) {
                $identity.SystemSkuNormalized = $baseBoardSku.ToUpperInvariant()
                $identity.IdentifierValue = $baseBoardSku
            }
            break
        }
        'AZW' {
            $identity.IdentifierLabel = 'System ID'
            if ($msBaseBoardProduct) {
                $identity.SystemSkuNormalized = $msBaseBoardProduct.ToUpperInvariant()
                $identity.IdentifierValue = $msBaseBoardProduct
            }
            break
        }
        'Fujitsu' {
            $identity.IdentifierLabel = 'System ID'
            if ($baseBoardSku) {
                $identity.SystemSkuNormalized = $baseBoardSku.ToUpperInvariant()
                $identity.IdentifierValue = $baseBoardSku
            }
            break
        }
        'Getac' {
            $identity.IdentifierLabel = 'System ID'
            if ($msBaseBoardProduct) {
                $identity.SystemSkuNormalized = $msBaseBoardProduct.ToUpperInvariant()
                $identity.IdentifierValue = $msBaseBoardProduct
            }
            break
        }
        'Intel' {
            $identity.IdentifierLabel = 'Model'
            if ($identity.ModelOriginal) {
                $identity.IdentifierValue = $identity.ModelOriginal
            }
            break
        }
        'ByteSpeed' {
            $modelValue = if ($ComputerSystem.Model) { $ComputerSystem.Model.Trim() } else { $null }
            if ($modelValue -and $modelValue -like '*NUC*') {
                $identity.ManufacturerNormalized = 'Intel'
                if ($msBaseBoardProduct) {
                    $identity.ModelOriginal = $msBaseBoardProduct
                    $identity.ModelNormalized = ConvertTo-ComparableModelName -Text $msBaseBoardProduct
                    $identity.IdentifierLabel = 'Model'
                    $identity.IdentifierValue = $msBaseBoardProduct
                }
                elseif ($modelValue) {
                    $identity.IdentifierLabel = 'Model'
                    $identity.IdentifierValue = $modelValue
                }
            }
            else {
                $identity.IdentifierLabel = 'Model'
                if ($modelValue) {
                    $identity.IdentifierValue = $modelValue
                }
            }
            break
        }
        default {
            break
        }
    }

    if ($null -eq $identity.IdentifierValue) {
        if ($identity.MachineTypeNormalized) {
            $identity.IdentifierValue = $identity.MachineTypeNormalized
        }
        elseif ($identity.SystemSkuNormalized) {
            $identity.IdentifierValue = $identity.SystemSkuNormalized
        }
    }

    return $identity
}

function Get-SystemInformation {
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject]$HardDrive
    )

    $computerSystem = Get-CimInstance -Class Win32_ComputerSystem
    $computerSystemProduct = Get-CimInstance -Class Win32_ComputerSystemProduct -ErrorAction SilentlyContinue
    $msSystemInformation = Get-CimInstance -Namespace 'root\WMI' -Class MS_SystemInformation -ErrorAction SilentlyContinue
    $systemIdentity = Get-SystemIdentityMetadata -ComputerSystem $computerSystem -ComputerSystemProduct $computerSystemProduct -MsSystemInformation $msSystemInformation

    $biosInfo = Get-CimInstance -Class Win32_Bios
    $processorInfo = Get-CimInstance -Class Win32_Processor | Select-Object -First 1
    $processor = if ($processorInfo) { $processorInfo.Name } else { 'Unknown' }
    $totalMemory = (Get-CimInstance -Class Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum
    if ($null -eq $totalMemory) {
        $totalMemory = 0
    }
    $totalMemoryGB = [math]::Round(($totalMemory / 1GB), 2)
    $diskSizeGB = [math]::Round(($HardDrive.DiskSize / 1GB), 2)

    $baseBoardManufacturer = if ($msSystemInformation -and $msSystemInformation.BaseBoardManufacturer) { $msSystemInformation.BaseBoardManufacturer.Trim() } else { $null }
    $baseBoardProduct = if ($msSystemInformation -and $msSystemInformation.BaseBoardProduct) { $msSystemInformation.BaseBoardProduct.Trim() } else { $null }
    $baseBoardVersion = if ($msSystemInformation -and $msSystemInformation.BaseBoardVersion) { $msSystemInformation.BaseBoardVersion.Trim() } else { $null }
    $biosMajorRelease = if ($msSystemInformation -and $null -ne $msSystemInformation.BiosMajorRelease) { [string]$msSystemInformation.BiosMajorRelease } else { $null }
    $biosMinorRelease = if ($msSystemInformation -and $null -ne $msSystemInformation.BiosMinorRelease) { [string]$msSystemInformation.BiosMinorRelease } else { $null }
    $biosReleaseDate = $null
    if ($msSystemInformation -and $msSystemInformation.BiosReleaseDate) {
        try {
            $biosReleaseDate = [System.Management.ManagementDateTimeConverter]::ToDateTime($msSystemInformation.BiosReleaseDate).ToString('yyyy-MM-dd HH:mm:ss')
        }
        catch {
            $biosReleaseDate = $msSystemInformation.BiosReleaseDate
        }
    }
    $biosVendor = if ($msSystemInformation -and $msSystemInformation.BiosVendor) { $msSystemInformation.BiosVendor.Trim() } else { $null }
    $biosVersion = if ($msSystemInformation -and $msSystemInformation.BiosVersion) { $msSystemInformation.BiosVersion.Trim() } else { $null }
    $ecFirmwareMajorRelease = if ($msSystemInformation -and $null -ne $msSystemInformation.ECFirmwareMajorRelease) { [string]$msSystemInformation.ECFirmwareMajorRelease } else { $null }
    $ecFirmwareMinorRelease = if ($msSystemInformation -and $null -ne $msSystemInformation.ECFirmwareMinorRelease) { [string]$msSystemInformation.ECFirmwareMinorRelease } else { $null }

    $displayManufacturer = if ($systemIdentity.ManufacturerNormalized) { $systemIdentity.ManufacturerNormalized } else { $computerSystem.Manufacturer }
    $displayModel = if ($systemIdentity.ModelNormalized) { $systemIdentity.ModelNormalized } else { $systemIdentity.ModelOriginal }

    $sysInfoData = [ordered]@{
        "Manufacturer"           = $displayManufacturer
        "Model"                  = $displayModel
        "Serial Number"          = $biosInfo.SerialNumber
        "Processor"              = $processor
        "Memory"                 = "{0} GB" -f $totalMemoryGB
        "Disk Size"              = "{0} GB" -f $diskSizeGB
        "Logical Sector Size"    = "$($HardDrive.BytesPerSector) Bytes"
        "BaseBoardManufacturer"  = if ($baseBoardManufacturer) { $baseBoardManufacturer } else { 'Not Detected' }
        "BaseBoardProduct"       = if ($baseBoardProduct) { $baseBoardProduct } else { 'Not Detected' }
        "BaseBoardVersion"       = if ($baseBoardVersion) { $baseBoardVersion } else { 'Not Detected' }
        "BiosMajorRelease"       = if ($biosMajorRelease) { $biosMajorRelease } else { 'Not Detected' }
        "BiosMinorRelease"       = if ($biosMinorRelease) { $biosMinorRelease } else { 'Not Detected' }
        "BiosReleaseDate"        = if ($biosReleaseDate) { $biosReleaseDate } else { 'Not Detected' }
        "BiosVendor"             = if ($biosVendor) { $biosVendor } else { 'Not Detected' }
        "BiosVersion"            = if ($biosVersion) { $biosVersion } else { 'Not Detected' }
        "ECFirmwareMajorRelease" = if ($ecFirmwareMajorRelease) { $ecFirmwareMajorRelease } else { 'Not Detected' }
        "ECFirmwareMinorRelease" = if ($ecFirmwareMinorRelease) { $ecFirmwareMinorRelease } else { 'Not Detected' }
        "ManufacturerNormalized" = $systemIdentity.ManufacturerNormalized
        "ModelNormalized"        = $systemIdentity.ModelNormalized
        "DriverIdentifierLabel"  = $systemIdentity.IdentifierLabel
        "DriverIdentifierValue"  = $systemIdentity.IdentifierValue
        "SystemSkuNormalized"    = $systemIdentity.SystemSkuNormalized
        "FallbackSkuNormalized"  = $systemIdentity.FallbackSkuNormalized
        "MachineTypeNormalized"  = $systemIdentity.MachineTypeNormalized
    }
    $sysInfoData[$systemIdentity.IdentifierLabel] = if ($systemIdentity.IdentifierValue) { $systemIdentity.IdentifierValue } else { 'Not Detected' }

    return [PSCustomObject]$sysInfoData
}

function Write-SystemInformation {
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject]$SystemInformation
    )

    $hiddenProperties = @(
        'SystemSkuNormalized',
        'FallbackSkuNormalized',
        'MachineTypeNormalized',
        'ManufacturerNormalized',
        'ModelNormalized',
        'DriverIdentifierLabel',
        'DriverIdentifierValue'
    )

    WriteLog '--- System Information ---'
    foreach ($property in $SystemInformation.psobject.Properties) {
        if ($hiddenProperties -contains $property.Name) {
            continue
        }
        WriteLog "$($property.Name): $($property.Value)"
    }
    WriteLog '--- End System Information ---'

    Write-SectionHeader -Title 'System Information'
    $displayData = [ordered]@{}
    foreach ($property in $SystemInformation.psobject.Properties) {
        if ($hiddenProperties -contains $property.Name) {
            continue
        }
        $displayData[$property.Name] = $property.Value
    }
    $consoleOutput = ([pscustomobject]$displayData | Format-List | Out-String)
    Write-Host $consoleOutput.Trim()
    Write-Host
}

function Find-DriverMappingRule {
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject]$SystemInformation,
        [Parameter(Mandatory = $true)]
        [object[]]$DriverMappings
    )

    $normalizedManufacturer = if ($SystemInformation.PSObject.Properties['ManufacturerNormalized']) {
        $SystemInformation.ManufacturerNormalized
    }
    else {
        Get-NormalizedManufacturer -Manufacturer $SystemInformation.Manufacturer
    }
    if ([string]::IsNullOrWhiteSpace($normalizedManufacturer)) {
        WriteLog 'DriverMapping: Unable to determine manufacturer for automatic matching.'
        return $null
    }

    $driverMappingsArray = @()
    foreach ($entry in @($DriverMappings)) {
        if ($null -ne $entry) {
            $driverMappingsArray += $entry
        }
    }
    if ($driverMappingsArray.Count -eq 0) {
        WriteLog 'DriverMapping: Mapping file contained no entries.'
        return $null
    }

    $rulesForMake = @($driverMappingsArray | Where-Object {
            $entryManufacturer = Get-NormalizedManufacturer -Manufacturer $_.Manufacturer
            $entryManufacturer -eq $normalizedManufacturer
        })
    if ($rulesForMake.Count -eq 0) {
        WriteLog "DriverMapping: No entries found for manufacturer '$normalizedManufacturer'."
        return $null
    }

    $systemSkuNormalized = if ($SystemInformation.PSObject.Properties['SystemSkuNormalized']) { $SystemInformation.SystemSkuNormalized } else { $null }
    $fallbackSkuNormalized = if ($SystemInformation.PSObject.Properties['FallbackSkuNormalized']) { $SystemInformation.FallbackSkuNormalized } else { $null }
    $machineTypeNormalized = if ($SystemInformation.PSObject.Properties['MachineTypeNormalized']) { $SystemInformation.MachineTypeNormalized } else { $null }
    $normalizedModel = if ($SystemInformation.PSObject.Properties['ModelNormalized']) { $SystemInformation.ModelNormalized } else { $null }
    if ([string]::IsNullOrWhiteSpace($normalizedModel)) {
        $normalizedModel = ConvertTo-ComparableModelName -Text $SystemInformation.Model
    }

    switch ($normalizedManufacturer) {
        'Dell' {
            if (-not [string]::IsNullOrWhiteSpace($systemSkuNormalized)) {
                $match = $rulesForMake | Where-Object { $_.PSObject.Properties['SystemId'] -and $_.SystemId.Trim().ToUpperInvariant() -eq $systemSkuNormalized } | Select-Object -First 1
                if ($match) {
                    WriteLog "DriverMapping: Dell SystemId '$systemSkuNormalized' matched '$($match.Model)'."
                    return $match
                }
            }
            if (-not [string]::IsNullOrWhiteSpace($fallbackSkuNormalized)) {
                $match = $rulesForMake | Where-Object { $_.PSObject.Properties['SystemId'] -and $_.SystemId.Trim().ToUpperInvariant() -eq $fallbackSkuNormalized } | Select-Object -First 1
                if ($match) {
                    WriteLog "DriverMapping: Dell fallback SKU '$fallbackSkuNormalized' matched '$($match.Model)'."
                    return $match
                }
            }
            WriteLog 'DriverMapping: Dell identifiers did not match any entries.'
            return $null
        }
        'HP' {
            if (-not [string]::IsNullOrWhiteSpace($systemSkuNormalized)) {
                $match = $rulesForMake | Where-Object { $_.PSObject.Properties['SystemId'] -and $_.SystemId.Trim().ToUpperInvariant() -eq $systemSkuNormalized } | Select-Object -First 1
                if ($match) {
                    WriteLog "DriverMapping: HP SystemId '$systemSkuNormalized' matched '$($match.Model)'."
                    return $match
                }
            }
            WriteLog 'DriverMapping: HP SystemId not detected or not present in mapping.'
            return $null
        }
        'Lenovo' {
            if (-not [string]::IsNullOrWhiteSpace($machineTypeNormalized)) {
                $match = $rulesForMake | Where-Object { $_.PSObject.Properties['MachineType'] -and $_.MachineType.Trim().ToUpperInvariant() -eq $machineTypeNormalized } | Select-Object -First 1
                if ($match) {
                    WriteLog "DriverMapping: Lenovo MachineType '$machineTypeNormalized' matched '$($match.Model)'."
                    return $match
                }
            }
            WriteLog 'DriverMapping: Lenovo MachineType not detected or not present in mapping.'
            return $null
        }
        'Microsoft' {
            # Prefer System SKU matching for Microsoft/Surface when available.
            if (-not [string]::IsNullOrWhiteSpace($systemSkuNormalized)) {
                foreach ($rule in $rulesForMake) {
                    if ($rule.PSObject.Properties['SystemSku'] -and $null -ne $rule.SystemSku) {
                        foreach ($sku in @($rule.SystemSku)) {
                            if (-not [string]::IsNullOrWhiteSpace($sku) -and $sku.Trim().ToUpperInvariant() -eq $systemSkuNormalized) {
                                WriteLog "DriverMapping: Microsoft SystemSku '$systemSkuNormalized' matched '$($rule.Model)'."
                                return $rule
                            }
                        }
                    }
                }
            }

            # Fallback to model string comparison (legacy behavior).
            foreach ($rule in $rulesForMake) {
                $ruleModelNorm = ConvertTo-ComparableModelName -Text $rule.Model
                if (-not [string]::IsNullOrWhiteSpace($ruleModelNorm) -and $ruleModelNorm -eq $normalizedModel) {
                    WriteLog "DriverMapping: Microsoft model '$normalizedModel' matched '$($rule.Model)'."
                    return $rule
                }
            }
            WriteLog 'DriverMapping: Microsoft model not present in mapping.'
            return $null
        }
        'Panasonic Corporation' {
            if (-not [string]::IsNullOrWhiteSpace($systemSkuNormalized)) {
                $match = $rulesForMake | Where-Object { $_.PSObject.Properties['SystemId'] -and $_.SystemId.Trim().ToUpperInvariant() -eq $systemSkuNormalized } | Select-Object -First 1
                if ($match) {
                    WriteLog "DriverMapping: Panasonic SystemId '$systemSkuNormalized' matched '$($match.Model)'."
                    return $match
                }
            }
            WriteLog 'DriverMapping: Panasonic SystemId not detected or not present in mapping.'
            return $null
        }
        'Viglen' {
            if (-not [string]::IsNullOrWhiteSpace($systemSkuNormalized)) {
                $match = $rulesForMake | Where-Object { $_.PSObject.Properties['SystemId'] -and $_.SystemId.Trim().ToUpperInvariant() -eq $systemSkuNormalized } | Select-Object -First 1
                if ($match) {
                    WriteLog "DriverMapping: Viglen SystemId '$systemSkuNormalized' matched '$($match.Model)'."
                    return $match
                }
            }
            WriteLog 'DriverMapping: Viglen SystemId not detected or not present in mapping.'
            return $null
        }
        'AZW' {
            if (-not [string]::IsNullOrWhiteSpace($systemSkuNormalized)) {
                $match = $rulesForMake | Where-Object { $_.PSObject.Properties['SystemId'] -and $_.SystemId.Trim().ToUpperInvariant() -eq $systemSkuNormalized } | Select-Object -First 1
                if ($match) {
                    WriteLog "DriverMapping: AZW SystemId '$systemSkuNormalized' matched '$($match.Model)'."
                    return $match
                }
            }
            WriteLog 'DriverMapping: AZW SystemId not detected or not present in mapping.'
            return $null
        }
        'Fujitsu' {
            if (-not [string]::IsNullOrWhiteSpace($systemSkuNormalized)) {
                $match = $rulesForMake | Where-Object { $_.PSObject.Properties['SystemId'] -and $_.SystemId.Trim().ToUpperInvariant() -eq $systemSkuNormalized } | Select-Object -First 1
                if ($match) {
                    WriteLog "DriverMapping: Fujitsu SystemId '$systemSkuNormalized' matched '$($match.Model)'."
                    return $match
                }
            }
            WriteLog 'DriverMapping: Fujitsu SystemId not detected or not present in mapping.'
            return $null
        }
        'Getac' {
            if (-not [string]::IsNullOrWhiteSpace($systemSkuNormalized)) {
                $match = $rulesForMake | Where-Object { $_.PSObject.Properties['SystemId'] -and $_.SystemId.Trim().ToUpperInvariant() -eq $systemSkuNormalized } | Select-Object -First 1
                if ($match) {
                    WriteLog "DriverMapping: Getac SystemId '$systemSkuNormalized' matched '$($match.Model)'."
                    return $match
                }
            }
            WriteLog 'DriverMapping: Getac SystemId not detected or not present in mapping.'
            return $null
        }
        'Intel' {
            foreach ($rule in $rulesForMake) {
                if (-not $rule.PSObject.Properties['Model']) { continue }
                $ruleModelNorm = ConvertTo-ComparableModelName -Text $rule.Model
                if (-not [string]::IsNullOrWhiteSpace($ruleModelNorm) -and $ruleModelNorm -eq $normalizedModel) {
                    WriteLog "DriverMapping: Intel model '$normalizedModel' matched '$($rule.Model)'."
                    return $rule
                }
            }
            WriteLog 'DriverMapping: Intel model not detected or not present in mapping.'
            return $null
        }
        'ByteSpeed' {
            foreach ($rule in $rulesForMake) {
                if (-not $rule.PSObject.Properties['Model']) { continue }
                $ruleModelNorm = ConvertTo-ComparableModelName -Text $rule.Model
                if (-not [string]::IsNullOrWhiteSpace($ruleModelNorm) -and $ruleModelNorm -eq $normalizedModel) {
                    WriteLog "DriverMapping: ByteSpeed model '$normalizedModel' matched '$($rule.Model)'."
                    return $rule
                }
            }
            WriteLog 'DriverMapping: ByteSpeed model not detected or not present in mapping.'
            return $null
        }
        default {
            # Generic fallback for manufacturers without explicit handling
            foreach ($rule in $rulesForMake) {
                if (-not $rule.PSObject.Properties['Model']) { continue }
                $ruleModelNorm = ConvertTo-ComparableModelName -Text $rule.Model
                if (-not [string]::IsNullOrWhiteSpace($ruleModelNorm) -and $ruleModelNorm -eq $normalizedModel) {
                    WriteLog "DriverMapping: Manufacturer '$normalizedManufacturer' model '$normalizedModel' matched '$($rule.Model)'."
                    return $rule
                }
            }
            WriteLog "DriverMapping: No generic match found for manufacturer '$normalizedManufacturer' using model '$normalizedModel'."
            return $null
        }
    }
}

function Stop-Script {
    param(
        [string]$Message
    )
    Write-Host "`n"
    if (-not [string]::IsNullOrWhiteSpace($Message)) {
        Write-Error -Message $Message
    }
    WriteLog "Copying dism log to $USBDrive"
    Invoke-Process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" 
    WriteLog "Copying dism log to $USBDrive succeeded"
    Read-Host "Press Enter to exit"
    Exit
}

function ConvertTo-ComparableModelName {
    [CmdletBinding()]
    param(
        [string]$Text
    )
    # Normalize model strings with HP-specific adjustments.
    # Remove inch unit variants (23.8-in, 23.8 inch, 23inch, 23-in, etc.) keeping only the numeric size.
    # Canonicalize All-in-One variants (All in One, All-in-One, All-in-One PC, AiO, AIO) to 'AIO'.
    # Convert any non-alphanumeric sequence to a single space, collapse whitespace, and trim.
    if ($null -eq $Text) { return '' }
    $original = $Text
    # Remove inch unit variants while preserving the numeric size
    $Text = [regex]::Replace($Text, '(?i)(\d+(?:\.\d+)?)(?:\s*[-]?\s*)(?:in|inch)\b', '$1')
    # Canonicalize All-in-One variants
    $Text = [regex]::Replace($Text, '(?i)\bAll[\s-]*in[\s-]*One(?:\s*PC)?\b', 'AIO')
    $Text = [regex]::Replace($Text, '(?i)\bAiO\b', 'AIO')
    # Generic normalization
    $normalized = ($Text -replace '[^A-Za-z0-9]+', ' ')
    $normalized = ($normalized -replace '\s+', ' ').Trim()
    if ($normalized -ne $original) {
        WriteLog "Normalized model string: Original='$original' -> Normalized='$normalized'"
    }
    return $normalized
}
    
function Test-DriverFolderHasInstallableContent {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path
    )
    
    if (-not (Test-Path -Path $Path -PathType Container)) {
        return $false
    }
    
    try {
        $nonWimFile = Get-ChildItem -Path $Path -File -Recurse -ErrorAction Stop | Where-Object {
            $extension = $_.Extension
            if ([string]::IsNullOrWhiteSpace($extension)) {
                return $true
            }
            return $extension.ToLowerInvariant() -ne '.wim'
        } | Select-Object -First 1
    
        if ($nonWimFile) {
            return $true
        }
    
        return $false
    }
    catch {
        WriteLog "Failed to inspect driver folder '$Path': $($_.Exception.Message)"
        return $false
    }
}
    
function Get-AvailableDriveLetter {
    $usedLetters = (Get-PSDrive -PSProvider FileSystem).Name | ForEach-Object { $_.ToUpperInvariant() }
    for ($ascii = [int][char]'Z'; $ascii -ge [int][char]'A'; $ascii--) {
        $candidate = [char]$ascii
        if ($usedLetters -notcontains $candidate) {
            return $candidate
        }
    }
    return $null
}
    
function New-DriverSubstMapping {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$SourcePath
    )
    
    $resolvedPath = (Resolve-Path -Path $SourcePath -ErrorAction Stop).Path
    $driveLetter = Get-AvailableDriveLetter
    if ($null -eq $driveLetter) {
        throw 'No drive letters are available for SUBST mapping.'
    }
    $driveName = "$driveLetter`:"
    $mappedPath = "$driveLetter`:\"
    WriteLog "Mapping driver folder '$resolvedPath' to $driveName with SUBST."
    $escapedPath = $resolvedPath -replace '"', '""'
    $arguments = "/c subst $driveName `"$escapedPath`""
    Invoke-Process -FilePath cmd.exe -ArgumentList $arguments
    return [PSCustomObject]@{
        DriveLetter = $driveLetter
        DriveName   = $driveName
        DrivePath   = $mappedPath
    }
}
    
function Remove-DriverSubstMapping {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$DriveLetter
    )
    
    $driveName = "$DriveLetter`:"
    WriteLog "Removing SUBST drive $driveName"
    try {
        $arguments = "/c subst $driveName /d"
        Invoke-Process -FilePath cmd.exe -ArgumentList $arguments
    }
    catch {
        WriteLog "Failed to remove SUBST drive $($driveName): $_"
    }
}
        
#Get USB Drive and create log file
$LogFileName = 'ScriptLog.txt'
$USBDrive = Get-USBDrive
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
$LogFile = $USBDrive + $LogFilename
$version = '2608.1'
WriteLog 'Begin Logging'
WriteLog "Script version: $version"

# Display banner and version
$banner = @"

███████╗███████╗██╗   ██╗    ██████╗ ██╗   ██╗██╗██╗     ██████╗ ███████╗██████╗ 
██╔════╝██╔════╝██║   ██║    ██╔══██╗██║   ██║██║██║     ██╔══██╗██╔════╝██╔══██╗
█████╗  █████╗  ██║   ██║    ██████╔╝██║   ██║██║██║     ██║  ██║█████╗  ██████╔╝
██╔══╝  ██╔══╝  ██║   ██║    ██╔══██╗██║   ██║██║██║     ██║  ██║██╔══╝  ██╔══██╗
██║     ██║     ╚██████╔╝    ██████╔╝╚██████╔╝██║███████╗██████╔╝███████╗██║  ██║
╚═╝     ╚═╝      ╚═════╝     ╚═════╝  ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝  ╚═╝
                                                                                                                                                                
"@
Write-Host $banner -ForegroundColor Cyan
Write-Host "Version $version" -ForegroundColor Cyan

#Find PhysicalDrive
Write-SectionHeader -Title 'Target Disk Selection'
$diskDriveCandidates = @(Get-HardDrive)
$diskCount = $diskDriveCandidates.Count
if ($diskCount -eq 0) {
    $errorMessage = 'No hard drive found. You may need to add storage drivers to the WinPE image.'
    WriteLog ($errorMessage + ' Exiting.')
    WriteLog 'To add drivers, place them in the PEDrivers folder and re-run the creation script with -CopyPEDrivers $true, or add them manually via DISM.'
    Stop-Script -Message $errorMessage
}

# Select target disk - prompt user if multiple disks found
if ($diskCount -eq 1) {
    $selectedDisk = $diskDriveCandidates[0]
    WriteLog "Single fixed disk detected: DiskNumber=$($selectedDisk.Index), Model=$($selectedDisk.Model)"
    Write-Host "Single fixed disk detected: $($selectedDisk.Model)"
}
else {
    WriteLog "Found $diskCount fixed disks. Prompting for selection."
    Write-Host "Found $diskCount fixed disks"
    
    # Build list of available disk indexes for validation
    $validDiskIndexes = @($diskDriveCandidates | ForEach-Object { $_.Index })
    
    # Display disk list using actual disk index as the selection value
    $displayList = @()
    foreach ($currentDisk in $diskDriveCandidates) {
        $sizeGB = [math]::Round(($currentDisk.Size / 1GB), 2)
        $displayList += [PSCustomObject]@{
            Disk        = $currentDisk.Index
            'Size (GB)' = $sizeGB
            'Sector'    = $currentDisk.BytesPerSector
            'Bus Type'  = $currentDisk.InterfaceType
            Model       = $currentDisk.Model
        }
    }
    $displayList | Format-Table -AutoSize -Property Disk, 'Size (GB)', Sector, 'Bus Type', Model

    $diskSelection = Read-MenuSelection -Prompt 'Enter the disk number to apply the FFU to' -InvalidInputMessage 'Invalid disk number. Please select from the available disks.' -ValidSelections $validDiskIndexes

    $selectedDisk = $diskDriveCandidates | Where-Object { $_.Index -eq $diskSelection }
    WriteLog "Disk selection: DiskNumber=$($selectedDisk.Index), Model=$($selectedDisk.Model), SizeGB=$([math]::Round(($selectedDisk.Size / 1GB), 2)), BusType=$($selectedDisk.InterfaceType)"
    Write-Host "`nDisk $($selectedDisk.Index) selected: $($selectedDisk.Model)"
}

# Set variables from selected disk
$PhysicalDeviceID = $selectedDisk.DeviceID
$BytesPerSector = $selectedDisk.BytesPerSector
$DiskID = $selectedDisk.Index
$diskSizeGB = [math]::Round(($selectedDisk.Size / 1GB), 2)

# Create hardDrive object for Get-SystemInformation compatibility
$hardDrive = [PSCustomObject]@{
    DeviceID       = $PhysicalDeviceID
    BytesPerSector = $BytesPerSector
    DiskSize       = $selectedDisk.Size
    DiskNumber     = $DiskID
}

WriteLog "Physical DeviceID is $PhysicalDeviceID"
WriteLog "DiskNumber is $DiskID with size $diskSizeGB GB"

# Gather and write system information
$sysInfoObject = Get-SystemInformation -HardDrive $hardDrive
Write-SystemInformation -SystemInformation $sysInfoObject

#Find FFU Files
Write-SectionHeader 'FFU File Selection'
[array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu)
$FFUCount = $FFUFiles.Count

#If multiple FFUs found, ask which to install
If ($FFUCount -gt 1) {
    WriteLog "Found $FFUCount FFU Files"
    Write-Host "Found $FFUCount FFU Files"
    $array = @()

    for ($i = 0; $i -le $FFUCount - 1; $i++) {
        $Properties = [ordered]@{Number = $i + 1 ; FFUFile = $FFUFiles[$i].FullName }
        $array += New-Object PSObject -Property $Properties
    }
    $array | Format-Table -AutoSize -Property Number, FFUFile | Out-Host
    $FFUSelected = Read-MenuSelection -Prompt 'Enter the FFU number to install' -InvalidInputMessage 'Input was not in correct format. Please enter a valid FFU number.' -Minimum 1 -Maximum $FFUCount
    $FFUSelected = $FFUSelected - 1

    $FFUFileToInstall = $array[$FFUSelected].FFUFile
    WriteLog "$FFUFileToInstall was selected"
}
elseif ($FFUCount -eq 1) {
    WriteLog "Found $FFUCount FFU File"
    Write-Host "Found $FFUCount FFU File"
    $FFUFileToInstall = $FFUFiles[0].FullName
    WriteLog "$FFUFileToInstall will be installed"
    Write-Host "$FFUFileToInstall will be installed"
} 
else {
    $errorMessage = 'No FFU files found.'
    Writelog $errorMessage
    Stop-Script -Message $errorMessage
}

#FindAP
$APFolder = $USBDrive + "Autopilot\"
If (Test-Path -Path $APFolder) {
    [array]$APFiles = @(Get-ChildItem -Path $APFolder*.json)
    $APFilesCount = $APFiles.Count
    if ($APFilesCount -ge 1) {
        $autopilot = $true
    }
}


#FindPPKG
$PPKGFolder = $USBDrive + "PPKG\"
if (Test-Path -Path $PPKGFolder) {
    [array]$PPKGFiles = @(Get-ChildItem -Path $PPKGFolder*.ppkg)
    $PPKGFilesCount = $PPKGFiles.Count
    if ($PPKGFilesCount -ge 1) {
        $PPKG = $true
    }
}

#FindUnattend
$UnattendFolder = $USBDrive + "unattend\"
$UnattendSourceFilePath = $UnattendFolder + "unattend.xml"
$UnattendWorkingFilePath = Join-Path -Path $env:TEMP -ChildPath 'unattend.xml'
$UnattendPrefixPath = $UnattendFolder + "prefixes.txt"
$UnattendComputerNamePath = $UnattendFolder + "SerialComputerNames.csv"
If (Test-Path -Path $UnattendSourceFilePath) {
    $UnattendSourceFile = Get-ChildItem -Path $UnattendSourceFilePath
    If ($UnattendSourceFile) {
        try {
            WriteLog "Copying source unattend file $($UnattendSourceFile.FullName) to temporary working file $UnattendWorkingFilePath"
            Copy-Item -Path $UnattendSourceFile.FullName -Destination $UnattendWorkingFilePath -Force -ErrorAction Stop
            $UnattendFile = Get-ChildItem -Path $UnattendWorkingFilePath -ErrorAction Stop
            WriteLog "Using temporary unattend working file $($UnattendFile.FullName)"
            $Unattend = $true
        }
        catch {
            WriteLog "Copying source unattend file to temporary working file failed with error: $_"
            Stop-Script -Message "Copying source unattend file to temporary working file failed with error: $_"
        }
    }
}
If (Test-Path -Path $UnattendPrefixPath) {
    $UnattendPrefixFile = Get-ChildItem -Path $UnattendPrefixPath
    If ($UnattendPrefixFile) {
        $UnattendPrefix = $true
    }
}
If (Test-Path -Path $UnattendComputerNamePath) {
    $UnattendComputerNameFile = Get-ChildItem -Path $UnattendComputerNamePath
    If ($UnattendComputerNameFile) {
        $UnattendComputerName = $true
    }
}

$UnattendConfiguredComputerName = $null
$RequiresLegacyDeviceNamePrompt = $false
$RequiresTemplateDeviceName = $false
if ($Unattend) {
    $UnattendConfiguredComputerName = Get-UnattendComputerNameValue
    $RequiresLegacyDeviceNamePrompt = Test-LegacyPromptComputerName($UnattendConfiguredComputerName)
    if (-not [string]::IsNullOrWhiteSpace($UnattendConfiguredComputerName) -and $UnattendConfiguredComputerName -match '(?i)%serial%') {
        $RequiresTemplateDeviceName = $true
    }
}

#Ask for device name if naming is explicitly required
If ($UnattendPrefix -or $UnattendComputerName -or $RequiresTemplateDeviceName -or $RequiresLegacyDeviceNamePrompt) {
    Write-SectionHeader 'Device Name Selection'
    if ($Unattend -and $UnattendPrefix) {
        Writelog 'Unattend file found with prefixes.txt. Getting prefixes.'
        $UnattendPrefixes = @(Get-content $UnattendPrefixFile)
        $UnattendPrefixCount = $UnattendPrefixes.Count
        $skipPrefixSelection = $false
        $PrefixToUse = $null
        If ($UnattendPrefixCount -gt 1) {
            WriteLog "Found $UnattendPrefixCount Prefixes"
            $array = @()
            for ($i = 0; $i -le $UnattendPrefixCount - 1; $i++) {
                $Properties = [ordered]@{Number = $i + 1 ; DeviceNamePrefix = $UnattendPrefixes[$i] }
                $array += New-Object PSObject -Property $Properties
            }
            $array | Format-Table -AutoSize -Property Number, DeviceNamePrefix
            $prefixSelection = Read-MenuSelection -Prompt 'Enter the prefix number to use for the device name (0 to skip)' -InvalidInputMessage 'Input was not in correct format. Please enter 0 to skip or a valid prefix number.' -Minimum 1 -Maximum $UnattendPrefixCount -AllowSkip
            if ($prefixSelection -eq 0) {
                $skipPrefixSelection = $true
                WriteLog 'User chose to skip device name prefix selection. Existing unattend computer name will remain unchanged.'
                Write-Host "`nDevice name prefix selection was skipped. The existing unattend computer name will remain unchanged."
            }
            else {
                $PrefixSelected = $prefixSelection - 1
                $PrefixToUse = $array[$PrefixSelected].DeviceNamePrefix
                WriteLog "$PrefixToUse was selected"
                Write-Host "`n$PrefixToUse was selected as device name prefix"
            }
        }
        elseif ($UnattendPrefixCount -eq 1) {
            WriteLog "Found $UnattendPrefixCount Prefix"
            Write-Host "Found $UnattendPrefixCount Prefix"
            $PrefixToUse = $UnattendPrefixes[0]
            WriteLog "Will use $PrefixToUse as device name prefix"
            Write-Host "Will use $PrefixToUse as device name prefix"
        }
        if (-not $skipPrefixSelection) {
            $serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim()
            $computername = Set-ConfiguredComputerName($PrefixToUse + $serial)
        }
    }
    elseif ($Unattend -and $UnattendComputerName) {
        Writelog 'Unattend file found with SerialComputerNames.csv. Getting name for current computer.'
        $SerialComputerNames = Import-Csv -Path $UnattendComputerNameFile.FullName -Delimiter ","

        $SerialNumber = (Get-CimInstance -Class Win32_Bios).SerialNumber
        $SCName = $SerialComputerNames | Where-Object { $_.SerialNumber -eq $SerialNumber }

        If ($SCName) {
            [string]$computername = Set-ConfiguredComputerName($SCName.ComputerName)
        }
        else {
            Writelog 'No matching serial number found in SerialComputerNames.csv. Setting random computer name to complete setup.'
            Write-Host 'No matching serial number found in SerialComputerNames.csv. Setting random computer name to complete setup.'
            [string]$computername = Set-ConfiguredComputerName(("FFU-" + ( -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 11 | ForEach-Object { [char]$_ }))))
        }
    }
    elseif ($Unattend -and $RequiresTemplateDeviceName) {
        Writelog 'Unattend file found with a %serial% computer name template. Resolving the template.'
        $serialNumber = (Get-CimInstance -ClassName Win32_Bios).SerialNumber.Trim()
        [string]$computername = Set-ConfiguredComputerName((Resolve-ComputerNameTemplate -computerNameTemplate $UnattendConfiguredComputerName -serialNumber $serialNumber))
    }
    elseif ($Unattend -and $RequiresLegacyDeviceNamePrompt) {
        Writelog 'Unattend file found with no prefixes.txt, asking for name'
        Write-Host 'Unattend file found but no prefixes.txt. Please enter a device name.'
        [string]$computername = Set-ConfiguredComputerName((Read-Host 'Enter device name'))
    }
    else {
        WriteLog 'Device naming assets detected without unattend.xml. Skipping device naming prompts.'
    }
}
elseif ($Unattend) {
    WriteLog 'Unattend file found. Device naming is not required, but unattend settings will still be applied.'
}
else {
    WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.'
}

#If both AP and PPKG folder found with files, ask which to use.
If ($autopilot -eq $true -and $PPKG -eq $true) {
    WriteLog 'Both PPKG and Autopilot json files found'
    Write-Host 'Both Autopilot JSON files and Provisioning packages were found.'
    $APorPPKG = Read-MenuSelection -Prompt 'Enter 1 for Autopilot or 2 for Provisioning Package' -InvalidInputMessage 'Incorrect value. Please enter 1 for Autopilot or 2 for Provisioning Package.' -Minimum 1 -Maximum 2
    If ($APorPPKG -eq 1) {
        $PPKG = $false
    }
    else {
        $autopilot = $false
    } 
}

#If multiple AP json files found, ask which to install
If ($APFilesCount -gt 1 -and $autopilot -eq $true) {
    WriteLog "Found $APFilesCount Autopilot json Files"
    $array = @()

    for ($i = 0; $i -le $APFilesCount - 1; $i++) {
        $Properties = [ordered]@{Number = $i + 1 ; APFile = $APFiles[$i].FullName; APFileName = $APFiles[$i].Name }
        $array += New-Object PSObject -Property $Properties
    }
    $array | Format-Table -AutoSize -Property Number, APFileName
    $APFileSelection = Read-MenuSelection -Prompt 'Enter the AP json file number to install (0 to skip)' -InvalidInputMessage 'Input was not in correct format. Please enter 0 to skip or a valid AP json file number.' -Minimum 1 -Maximum $APFilesCount -AllowSkip

    if ($APFileSelection -eq 0) {
        $APFileToInstall = $null
        $APFileName = $null
        WriteLog 'User chose to skip Autopilot JSON selection.'
        Write-Host "`nAutopilot JSON selection was skipped."
    }
    else {
        $APFileSelected = $APFileSelection - 1
        $APFileToInstall = $array[$APFileSelected].APFile
        $APFileName = $array[$APFileSelected].APFileName
        WriteLog "$APFileToInstall was selected"
    }
}
elseif ($APFilesCount -eq 1 -and $autopilot -eq $true) {
    WriteLog "Found $APFilesCount AP File"
    $APFileToInstall = $APFiles[0].FullName
    $APFileName = $APFiles[0].Name
    WriteLog "$APFileToInstall will be copied"
} 
else {
    Writelog 'No AP files found or AP was not selected'
}

#If multiple PPKG files found, ask which to install
If ($PPKGFilesCount -gt 1 -and $PPKG -eq $true) {
    Write-SectionHeader -Title 'Provisioning Package Selection'
    WriteLog "Found $PPKGFilesCount PPKG Files"
    $array = @()

    for ($i = 0; $i -le $PPKGFilesCount - 1; $i++) {
        $Properties = [ordered]@{Number = $i + 1 ; PPKGFile = $PPKGFiles[$i].FullName; PPKGFileName = $PPKGFiles[$i].Name }
        $array += New-Object PSObject -Property $Properties
    }
    $array | Format-Table -AutoSize -Property Number, PPKGFileName
    $PPKGFileSelection = Read-MenuSelection -Prompt 'Enter the PPKG file number to install (0 to skip)' -InvalidInputMessage 'Input was not in correct format. Please enter 0 to skip or a valid PPKG file number.' -Minimum 1 -Maximum $PPKGFilesCount -AllowSkip

    if ($PPKGFileSelection -eq 0) {
        $PPKGFileToInstall = $null
        WriteLog 'User chose to skip Provisioning Package selection.'
        Write-Host "`nProvisioning Package selection was skipped."
    }
    else {
        $PPKGFileSelected = $PPKGFileSelection - 1
        $PPKGFileToInstall = $array[$PPKGFileSelected].PPKGFile
        WriteLog "$PPKGFileToInstall was selected"
        Write-Host "`n$PPKGFileToInstall will be used"
    }
}
elseif ($PPKGFilesCount -eq 1 -and $PPKG -eq $true) {
    Write-SectionHeader -Title 'Provisioning Package Selection'
    WriteLog "Found $PPKGFilesCount PPKG File"
    Write-Host "Found $PPKGFilesCount PPKG File"
    $PPKGFileToInstall = $PPKGFiles[0].FullName
    WriteLog "$PPKGFileToInstall will be used"
    Write-Host "`n$PPKGFileToInstall will be used"
} 
else {
    Writelog 'No PPKG files found or PPKG not selected.'
}

#Find Drivers
$DriversPath = $USBDrive + "Drivers"
$DriverSourcePath = $null
$DriverSourceType = $null # Will be 'WIM' or 'Folder'
$driverMappingPath = Join-Path -Path $DriversPath -ChildPath "DriverMapping.json"

If (Test-Path -Path $DriversPath) {
    Write-SectionHeader -Title 'Drivers Selection'
}

# --- Automatic Driver Detection using DriverMapping.json ---
if (Test-Path -Path $driverMappingPath -PathType Leaf) {
    WriteLog "DriverMapping.json found at $driverMappingPath. Attempting automatic driver selection."
    Write-Host "DriverMapping.json found. Attempting automatic driver selection."
    try {
        $driverMappings = Get-Content -Path $driverMappingPath | Out-String | ConvertFrom-Json -ErrorAction Stop
        $driverMappings = @($driverMappings) | Where-Object { $null -ne $_ }
        if ($driverMappings.Count -eq 0) {
            throw "DriverMapping.json does not contain any entries."
        }

        if ($null -eq $sysInfoObject) {
            $sysInfoObject = Get-SystemInformation -HardDrive $hardDrive
        }

        $identifierLabelForLog = $null
        $identifierValueForLog = $null
        if ($sysInfoObject.PSObject.Properties['Machine Type'] -and -not [string]::IsNullOrWhiteSpace($sysInfoObject.'Machine Type')) {
            $identifierLabelForLog = 'Machine Type'
            $identifierValueForLog = $sysInfoObject.'Machine Type'
        }
        elseif ($sysInfoObject.PSObject.Properties['System ID'] -and -not [string]::IsNullOrWhiteSpace($sysInfoObject.'System ID')) {
            $identifierLabelForLog = 'System ID'
            $identifierValueForLog = $sysInfoObject.'System ID'
        }
        else {
            $identifierLabelForLog = 'System ID'
            $identifierValueForLog = 'Not Detected'
        }
        WriteLog ("Detected System: Manufacturer='{0}', Model='{1}', {2}='{3}'" -f $sysInfoObject.Manufacturer, $sysInfoObject.Model, $identifierLabelForLog, $identifierValueForLog)
        Write-Host ("Detected System: Manufacturer='{0}', Model='{1}'" -f $sysInfoObject.Manufacturer, $sysInfoObject.Model)

        $matchedRule = Find-DriverMappingRule -SystemInformation $sysInfoObject -DriverMappings $driverMappings

        if ($null -ne $matchedRule) {
            WriteLog "Automatic match found: Manufacturer='$($matchedRule.Manufacturer)', Model='$($matchedRule.Model)'"
            Write-Host "Automatic match found: Manufacturer='$($matchedRule.Manufacturer)', Model='$($matchedRule.Model)'"
            $potentialDriverPath = Join-Path -Path $DriversPath -ChildPath $matchedRule.DriverPath

            if (Test-Path -Path $potentialDriverPath) {
                $DriverSourcePath = $potentialDriverPath
                if ($DriverSourcePath -like '*.wim') {
                    $DriverSourceType = 'WIM'
                }
                else {
                    $DriverSourceType = 'Folder'
                }
                WriteLog "Automatically selected driver source. Type: $DriverSourceType, Path: $DriverSourcePath"
                Write-Host "Automatically selected driver source. Type: $DriverSourceType, Path: $DriverSourcePath"
            }
            else {
                WriteLog "Matched driver path '$potentialDriverPath' not found. Falling back to manual selection."
                Write-Host "Matched driver path '$potentialDriverPath' not found. Falling back to manual selection."
            }
        }
        else {
            WriteLog "No automatic driver mapping rule matched identifiers for this system. Falling back to manual selection."
            Write-Host "No matching driver mapping rule was found for this system. Falling back to manual selection."
        }
    }
    catch {
        WriteLog "An error occurred during automatic driver detection: $($_.Exception.Message). Falling back to manual selection."
        Write-Host "An error occurred during automatic driver detection: $($_.Exception.Message). Falling back to manual selection."
    }
}
else {
    WriteLog "DriverMapping.json not found. Proceeding with manual driver selection."
}

# --- Manual Driver Selection (Fallback) ---
if ($null -eq $DriverSourcePath) {
    If (Test-Path -Path $DriversPath) {
        WriteLog "Searching for driver WIMs and folders in $DriversPath"
    
        # Collect all WIM-based driver sources anywhere under Drivers
        $wimFiles = Get-ChildItem -Path $DriversPath -Filter *.wim -File -Recurse -ErrorAction SilentlyContinue
        
        # Treat each immediate child folder as a manufacturer container (supports known and unknown vendors)
        $manufacturerFolders = Get-ChildItem -Path $DriversPath -Directory -ErrorAction SilentlyContinue
        $driversRootFullPath = (Get-Item -Path $DriversPath).FullName.TrimEnd('\')
        $relativePathResolver = {
            param(
                [string]$candidatePath,
                [string]$rootPath
            )
            try {
                $normalizedPath = [System.IO.Path]::GetFullPath($candidatePath)
                if ($normalizedPath.StartsWith($rootPath, [System.StringComparison]::OrdinalIgnoreCase)) {
                    $relativeSegment = $normalizedPath.Substring($rootPath.Length).TrimStart('\', '/')
                    if ([string]::IsNullOrWhiteSpace($relativeSegment)) {
                        return Split-Path -Path $normalizedPath -Leaf
                    }
                    return $relativeSegment
                }
                return $normalizedPath
            }
            catch {
                return $candidatePath
            }
        }

        # Create a combined list
        $DriverSources = @()
        foreach ($wimFile in $wimFiles) {
            $relativePath = & $relativePathResolver -candidatePath $wimFile.FullName -rootPath $driversRootFullPath
            $DriverSources += [PSCustomObject]@{
                Type         = 'WIM'
                Path         = $wimFile.FullName
                RelativePath = $relativePath
            }
        }
        foreach ($manufacturerFolder in $manufacturerFolders) {
            $modelFolders = Get-ChildItem -Path $manufacturerFolder.FullName -Directory -ErrorAction SilentlyContinue

            if ($null -eq $modelFolders -or $modelFolders.Count -eq 0) {
                if (Test-DriverFolderHasInstallableContent -Path $manufacturerFolder.FullName) {
                    $relativePath = & $relativePathResolver -candidatePath $manufacturerFolder.FullName -rootPath $driversRootFullPath
                    $DriverSources += [PSCustomObject]@{
                        Type         = 'Folder'
                        Path         = $manufacturerFolder.FullName
                        RelativePath = $relativePath
                    }
                    WriteLog "Using manufacturer folder '$($manufacturerFolder.FullName)' as a driver source because it contains installable content."
                }
                else {
                    WriteLog "Skipping '$($manufacturerFolder.FullName)' because it has no model folders with installable content."
                }
                continue
            }

            foreach ($modelFolder in $modelFolders) {
                if (-not (Test-DriverFolderHasInstallableContent -Path $modelFolder.FullName)) {
                    WriteLog "Skipping driver folder '$($modelFolder.FullName)' because no installable files were found."
                    continue
                }
                $relativePath = & $relativePathResolver -candidatePath $modelFolder.FullName -rootPath $driversRootFullPath
                $DriverSources += [PSCustomObject]@{
                    Type         = 'Folder'
                    Path         = $modelFolder.FullName
                    RelativePath = $relativePath
                }
            }
        }

        $DriverSourcesCount = $DriverSources.Count

        if ($DriverSourcesCount -gt 0) {
            WriteLog "Found $DriverSourcesCount total driver sources (WIMs and folders)."
            if ($DriverSourcesCount -eq 1) {
                $DriverSourcePath = $DriverSources[0].Path
                $DriverSourceType = $DriverSources[0].Type
                $selectedRelativePath = $DriverSources[0].RelativePath
                WriteLog "Single driver source found. Type: $DriverSourceType, Path: $DriverSourcePath, RelativePath: $selectedRelativePath"
                Write-Host "Single driver source found. Type: $DriverSourceType, RelativePath: $selectedRelativePath"
            }
            else {
                # Multiple sources found, prompt user
                WriteLog "Multiple driver sources found. Prompting for selection."
                $displayArray = @()
                for ($i = 0; $i -lt $DriverSourcesCount; $i++) {
                    $displayArray += [PSCustomObject]@{
                        Number       = $i + 1
                        Type         = $DriverSources[$i].Type
                        RelativePath = $DriverSources[$i].RelativePath
                        Path         = $DriverSources[$i].Path
                    }
                }
                $displayArray | Format-Table -Property Number, Type, RelativePath -AutoSize

                $DriverSelected = -1
                $skipDriverInstall = $false
                $userSelection = Read-MenuSelection -Prompt 'Enter the number of the driver source to install (0 to skip)' -InvalidInputMessage 'Input was not in correct format. Please enter 0 to skip or a valid number.' -Minimum 1 -Maximum $DriverSourcesCount -AllowSkip
                if ($userSelection -eq 0) {
                    $skipDriverInstall = $true
                }
                else {
                    $DriverSelected = $userSelection - 1
                }

                if ($skipDriverInstall) {
                    $DriverSourcePath = $null
                    $DriverSourceType = $null
                    $selectedRelativePath = $null
                    WriteLog 'User chose to skip driver installation.'
                    Write-Host "`nDriver installation was skipped."
                }
                else {
                    $DriverSourcePath = $DriverSources[$DriverSelected].Path
                    $DriverSourceType = $DriverSources[$DriverSelected].Type
                    $selectedRelativePath = $DriverSources[$DriverSelected].RelativePath
                    WriteLog "User selected Type: $DriverSourceType, Path: $DriverSourcePath, RelativePath: $selectedRelativePath"
                    Write-Host "`nUser selected Type: $DriverSourceType, RelativePath: $selectedRelativePath"
                }
            }
        }
        else {
            WriteLog "No driver WIMs or folders found in Drivers directory."
            Write-Host "No driver WIMs or folders found in Drivers directory."
        }
    }
    else {
        WriteLog "Drivers folder not found at $DriversPath. Skipping driver installation."
    }
}
#Partition drive
Writelog 'Clean Disk'
$originalProgressPreference = $ProgressPreference
try {
    $ProgressPreference = 'SilentlyContinue'
    $Disk = Get-Disk -Number $DiskID
    if ($Disk.PartitionStyle -ne "RAW") {
        $Disk | clear-disk -RemoveData -RemoveOEM -Confirm:$false
    }
}
catch {
    WriteLog 'Cleaning disk failed. Exiting'
    throw $_
}
finally {
    $ProgressPreference = $originalProgressPreference
}

Writelog 'Cleaning Disk succeeded'

#Apply FFU
Write-SectionHeader -Title 'Applying FFU'
WriteLog "Applying FFU to $PhysicalDeviceID"
WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID"
#In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen.
dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID
$dismExitCode = $LASTEXITCODE

if ($dismExitCode -ne 0) {
    $errorMessage = "Failed to apply FFU. LastExitCode = $dismExitCode."
    if ($dismExitCode -eq 1393) {
        WriteLog "Failed to apply FFU - LastExitCode = $dismExitCode"
        WriteLog "This is likely due to a mismatched LogicalSectorSizeBytes"
        WriteLog "BytesPerSector value from Win32_Diskdrive is $BytesPerSector"
        if ($BytesPerSector -eq 4096) {
            WriteLog "The FFU build process by default uses a 512 LogicalSectorSizeBytes. Rebuild the FFU by adding -LogicalSectorSizeBytes 4096 to the command line"
        }
        elseif ($BytesPerSector -eq 512) {
            WriteLog "This FFU was likely built with a LogicalSectorSizeBytes of 4096. Rebuild the FFU by adding -LogicalSectorSizeBytes 512 to the command line"
        }
        $errorMessage += " This is likely due to a mismatched logical sector size. Check logs for details."
    }
    else {
        Writelog "Failed to apply FFU - LastExitCode = $dismExitCode also check dism.log on the USB drive for more info"
        $errorMessage += " Check dism.log on the USB drive for more info."
    }
    Stop-Script -Message $errorMessage
}

WriteLog 'Successfully applied FFU'

function Get-WindowsPartitionFromAppliedDisk {
    param(
        [Parameter(Mandatory)]
        [int]$DiskNumber
    )

    $basicDataPartitions = @(Get-Partition -DiskNumber $DiskNumber | Where-Object { $_.GptType -eq '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}' })
    foreach ($partition in $basicDataPartitions) {
        if ([string]::IsNullOrWhiteSpace($partition.DriveLetter)) { continue }
        $volume = Get-Volume -DriveLetter $partition.DriveLetter -ErrorAction SilentlyContinue
        if ($null -ne $volume -and $volume.FileSystemLabel -eq 'Windows') {
            return $partition
        }
    }

    return $basicDataPartitions | Select-Object -First 1
}

# Verify Windows partition exists and assign drive letter
$windowsPartition = Get-WindowsPartitionFromAppliedDisk -DiskNumber $DiskID
if ($null -eq $windowsPartition) {
    $errorMessage = "Windows partition not found after applying FFU, even though DISM reported success."
    WriteLog $errorMessage
    Stop-Script -Message $errorMessage
}

WriteLog "Assigning drive letter 'W' to Windows partition."
Set-Partition -InputObject $windowsPartition -NewDriveLetter W

# Verify the drive letter was set
$windowsVolume = Get-Volume -DriveLetter W -ErrorAction SilentlyContinue
if ($null -eq $windowsVolume) {
    $errorMessage = "Failed to assign drive letter 'W' to the Windows partition after applying FFU."
    WriteLog $errorMessage
    Stop-Script -Message $errorMessage
}
WriteLog "Successfully assigned drive letter 'W'."

$dataPartitionDriveLetterDeploymentContext = $null
try {
    $dataPartitionDriveLetterDeploymentContext = Get-FFUDataPartitionDriveLetterDeploymentContext -WindowsPartitionRoot 'W:\'
}
catch {
    WriteLog "Validating data partition drive-letter deployment artifacts failed with error: $_"
    Stop-Script -Message "Validating data partition drive-letter deployment artifacts failed with error: $_"
}

$recoveryPartition = Get-Partition -DiskNumber $DiskID | Where-Object Type -eq Recovery | Select-Object -First 1
if ($recoveryPartition) {
    WriteLog 'Setting recovery partition attributes'
    $diskpartScript = @(
        "SELECT DISK $($Disk.Number)",
        "SELECT PARTITION $($recoveryPartition.PartitionNumber)",
        "GPT ATTRIBUTES=0x8000000000000001",
        "EXIT"
    )
    $diskpartScript | diskpart.exe | Out-Null
    WriteLog 'Setting recovery partition attributes complete'
}

#Copy modified WinRE if folder exists, else copy inbox WinRE
$WinRE = $USBDrive + "WinRE\winre.wim"
If (Test-Path -Path $WinRE) {
    WriteLog 'Copying modified WinRE to Recovery directory'
    if ($null -eq $recoveryPartition) {
        $errorMessage = 'Recovery partition not found after applying FFU. Cannot copy modified WinRE.'
        WriteLog $errorMessage
        Stop-Script -Message $errorMessage
    }
    Set-Partition -InputObject $recoveryPartition -NewDriveLetter R
    Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y"
    WriteLog 'Copying WinRE to Recovery directory succeeded'
    WriteLog 'Registering location of recovery tools'
    Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows"
    Remove-PartitionAccessPath -InputObject $recoveryPartition -AccessPath R:
    WriteLog 'Registering location of recovery tools succeeded'
}
#Autopilot JSON
If ($APFileToInstall) {
    Write-SectionHeader -Title 'Applying Autopilot Configuration'
    WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot"
    Invoke-process xcopy.exe "$APFileToInstall W:\Windows\provisioning\autopilot\"
    WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot succeeded"
    # Rename file in W:\Windows\Provisioning\Autopilot to AutoPilotConfigurationFile.json
    try {
        Rename-Item -Path "W:\Windows\Provisioning\Autopilot\$APFileName" -NewName 'W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json'
        WriteLog "Renamed W:\Windows\Provisioning\Autopilot\$APFilename to W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json"
    }
    
    catch {
        Writelog "Copying $APFileToInstall to W:\windows\provisioning\autopilot failed with error: $_"
        throw $_
    }
}
#Apply PPKG
If ($PPKGFileToInstall) {
    Write-SectionHeader -Title 'Applying Provisioning Package'
    try {
        #Make sure to delete any existing PPKG on the USB drive
        Get-Childitem -Path $USBDrive\*.ppkg | ForEach-Object {
            Remove-item -Path $_.FullName
        }
        WriteLog "Copying $PPKGFileToInstall to $USBDrive"
        Write-Host "Copying $PPKGFileToInstall to $USBDrive"
        # Quote paths to handle PPKG filenames with spaces
        Invoke-process xcopy.exe """$PPKGFileToInstall"" ""$USBDrive"""
        WriteLog "Copying $PPKGFileToInstall to $USBDrive succeeded"
        Write-Host "Copying $PPKGFileToInstall to $USBDrive succeeded"
    }

    catch {
        Writelog "Copying $PPKGFileToInstall to $USBDrive failed with error: $_"
        Write-Host "Copying $PPKGFileToInstall to $USBDrive failed with error: $_"
        throw $_
    }
}
#Set DeviceName
If ($Unattend) {
    $unattendSectionTitle = if ($computername) { 'Applying Computer Name and Unattend Configuration' } else { 'Applying Unattend Configuration' }
    Write-SectionHeader -Title $unattendSectionTitle
    try {
        $PantherDir = 'w:\windows\panther'
        If (Test-Path -Path $PantherDir) {
            Writelog "Copying $UnattendFile to $PantherDir"
            Write-Host "Copying $UnattendFile to $PantherDir"
            Invoke-process xcopy "$UnattendFile $PantherDir /Y"
            WriteLog "Copying $UnattendFile to $PantherDir succeeded"
            Write-Host "Copying $UnattendFile to $PantherDir succeeded"
        }
        else {
            Writelog "$PantherDir doesn't exist, creating it"
            New-Item -Path $PantherDir -ItemType Directory -Force
            Writelog "Copying $UnattendFile to $PantherDir"
            Write-Host "Copying $UnattendFile to $PantherDir"
            Invoke-Process xcopy.exe "$UnattendFile $PantherDir"
            WriteLog "Copying $UnattendFile to $PantherDir succeeded"
            Write-Host "Copying $UnattendFile to $PantherDir succeeded"
        }
    }
    catch {
        WriteLog 'Copying Unattend.xml to Panther failed'
        Stop-Script -Message "Copying Unattend.xml to Panther failed with error: $_"
    }   
}

if ($null -ne $dataPartitionDriveLetterDeploymentContext) {
    $deploymentUnattendPath = 'W:\Windows\Panther\Unattend.xml'
    try {
        if ($Unattend) {
            WriteLog "Merging data partition drive-letter assignment into custom unattend at $deploymentUnattendPath."
            Add-FFUDataPartitionDriveLetterCommandToAppliedUnattend -UnattendPath $deploymentUnattendPath -ProcessorArchitecture $dataPartitionDriveLetterDeploymentContext.ProcessorArchitecture
        }
        else {
            WriteLog "No custom unattend was supplied. Verifying the FFU-embedded data partition drive-letter command at $deploymentUnattendPath."
        }

        Test-FFUDataPartitionDriveLetterCommandInAppliedUnattend -UnattendPath $deploymentUnattendPath -ProcessorArchitecture $dataPartitionDriveLetterDeploymentContext.ProcessorArchitecture
    }
    catch {
        WriteLog "Preparing data partition drive-letter deployment hook failed with error: $_"
        Stop-Script -Message "Preparing data partition drive-letter deployment hook failed with error: $_"
    }
}

# Add Drivers
if ($null -ne $DriverSourcePath) {
    Write-SectionHeader -Title 'Installing Drivers'
    if ($DriverSourceType -eq 'WIM') {
        WriteLog "Installing drivers from WIM: $DriverSourcePath"
        Write-Host "Installing drivers from WIM: $DriverSourcePath"
        $TempDriverDir = "W:\TempDrivers"
        try {
            # Create working folder for WIM-based drivers
            WriteLog "Creating temporary directory for drivers at $TempDriverDir"
            New-Item -Path $TempDriverDir -ItemType Directory -Force | Out-Null
            
            # Mount the driver WIM read-only so DISM can recurse the extracted INF tree
            WriteLog "Mounting WIM contents to $TempDriverDir"
            Write-Host "Mounting WIM contents to $TempDriverDir"
            # For some reason can't use /mount-image with invoke-process, so using dism.exe directly
            dism.exe /Mount-Image /ImageFile:$DriverSourcePath /Index:1 /MountDir:$TempDriverDir /ReadOnly /optimize
            $mountExitCode = $LASTEXITCODE
            if ($mountExitCode -ne 0) {
                throw "DISM WIM mount failed. LastExitCode = $mountExitCode."
            }
            WriteLog "WIM mount successful."

            # Inject drivers into the offline Windows image; failures here should not stop deployment
            WriteLog "Injecting drivers from $TempDriverDir"
            Write-Host "Injecting drivers from $TempDriverDir"
            Write-Host "This may take a while, please be patient."
            $driverInjectExitCode = Invoke-Process -FilePath dism.exe -ArgumentList "/image:W:\ /Add-Driver /Driver:""$TempDriverDir"" /Recurse" -IgnoreExitCode -PassThruExitCode
            if ($driverInjectExitCode -ne 0) {
                $warningMessage = "Warning: One or more drivers failed to inject from WIM. ExitCode = $driverInjectExitCode. Continuing deployment."
                WriteLog $warningMessage
                Write-Host $warningMessage -ForegroundColor Yellow

                # Copy setupapi.offline.log to the USB drive when driver injection fails

                $setupApiLogPath = 'W:\Windows\INF\setupapi.offline.log'
                if (Test-Path -Path $setupApiLogPath) {
                    try {
                        Invoke-Process xcopy.exe """$setupApiLogPath"" ""$USBDrive"" /Y"
                    }
                    catch {
                        WriteLog "Warning: Failed to copy setupapi.offline.log to $USBDrive. "
                    }
                }
                else {
                    WriteLog "Warning: setupapi.offline.log not found at $setupApiLogPath"
                }
            }
            else {
                WriteLog "Driver injection from WIM succeeded."
                Write-Host "Driver injection from WIM succeeded."
            }
        }
        catch {
            $warningMessage = "Warning: An error occurred during WIM driver installation. Continuing deployment."
            WriteLog $warningMessage
            Write-Host $warningMessage -ForegroundColor Yellow

            # Copy troubleshooting logs to the USB drive when driver installation fails
            try {
                Invoke-Process cmd.exe "/c copy /Y ""X:\Windows\logs\dism\dism.log"" ""$($USBDrive)dism_driverinject.log"""
            }
            catch {
                WriteLog "Warning: Failed to copy dism.log to $USBDrive."
            }

            $setupApiLogPath = 'W:\Windows\INF\setupapi.offline.log'
            if (Test-Path -Path $setupApiLogPath) {
                try {
                    Invoke-Process xcopy.exe """$setupApiLogPath"" ""$USBDrive"" /Y"
                }
                catch {
                    WriteLog "Warning: Failed to copy setupapi.offline.log to $USBDrive."
                }
            }
            else {
                WriteLog "Warning: setupapi.offline.log not found at $setupApiLogPath"
            }
        }
        finally {
            if (Test-Path -Path $TempDriverDir) {
                # Always attempt to unmount and clean up; unmount failures should not stop deployment
                WriteLog "Unmounting WIM from $TempDriverDir"
                Write-Host "Unmounting WIM from $TempDriverDir"
                try {
                    Invoke-Process dism.exe "/Unmount-Image /MountDir:""$TempDriverDir"" /Discard"
                    WriteLog "Unmount successful."
                    Write-Host "Unmount successful."
                }
                catch {
                    $warningMessage = "Warning: Failed to unmount WIM from $TempDriverDir. Continuing cleanup."
                    WriteLog $warningMessage
                    Write-Host $warningMessage -ForegroundColor Yellow
                }

                WriteLog "Cleaning up temporary driver directory: $TempDriverDir"
                Write-Host "Cleaning up temporary driver directory: $TempDriverDir"
                try {
                    Remove-Item -Path $TempDriverDir -Recurse -Force
                    WriteLog "Cleanup successful."
                    Write-Host "Cleanup successful."
                }
                catch {
                    $warningMessage = "Warning: Failed to clean up temporary driver directory: $TempDriverDir."
                    WriteLog $warningMessage
                    Write-Host $warningMessage -ForegroundColor Yellow
                }
            }
        }
    }
    elseif ($DriverSourceType -eq 'Folder') {
        $substMapping = $null
        try {
            # Use SUBST to shorten long paths for DISM /Add-Driver
            $substMapping = New-DriverSubstMapping -SourcePath $DriverSourcePath
            $shortDriverPath = $substMapping.DrivePath
            WriteLog "Injecting drivers from folder via SUBST. Source: $DriverSourcePath, Mapped: $($substMapping.DriveName)"
            Write-Host "Injecting drivers from folder: $shortDriverPath"
            Write-Host "This may take a while, please be patient."

            # Inject drivers into the offline Windows image; failures here should not stop deployment
            $driverInjectExitCode = Invoke-Process -FilePath dism.exe -ArgumentList "/image:W:\ /Add-Driver /Driver:$shortDriverPath /Recurse" -IgnoreExitCode -PassThruExitCode
            if ($driverInjectExitCode -ne 0) {
                $warningMessage = "Warning: One or more drivers failed to inject from folder. ExitCode = $driverInjectExitCode. Continuing deployment."
                WriteLog $warningMessage
                Write-Host $warningMessage -ForegroundColor Yellow

                # Copy setupapi.offline.log to the USB drive when driver injection fails
                $setupApiLogPath = 'W:\Windows\INF\setupapi.offline.log'
                if (Test-Path -Path $setupApiLogPath) {
                    try {
                        Invoke-Process xcopy.exe """$setupApiLogPath"" ""$USBDrive"" /Y"
                    }
                    catch {
                        WriteLog "Warning: Failed to copy setupapi.offline.log to $USBDrive. "
                    }
                }
                else {
                    WriteLog "Warning: setupapi.offline.log not found at $setupApiLogPath"
                }
            }
            else {
                WriteLog "Driver injection from folder succeeded."
                Write-Host "Driver injection from folder succeeded."
            }
        }
        catch {
            $warningMessage = "Warning: An error occurred during folder driver installation. Continuing deployment."
            WriteLog $warningMessage
            Write-Host $warningMessage -ForegroundColor Yellow

            # Copy troubleshooting logs to the USB drive when driver installation fails
            try {
                Invoke-Process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y"
            }
            catch {
                WriteLog "Warning: Failed to copy dism.log to $USBDrive."
            }

            $setupApiLogPath = 'W:\Windows\INF\setupapi.offline.log'
            if (Test-Path -Path $setupApiLogPath) {
                try {
                    Invoke-Process xcopy.exe """$setupApiLogPath"" ""$USBDrive"" /Y"
                }
                catch {
                    WriteLog "Warning: Failed to copy setupapi.offline.log to $USBDrive."
                }
            }
            else {
                WriteLog "Warning: setupapi.offline.log not found at $setupApiLogPath"
            }
        }
        finally {
            # Always attempt to remove SUBST mapping; failures here should not stop deployment
            if ($null -ne $substMapping) {
                try {
                    Remove-DriverSubstMapping -DriveLetter $substMapping.DriveLetter
                }
                catch {
                    $warningMessage = "Warning: Failed to remove SUBST mapping $($substMapping.DriveLetter). Continuing deployment."
                    WriteLog $warningMessage
                    Write-Host $warningMessage -ForegroundColor Yellow
                }
            }
        }
    }
}
else {
    WriteLog "No drivers to install."
}
Write-SectionHeader -Title 'Setting Boot Configuration'
WriteLog "Setting Windows Boot Manager to be first in the firmware display order."
Write-Host "Setting Windows Boot Manager to be first in the firmware display order."
Invoke-Process bcdedit.exe "/set {fwbootmgr} displayorder {bootmgr} /addfirst"
WriteLog "Setting Windows Boot Manager to be first in the default display order."
Write-Host "Setting Windows Boot Manager to be first in the default display order."
Invoke-Process bcdedit.exe "/set {bootmgr} displayorder {default} /addfirst"
#Copy DISM log to USBDrive
WriteLog "Copying dism log to $USBDrive"
invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" 
WriteLog "Copying dism log to $USBDrive succeeded"




