Public/Backup/Backup-DiskToFFU.ps1

<#
.SYNOPSIS
Saves a Drive as Full Flash Update Windows Image (FFU)
 
.DESCRIPTION
Saves a Drive as Full Flash Update Windows Image (FFU)
 
.LINK
https://osd.osdeploy.com/module/functions/backup/backup-disktoffu
https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/deploy-windows-using-full-flash-update--ffu
 
.NOTES
21.1.27 Initial Release
#>

function Backup-DiskToFFU {
    [CmdletBinding()]
    param (
        #Disk Number of the Drive to capture
        #Use Get-Disk to get the DiskNumber Property
        [Alias('Number')]
        [ValidateScript({$_ -in (Get-DiskToBackup | Select-Object -ExpandProperty DiskNumber)})]
        [int] $DiskNumber = (Get-DiskToBackup | Select-Object -ExpandProperty DiskNumber -First 1),

        [ValidateScript({$_ -in (Get-DriveForBackupFile | Where-Object {$_.DiskNumber -ne $DiskNumber} | Select-Object -ExpandProperty DriveLetter)})]
        [string] $DestinationDriveLetter = "$(Get-DriveForBackupFile | Where-Object {$_.DiskNumber -ne $DiskNumber} | Select-Object -ExpandProperty DriveLetter -First 1)",
        
        #Windows Image Property: Specifies the name of an image
        [string] $Name = "disk$DiskNumber",

        #Full path to save the Windows Image
        [Alias('ImagePath')]
        [string] $ImageFile = "$($DestinationDriveLetter):\BackupFFU\$(Get-MyComputerManufacturer -Brief)\$(Get-MyComputerModel -Brief)\$(Get-MyBiosSerialNumber -Brief)_$Name.ffu",

        #Windows Image Property: Specifies the description of the image
        [string] $Description = "$(Get-MyComputerManufacturer -Brief) $(Get-MyComputerModel -Brief) $(Get-MyBiosSerialNumber -Brief)",

        #Compression level. Default or None
        [ValidateSet('Default','None')]
        [string] $Compress = 'Default',

        #Executes the capture
        [switch] $Force
    )
    #===================================================================================================
    # Require Admin Rights
    #===================================================================================================
    if ((Get-OSDGather -Property IsAdmin) -eq $false) {
        Write-Warning "$($MyInvocation.MyCommand) requires Admin Rights ELEVATED"
        Break
    }
    #===================================================================================================
    # Enable Verbose
    #===================================================================================================
    if ($Force -eq $false) {$VerbosePreference = 'Continue'}
    #===================================================================================================
    # Gather
    #===================================================================================================
    $GetCommandNoun = Get-Command -Name Backup-DiskToFFU | Select-Object -ExpandProperty Noun
    $GetCommandVersion = Get-Command -Name Backup-DiskToFFU | Select-Object -ExpandProperty Version
    $GetCommandHelpUri = Get-Command -Name Backup-DiskToFFU | Select-Object -ExpandProperty HelpUri
    $GetCommandModule = Get-Command -Name Backup-DiskToFFU | Select-Object -ExpandProperty Module
    $GetModuleDescription = Get-Module -Name $GetCommandModule | Select-Object -ExpandProperty Description
    $GetModuleProjectUri = Get-Module -Name $GetCommandModule | Select-Object -ExpandProperty ProjectUri
    $GetModulePath = Get-Module -Name $GetCommandModule | Select-Object -ExpandProperty Path

    $GetDriveForBackupFile = $(Get-DriveForBackupFile)
    $DiskIsBoot = $(Get-DiskIsBoot)
    $DiskToBackup = $(Get-DiskToBackup)
    $Volumes = $(Get-Volume)
    #===================================================================================================
    # Validate
    #===================================================================================================
    if ($ImageFile -like ":*") {
        $ImageFile = "C$ImageFile"
    }
    #===================================================================================================
    # Usage
    #===================================================================================================
    Write-Host -ForegroundColor DarkGray    '======================================================================================================'
    Write-Host -ForegroundColor White       "Backup-DiskToFFU " -NoNewline
    Write-Host -ForegroundColor Cyan        "$GetCommandVersion $GetModulePath"
    Write-Host -ForegroundColor DarkCyan    $GetCommandHelpUri
    Write-Host -ForegroundColor DarkGray    '======================================================================================================'
    Write-Host -ForegroundColor Yellow       "The following Partitions will be saved in the FFU:"
    foreach ($item in (Get-Partition | Where-Object {$_.DiskNumber -eq $DiskNumber})) {
        Write-Host -ForegroundColor White "DiskNumber:$($item.DiskNumber) Partition:$($item.PartitionNumber) DriveLetter:$($item.DriveLetter) Type:$($item.Type) $([math]::round($item.Size / 1000000000, 0)) GB"
    }
    
    if ($DiskToBackup -or $DiskIsBoot) {
        Write-Host -ForegroundColor DarkGray    '======================================================================================================'
        Write-Host -ForegroundColor Cyan        "-DiskNumber $DiskNumber"
        Write-Host -ForegroundColor White       "The Disk Number of the Disk to capture as an FFU. The default is the first available Disk"
        foreach ($item in $DiskToBackup) {
            Write-Host -ForegroundColor Cyan    "$($item.DiskNumber) " -NoNewline
            Write-Host -ForegroundColor Gray    "$($item.PartitionStyle) Partitions:$($item.NumberOfPartitions) $($item.FriendlyName) $($item.BusType) [$([math]::round($item.Size / 1000000000, 0))GB]"
        }
        if ($DiskIsBoot) {
            Write-Warning "Disks from a running OS cannot be selected"
            foreach ($item in $DiskIsBoot) {
                Write-Host -ForegroundColor Red "$($item.DiskNumber) $($item.PartitionStyle) Partitions:$($item.NumberOfPartitions) $($item.FriendlyName) $($item.BusType) [$([math]::round($item.Size / 1000000000, 0))GB]"
            }
        }
    }

    Write-Host -ForegroundColor DarkGray    '======================================================================================================'
    Write-Host -ForegroundColor Cyan        "-DestinationDriveLetter $DestinationDriveLetter"

    if ($GetDriveForBackupFile | Where-Object {$_.DiskNumber -ne $DiskNumber}) {
        Write-Host -ForegroundColor White   "Verify that the Drive you select below has plenty of space for your image"
        foreach ($item in ($GetDriveForBackupFile | Where-Object {$_.DiskNumber -ne $DiskNumber})) {
            Write-Host -ForegroundColor Cyan    "$($item.DriveLetter) " -NoNewline
            Write-Host -ForegroundColor Gray    "$($item.FileSystem) $($item.FileSystemLabel) [$($item.DriveType) TotalSize:$([math]::round($item.Size / 1000000000, 0))GB SizeRemaining:$([math]::round($item.SizeRemaining / 1000000000, 0))GB]"
        }
        foreach ($item in ($GetDriveForBackupFile | Where-Object {$_.DiskNumber -eq $DiskNumber})) {
            Write-Warning "Volumes that are being captured cannot be used as a Destination Drive"
            Write-Host -ForegroundColor Red    "$($item.DriveLetter) $($item.FileSystem) $($item.FileSystemLabel) [$($item.DriveType) TotalSize:$([math]::round($item.Size / 1000000000, 0))GB SizeRemaining:$([math]::round($item.SizeRemaining / 1000000000, 0))GB]"
        }
    } else {
        Write-Warning "Could not find any drives that you can backup to"
        Break
    }
    Write-Host -ForegroundColor DarkGray    '======================================================================================================'
    Write-Host -ForegroundColor Cyan        "-ImageFile $ImageFile"
    Write-Host -ForegroundColor White       'This path is generated automatically by combining the DestinationDriveLetter, CimComputerManufacturer,'
    Write-Host -ForegroundColor White       'ComputerModel SerialNumber and DiskNumber. You can fully modify this path to override the'
    Write-Host -ForegroundColor White       'DestinationDriveLetter or to save to a Network share'
    $ParentDirectory = Split-Path $ImageFile -Parent
    if (!(Test-Path "$ParentDirectory")) {
        Write-Host -ForegroundColor Yellow "Directory '$ParentDirectory' does not exist and will be created automatically"
    }

    Write-Host -ForegroundColor DarkGray    '======================================================================================================'
    Write-Host -ForegroundColor Cyan        'Other Parameters'
    Write-Host -ForegroundColor White       ' -Name ' -NoNewline
    Write-Host -ForegroundColor Gray        'Windows Image Property: Specifies the name of an image'
    Write-Host -ForegroundColor White       ' -Description ' -NoNewline
    Write-Host -ForegroundColor Gray        'Windows Image Property: Specifies the description of the image'
    Write-Host -ForegroundColor White       ' -Compress ' -NoNewline
    Write-Host -ForegroundColor Gray        'Compression level | Values: Default None'
    Write-Host -ForegroundColor Yellow      ' -Force ' -NoNewline
    Write-Host -ForegroundColor Gray        'Executes the capture'
    Write-Host -ForegroundColor DarkGray    '======================================================================================================'
    Write-Host -ForegroundColor Cyan        'Cmd Syntax:'
    Write-Host -ForegroundColor White       "DISM.exe /Capture-FFU /ImageFile=`"$ImageFile`" /CaptureDrive=\\.\PhysicalDrive$DiskNumber /Name:`"$Name`" /Description:`"$Description`" /Compress:$Compress"
    Write-Host -ForegroundColor DarkCyan    ''
    Write-Host -ForegroundColor Cyan        "PowerShell Syntax:"
    Write-Host -ForegroundColor White       "Backup-DiskToFFU -ImageFile `"$ImageFile`" -DiskNumber $DiskNumber -Name `"$Name`" -Description `"$Description`" -Compress $Compress " -NoNewline
    Write-Host -ForegroundColor Yellow      "-Force"
    Write-Host -ForegroundColor DarkCyan    ''
    Write-Host -ForegroundColor Cyan        "PowerShell Splatting:"
    Write-Host -ForegroundColor White       '$FFU = @{'
    Write-Host -ForegroundColor White       " ImageFile = `"$ImageFile`""
    Write-Host -ForegroundColor White       " DiskNumber = $DiskNumber"
    Write-Host -ForegroundColor White       " Name = `"$Name`""
    Write-Host -ForegroundColor White       " Description = `"$Description`""
    Write-Host -ForegroundColor White       " Compress = `"$Compress`""
    Write-Host -ForegroundColor White       "}"
    Write-Host -ForegroundColor White       "Backup-DiskToFFU @FFU " -NoNewline
    Write-Host -ForegroundColor Yellow      "-Force"
    Write-Host -ForegroundColor DarkGray    '======================================================================================================'
    
    if ([string]::IsNullOrEmpty($DestinationDriveLetter)) {
        Write-Warning "Unable to find a proper DestinationDriveLetter to store the Windows Image FFU file"
        Write-Warning "-Destination Drive must be larger than 10 GB and formatted NTFS"
        Write-Warning "-Destination Drive must not exist on the disk you are capturing (DiskNumber: $DiskNumber)"
        Write-Warning "-Network Drives are not supported in this release"
        Write-Warning "To bypass these issues, adjust and use the Command Prompt Syntax"
        Break
    }

    
    if ($env:SystemDrive -ne 'X:') {
        Write-Warning "You should be in WinPE to capure a proper FFU. If you have issues, that's on you!"
    }

    if ($Force) {
        if (!(Test-Path "$ParentDirectory")) {
            New-Item -Path $ParentDirectory -ItemType Directory -Force -ErrorAction Stop | Out-Null
        }
        DISM.exe /Capture-FFU /ImageFile="$ImageFile" /CaptureDrive=\\.\PhysicalDrive$DiskNumber /Name:"$Name" /Description:"$Description" /Compress:$Compress
        #Return Get-WindowsImage -ImagePath $ImageFile
    } else {
        Write-Warning "If everything looks good, add the -Force parameter to capture the FFU"
    }
}

$ScriptBlock = {
    param($CommandName,$ParameterName,$stringMatch)
    Get-DriveForBackupFile | Select-Object -ExpandProperty DriveLetter 
}

Register-ArgumentCompleter -CommandName Backup-DiskToFFU -ParameterName DestinationDriveLetter -ScriptBlock $ScriptBlock