private/steps/5-drivers/step-Add-WindowsDriver-DriverFolder.ps1

<#
.SYNOPSIS
Applies offline drivers from a local driver folder to the Windows image.
 
.DESCRIPTION
Validates the configured driver folder paths and, when present, uses Add-WindowsDriver
to inject all drivers recursively into the offline Windows installation at C:. If no
driver folders are configured or no paths exist, the step exits without error. When
selected removable media changes drive letters, the step re-resolves each saved
driver folder by scanning current file system drives for the same relative path.
 
.PARAMETER DriverFolderPath
Path(s) to folders that contain driver INF files and subfolders. When omitted, values
are read from $global:OSDCloudWorkflowInvoke.DriverFolderPaths and then
$global:OSDCloudWorkflowInvoke.DriverFolderPath for backward compatibility. Legacy
absolute paths are re-resolved by their OSDCloud\Drivers relative suffix when the
original drive letter is no longer valid.
 
.EXAMPLE
step-Add-WindowsDriver-DriverFolder -DriverFolderPath 'D:\DriverPack'
 
Injects drivers from D:\DriverPack into the offline Windows image.
 
.NOTES
Internal workflow step used by OSDCloud deployment tasks.
#>

function step-Add-WindowsDriver-DriverFolder {
    [CmdletBinding()]
    param (
        [System.String[]]
        $DriverFolderPath = @($global:OSDCloudWorkflowInvoke.DriverFolderPaths)
    )
    #=================================================
    $startMessage = "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Start"
    Write-Debug -Message $startMessage; Write-Verbose -Message $startMessage
    #=================================================
    $Error.Clear()
    $logPath = 'C:\Windows\Temp\osdcloud-logs'
    $offlinePath = 'C:\'
    $dismLogPath = Join-Path -Path $logPath -ChildPath 'dism-add-windowsdriver-driverfolder.log'
    $volumeMetadataCache = @{}

    function Get-DriveRootFromPath {
        param(
            [Parameter()]
            [string]$Path
        )

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

        $driveRootMatch = [System.Text.RegularExpressions.Regex]::Match($Path, '^[A-Z]:\\')
        if ($driveRootMatch.Success) {
            return $driveRootMatch.Value
        }

        return $null
    }

    function Get-DriverFolderRelativePath {
        param(
            [Parameter()]
            [string]$Path
        )

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

        $relativePathMatch = [System.Text.RegularExpressions.Regex]::Match($Path, '(?i)(OSDCloud\\Drivers(?:\\.*)?)$')
        if ($relativePathMatch.Success) {
            return $relativePathMatch.Groups[1].Value
        }

        return $null
    }

    function Get-DriveVolumeMetadata {
        param(
            [Parameter()]
            [string]$Path
        )

        $driveRoot = Get-DriveRootFromPath -Path $Path
        if ([string]::IsNullOrWhiteSpace($driveRoot)) {
            return [PSCustomObject]@{
                DriveRoot      = $null
                VolumeLabel    = $null
                VolumeUniqueId = $null
            }
        }

        if ($volumeMetadataCache.ContainsKey($driveRoot)) {
            return $volumeMetadataCache[$driveRoot]
        }

        $volume = $null
        try {
            $volume = Get-Volume -DriveLetter $driveRoot.Substring(0, 1) -ErrorAction Stop
        }
        catch {
            $volume = $null
        }

        $volumeMetadata = [PSCustomObject]@{
            DriveRoot      = $driveRoot
            VolumeLabel    = if ($volume) { [string]$volume.FileSystemLabel } else { $null }
            VolumeUniqueId = if ($volume) { [string]$volume.UniqueId } else { $null }
        }

        $volumeMetadataCache[$driveRoot] = $volumeMetadata
        return $volumeMetadata
    }

    function Resolve-DriverFolderPath {
        param(
            [Parameter()]
            $Selection,

            [Parameter()]
            [string]$FallbackPath
        )

        $originalPath = $FallbackPath
        if ($Selection -and -not [string]::IsNullOrWhiteSpace([string]$Selection.Path)) {
            $originalPath = [string]$Selection.Path
        }

        if (-not [string]::IsNullOrWhiteSpace($originalPath) -and (Test-Path -LiteralPath $originalPath -PathType Container)) {
            return $originalPath
        }

        $relativePath = $null
        if ($Selection -and -not [string]::IsNullOrWhiteSpace([string]$Selection.RelativePath)) {
            $relativePath = [string]$Selection.RelativePath
        }
        if ([string]::IsNullOrWhiteSpace($relativePath)) {
            $relativePath = Get-DriverFolderRelativePath -Path $originalPath
        }

        if ([string]::IsNullOrWhiteSpace($relativePath)) {
            Write-Warning "[$(Get-Date -format s)] Unable to determine a relative driver folder path for $originalPath"
            return $null
        }

        $candidatePaths = @(
            Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root -match '^[A-Z]:\\$' } | ForEach-Object {
                $candidatePath = Join-Path -Path $_.Root -ChildPath $relativePath
                if (Test-Path -LiteralPath $candidatePath -PathType Container) {
                    $candidatePath
                }
            } | Sort-Object -Unique
        )

        if (-not $candidatePaths -or $candidatePaths.Count -eq 0) {
            Write-Warning "[$(Get-Date -format s)] DriverFolderPath was not found: $originalPath"
            return $null
        }

        if ($candidatePaths.Count -eq 1) {
            return $candidatePaths[0]
        }

        $preferredPaths = @()
        $storedVolumeUniqueId = if ($Selection) { [string]$Selection.VolumeUniqueId } else { $null }
        $storedVolumeLabel = if ($Selection) { [string]$Selection.VolumeLabel } else { $null }

        if (-not [string]::IsNullOrWhiteSpace($storedVolumeUniqueId)) {
            $preferredPaths = @($candidatePaths | Where-Object {
                    [string](Get-DriveVolumeMetadata -Path $_).VolumeUniqueId -eq $storedVolumeUniqueId
                })
        }

        if ($preferredPaths.Count -eq 0 -and -not [string]::IsNullOrWhiteSpace($storedVolumeLabel)) {
            $preferredPaths = @($candidatePaths | Where-Object {
                    [string](Get-DriveVolumeMetadata -Path $_).VolumeLabel -eq $storedVolumeLabel
                })
        }

        if ($preferredPaths.Count -eq 1) {
            return $preferredPaths[0]
        }

        Write-Warning "[$(Get-Date -format s)] Multiple matching driver folders were found for $originalPath. Skipping this folder to avoid selecting the wrong drive."
        return $null
    }

    if (-not $DriverFolderPath -or $DriverFolderPath.Count -eq 0) {
        if (-not [string]::IsNullOrWhiteSpace([string]$global:OSDCloudWorkflowInvoke.DriverFolderPath)) {
            $DriverFolderPath = @([string]$global:OSDCloudWorkflowInvoke.DriverFolderPath)
        }
    }

    $validDriverFolderPaths = @($DriverFolderPath | Where-Object {
            -not [string]::IsNullOrWhiteSpace([string]$_)
        } | Select-Object -Unique)

    if (-not $validDriverFolderPaths -or $validDriverFolderPaths.Count -eq 0) {
        Write-Verbose "[$(Get-Date -format s)] DriverFolderPath is not set. Skipping driver injection."
        return
    }

    $selectionLookup = @{}
    foreach ($selection in @($global:OSDCloudWorkflowInvoke.DriverFolderSelections)) {
        if ($selection -and -not [string]::IsNullOrWhiteSpace([string]$selection.Path)) {
            $selectionLookup[[string]$selection.Path] = $selection
        }
    }

    $driverFolderSelections = @($validDriverFolderPaths | ForEach-Object {
            $driverPath = [string]$_
            if ($selectionLookup.ContainsKey($driverPath)) {
                $selectionLookup[$driverPath]
            }
            else {
                [PSCustomObject]@{
                    Path           = $driverPath
                    RelativePath   = Get-DriverFolderRelativePath -Path $driverPath
                    DriveRoot      = Get-DriveRootFromPath -Path $driverPath
                    VolumeLabel    = $null
                    VolumeUniqueId = $null
                }
            }
        })

    $resolvedDriverFolderPaths = @()
    foreach ($selection in $driverFolderSelections) {
        $originalPath = [string]$selection.Path
        $resolvedPath = Resolve-DriverFolderPath -Selection $selection -FallbackPath $originalPath
        if ([string]::IsNullOrWhiteSpace($resolvedPath)) {
            continue
        }

        if ($resolvedDriverFolderPaths -notcontains $resolvedPath) {
            if ($resolvedPath -ne $originalPath) {
                Write-Verbose "[$(Get-Date -format s)] Resolved driver folder path from $originalPath to $resolvedPath"
            }
            $resolvedDriverFolderPaths += $resolvedPath
        }
    }

    if (-not $resolvedDriverFolderPaths -or $resolvedDriverFolderPaths.Count -eq 0) {
        Write-Verbose "[$(Get-Date -format s)] No valid driver folders were resolved. Skipping driver injection."
        return
    }

    if (-not (Test-Path -LiteralPath $logPath)) {
        New-Item -ItemType Directory -Path $logPath -Force | Out-Null
    }

    foreach ($driverPath in $resolvedDriverFolderPaths) {
        Write-Verbose "[$(Get-Date -format s)] Applying drivers from $driverPath"
        try {
            Add-WindowsDriver -Path $offlinePath -Driver $driverPath -Recurse -ForceUnsigned `
                -LogPath $dismLogPath `
                -ErrorAction Stop | Out-Null
        }
        catch {
            Write-Warning "[$(Get-Date -format s)] Add-WindowsDriver failed for $driverPath. $($_.Exception.Message)"
        }
    }
    #=================================================
    $endMessage = "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] End"
    Write-Verbose -Message $endMessage; Write-Debug -Message $endMessage
    #=================================================
}