public/Add-WindowsPackage.ps1

<#
.SYNOPSIS
Adds one or more packages into install image indexes.
 
.DESCRIPTION
Mounts selected indexes from the install image in media, adds package files, and
commits changes back to the image.
 
.PARAMETER MediaRoot
Path to the root of extracted Windows install media.
 
.PARAMETER PackagePath
Path to a package file (.cab or .msu).
 
.PARAMETER Index
Optional index numbers to service. Defaults to all indexes.
 
.PARAMETER IgnoreCheck
Skips applicability checks.
 
.PARAMETER PreventPending
Skips package additions if the image has pending actions.
 
.PARAMETER MountRoot
Temporary root path used for DISM mount operations.
 
.EXAMPLE
Add-WindowsPackage -MediaRoot 'D:\Work\Media' -PackagePath '.\kb.cab' -Index 1 -Verbose
#>

function Add-WindowsPackage {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [string]
        $MediaRoot,

        [Parameter(Mandatory)]
        [string]
        $PackagePath,

        [int[]]
        $Index,

        [switch]
        $IgnoreCheck,

        [switch]
        $PreventPending,

        [string]
        $MountRoot = (Join-Path $env:TEMP 'SplitterMounts')
    )

    $resolvePackagePathParams = @{
        Path = $PackagePath
    }
    $resolvedPackagePath = Resolve-Path @resolvePackagePathParams

    $getInstallImagePathParams = @{
        MediaRoot = $MediaRoot
    }
    $installImagePath = Get-InstallImagePath @getInstallImagePathParams

    $getWindowsInstallImageParams = @{
        ImagePath = $installImagePath
    }
    $images = Get-WindowsInstallImage @getWindowsInstallImageParams

    if ($Index) {
        $images = $images | Where-Object {
            $_.Index -in $Index
        }
    }

    if (-not $images) {
        throw "No matching image indexes were found to service."
    }

    foreach ($image in $images) {
        $mountPath = Join-Path $MountRoot ("Image{0}" -f $image.Index)

        $newCleanDirectoryParams = @{
            Path = $mountPath
        }
        New-CleanDirectory @newCleanDirectoryParams

        Write-Verbose "Mounting image index $($image.Index) to $mountPath"
        $mountArgs = @(
            '/Mount-Image'
            "/ImageFile:$installImagePath"
            "/Index:$($image.Index)"
            "/MountDir:$mountPath"
            '/CheckIntegrity'
        )

        try {
            $invokeMountProcessParams = @{
                FilePath = 'dism.exe'
                ArgumentList = $mountArgs
                ErrorAction = 'Stop'
            }
            Invoke-NativeProcess @invokeMountProcessParams
        }
        catch {
            throw "Failed to mount image index $($image.Index). $($_.Exception.Message)"
        }

        $shouldCommit = $false
        try {
            $target = "index $($image.Index)"
            if ($PSCmdlet.ShouldProcess($target, 'Add package')) {
                Write-Verbose "Adding package $resolvedPackagePath"
                $addPackageArgs = @(
                    "/Image:$mountPath"
                    '/Add-Package'
                    "/PackagePath:$resolvedPackagePath"
                )

                if ($IgnoreCheck) {
                    $addPackageArgs += '/IgnoreCheck'
                }

                if ($PreventPending) {
                    $addPackageArgs += '/PreventPending'
                }

                try {
                    $invokeAddPackageProcessParams = @{
                        FilePath = 'dism.exe'
                        ArgumentList = $addPackageArgs
                        ErrorAction = 'Stop'
                    }
                    Invoke-NativeProcess @invokeAddPackageProcessParams
                }
                catch {
                    throw "Failed to add package to image index $($image.Index). $($_.Exception.Message)"
                }

                $shouldCommit = $true
            }
        }
        finally {
            if ($shouldCommit) {
                Write-Verbose "Committing mounted image index $($image.Index)"
                $unmountArgs = @(
                    '/Unmount-Image'
                    "/MountDir:$mountPath"
                    '/Commit'
                )
            }
            else {
                Write-Verbose "Discarding mounted image index $($image.Index)"
                $unmountArgs = @(
                    '/Unmount-Image'
                    "/MountDir:$mountPath"
                    '/Discard'
                )
            }

            try {
                $invokeUnmountProcessParams = @{
                    FilePath = 'dism.exe'
                    ArgumentList = $unmountArgs
                    ErrorAction = 'Stop'
                }
                Invoke-NativeProcess @invokeUnmountProcessParams
            }
            catch {
                throw "Failed to unmount image index $($image.Index). $($_.Exception.Message)"
            }
        }
    }
}