Files/RecycleBin/Backup-DiskToFFU.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
<#
.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/disk/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-FFUSourceDisks | Select-Object -ExpandProperty DiskNumber)})]
        [int] $DiskNumber = (Get-FFUSourceDisks | Select-Object -ExpandProperty DiskNumber -First 1),

        [ValidateScript({$_ -in (Get-FFUDestinationDisks | Where-Object {$_.DiskNumber -ne $DiskNumber} | Select-Object -ExpandProperty DriveLetter)})]
        [string] $DestinationDriveLetter = "$(Get-FFUDestinationDisks | 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
    )
    #=================================================
    # PSBoundParameters
    #=================================================
    $IsConfirmPresent   = $PSBoundParameters.ContainsKey('Confirm')
    $IsForcePresent     = $PSBoundParameters.ContainsKey('Force')
    $IsVerbosePresent   = $PSBoundParameters.ContainsKey('Verbose')
    #=================================================
    # Module and Command Information
    #=================================================
    $GetCommandName = $MyInvocation.MyCommand | Select-Object -ExpandProperty Name
    $GetModuleBase = $MyInvocation.MyCommand.Module | Select-Object -ExpandProperty ModuleBase
    $GetModulePath = $MyInvocation.MyCommand.Module | Select-Object -ExpandProperty Path
    $GetModuleVersion = $MyInvocation.MyCommand.Module | Select-Object -ExpandProperty Version
    $GetCommandHelpUri = Get-Command -Name $GetCommandName | Select-Object -ExpandProperty HelpUri
    Write-Host "$GetCommandName" -NoNewline
    Write-Host " $GetModuleVersion $GetModuleBase" -ForegroundColor Cyan
    Write-Host "$GetCommandHelpUri" -ForegroundColor Cyan
    #=================================================
    # IsAdmin
    #=================================================
    if (-NOT (Get-OSDGather -Property IsAdmin)) {
        Write-Warning "Administrative Rights are required for execution"
        Break
    }
    #=================================================
    # Gather
    #=================================================
    $GetLocalDisk = Get-Disk.fixed | Where-Object {$_.NumberOfPartitions -ge '1'} | Where-Object {$_.OperationalStatus -eq 'Online'} | Where-Object {$_.Size -gt 0} | Where-Object {$_.IsOffline -eq $false}
    $BootDisks = $GetLocalDisk | Where-Object {$_.IsBoot -eq $true}
    $SourceDisks = $GetLocalDisk | Where-Object {$_.IsBoot -eq $false}
    $DestinationDisks = $(Get-FFUDestinationDisks)
    $Volumes = $(Get-Volume)
    #=================================================
    # Validate
    #=================================================
    if ($ImageFile -like ":*") {
        $ImageFile = "C$ImageFile"
    }
    #=================================================
    # Source
    #=================================================
    if ($SourceDisks -or $BootDisks) {
        Write-Host -ForegroundColor DarkGray    '======================================================================================================'
        Write-Host -ForegroundColor Cyan        "-DiskNumber $DiskNumber" -NoNewline
        Write-Host -ForegroundColor Yellow       " [Disk to capture in the FFU. Default is the first available Disk]"
        foreach ($item in $SourceDisks) {
            Write-Host -ForegroundColor Cyan    "$($item.DiskNumber) " -NoNewline
            Write-Host -ForegroundColor White    "$($item.PartitionStyle) Partitions:$($item.NumberOfPartitions) $($item.FriendlyName) $($item.BusType) [$([math]::round($item.Size / 1000000000, 0))GB]"
        }
        if (Get-Partition | Where-Object {$_.DiskNumber -eq $DiskNumber}) {
            Write-Host ""
            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 "Partition:$($item.PartitionNumber) DriveLetter:$($item.DriveLetter) Type:$($item.Type) $([math]::round($item.Size / 1000000000, 0)) GB"
            }
        }
        if ($BootDisks) {
            Write-Host ""
            Write-Warning "Disks from a running OS cannot be selected"
            foreach ($item in $BootDisks) {
                Write-Host -ForegroundColor Gray "$($item.DiskNumber) $($item.PartitionStyle) Partitions:$($item.NumberOfPartitions) $($item.FriendlyName) $($item.BusType) [$([math]::round($item.Size / 1000000000, 0))GB]"
            }
        }
    } else {
        Write-Warning "Unable to find a Source Disk to backup"
        Break
    }
    #=================================================
    # Destination
    #=================================================
    Write-Host -ForegroundColor DarkGray    '======================================================================================================'
    Write-Host -ForegroundColor Cyan        "-DestinationDriveLetter $DestinationDriveLetter" -NoNewline
    Write-Host -ForegroundColor Yellow       " [Verify that the Volume selected has enough free space for the FFU]"

    if ($DestinationDisks | Where-Object {$_.DiskNumber -ne $DiskNumber}) {
        foreach ($item in ($DestinationDisks | Where-Object {$_.DiskNumber -ne $DiskNumber})) {
            Write-Host -ForegroundColor Cyan    "$($item.DriveLetter) " -NoNewline
            Write-Host -ForegroundColor White    "$($item.FileSystem) $($item.FileSystemLabel) [$($item.DriveType) TotalSize:$([math]::round($item.Size / 1000000000, 0))GB SizeRemaining:$([math]::round($item.SizeRemaining / 1000000000, 0))GB]"
        }
        if ($DestinationDisks | Where-Object {$_.DiskNumber -eq $DiskNumber}) {
            Write-Host ""
            foreach ($item in ($DestinationDisks | Where-Object {$_.DiskNumber -eq $DiskNumber})) {
                Write-Warning "Volumes that are being captured cannot be used as a Destination Drive"
                Write-Host -ForegroundColor Gray    "$($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 ""
    Write-Host -ForegroundColor Yellow       'This path is generated automatically by combining the DestinationDriveLetter, CimComputerManufacturer,'
    Write-Host -ForegroundColor Yellow       'ComputerModel SerialNumber and DiskNumber. You can fully modify this path to override the'
    Write-Host -ForegroundColor Yellow       'DestinationDriveLetter or to save to a Network share'
    $ParentDirectory = Split-Path $ImageFile -Parent
    if (!(Test-Path "$ParentDirectory")) {
        Write-Warning "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")) {
            Try {New-Item -Path $ParentDirectory -ItemType Directory -Force -ErrorAction Stop}
            Catch {Write-Warning "Destination appears to be Read Only. Try another Destination Drive";Break}
        }
        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 e.g. Backup-DiskToFFU -Force"
    }
}

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

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