Functions/Add-UpdateImage.ps1
function Add-UpdateImage { <# .Synopsis Add a Windows Image to a Windows Image Tools Update Directory .DESCRIPTION This command will convert a .ISO or .WIM into a VHD populated with an unattend.xml and first boot script .EXAMPLE Add-WitUpdateImage -Path c:\WitTools .EXAMPLE Another example of how to use this cmdlet .INPUTS System.IO.DirectoryInfo .OUTPUTS Custom object containing String -Path and String -Name #> [CmdletBinding(SupportsShouldProcess = $true)] #[OutputType([String])] Param ( # Path to the Windows Image Tools Update Folders (created via New-WindowsImageToolsExample) [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [ValidateScript({ if (Test-Path $_) { $true } else { throw "Path $_ does not exist" } })] [Alias('FullName')] $Path, # Friendly name for for Base VHD used for filenames and targeting in Invoke-WindwosImageUpdate [Parameter(Mandatory = $true)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [string] $FriendlyName, # Administrator Password for Base VHD [Parameter(Mandatory = $true)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [PSCredential] $AdminCredential, # Product Key for sorce image (Not required for volume licence media) [ValidateNotNullOrEmpty()] [ValidateScript({ if ($_ -imatch '^[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{5}$') { $true } else { throw "$_ not a valid key format" } })] [String] $ProductKey, # Size in Bytes (Default 40B) [ValidateRange(25GB,64TB)] [uint64]$Size = 40GB, # Create Dynamic disk [switch]$Dynamic, # Specifies whether to build the image for BIOS (MBR), UEFI (GPT), or WindowsToGo (MBR). # Generation 1 VMs require BIOS (MBR) images. Generation 2 VMs require UEFI (GPT) images. # Windows To Go images will boot in UEFI or BIOS [Parameter(Mandatory = $true)] [Alias('Layout')] [string] [ValidateNotNullOrEmpty()] [ValidateSet('BIOS', 'UEFI', 'WindowsToGo')] $DiskLayout, # Path to WIM or ISO used to populate VHDX [parameter(Position = 1,Mandatory = $true, HelpMessage = 'Enter the path to the WIM/ISO file')] [ValidateScript({ Test-Path -Path (Get-FullFilePath -Path $_ ) })] [string]$SourcePath, # Index of image inside of WIM (Default 1) [int]$Index = 1, # Add payload for all removed features [switch]$AddPayloadForRemovedFeature, # Features to turn on (in DISM format) [ValidateNotNullOrEmpty()] [string[]]$Feature, # Feature to remove (in DISM format) [ValidateNotNullOrEmpty()] [string[]]$RemoveFeature, # Feature Source path. If not provided, all ISO and WIM images in $sourcePath searched [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path -Path $(Resolve-Path $_) })] [string[]]$FeatureSource, # Feature Source index. If the source is a .wim provide an index Default =1 [int]$FeatureSourceIndex, # Path to drivers to inject [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path -Path $(Resolve-Path $_) })] [string[]]$Driver, # Path of packages to install via DSIM [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path -Path $(Resolve-Path $_) })] [string[]]$Package, # Files/Folders to copy to root of Winodws Drive (to place files in directories mimic the direcotry structure off of C:\) [ValidateNotNullOrEmpty()] [ValidateScript({ foreach ($Path in $_) { Test-Path -Path $(Resolve-Path $Path) } })] [string[]]$filesToInject, # Force the overwrite of existing Image [switch]$force ) $target = "$Path\BaseImage\$($FriendlyName)_base.vhdx" if ($pscmdlet.ShouldProcess("$target", 'Add Windows Image Tools Update Image')) { $ParametersToPass = @{} foreach ($key in ('Whatif', 'Verbose', 'Debug')) { if ($PSBoundParameters.ContainsKey($key)) { $ParametersToPass[$key] = $PSBoundParameters[$key] } } #region Validate Input try { $null = Test-Path -Path "$Path\BaseImage" -ErrorAction Stop $null = Test-Path -Path "$Path\Resource" -ErrorAction Stop } catch { Throw "$Path missing required folder structure use New-WindowsImagetoolsExample to create example" } if ((Test-Path -Path "$Path\BaseImage\$($FriendlyName)_Base.vhdx") -and (-not ($force))) { Throw "BaseImage $Path\BaseImage\$($FriendlyName)_Base.vhdx allready exists. use -force to overwrite " } #endregion #region Unattend $unattentParam = @{ FirstBootScriptPath = 'c:\pstemp\FirstBoot.ps1' AdminCredential = $AdminCredential EnableAdministrator = $true } if ($ProductKey) { $unattentParam.add('ProductKey',$ProductKey) } $UnattendPath = New-UnattendXml @unattentParam @ParametersToPass #endregion #region Create Base VHD $convertParm = @{ DiskLayout = $DiskLayout SourcePath = $SourcePath Index = $Index Unattend = $UnattendPath Path = $target } if ($Dynamic) { $convertParm.add('Dynamic',$Dynamic) } if ($AddPayloadForRemovedFeature) { $convertParm.add('AddPayloadForRemovedFeature', $AddPayloadForRemovedFeature) } if ($Feature) { $convertParm.add('Feature',$Feature) } if ($RemoveFeature) { $SetVHDPartitionParam.add('RemoveFeature', $RemoveFeature) } if ($FeatureSource) { $SetVHDPartitionParam.add('FeatureSource', $FeatureSource) } if ($FeatureSourceIndex) { $SetVHDPartitionParam.add('FeatureSourceIndex', $FeatureSourceIndex) } if ($Driver) { $convertParm.add('Driver',$Driver) } if ($Package) { $convertParm.add('Package',$Package) } if ($filesToInject) { $convertParm.add('filesToInject',$filesToInject) } if ($force) { $convertParm.add('force',$true) } Write-Verbose -Message "[$($MyInvocation.MyCommand)] : $target : Creating " Convert-Wim2VHD @convertParm @ParametersToPass #endregion #region add firstboot script $FirstBootContent = { Start-Transcript -Path $PSScriptRoot\FirstBoot.log Get-Service Schedule | Start-Service Start-Sleep -Seconds 20 schtasks.exe /Create /TN 'AtStartup' /RU 'SYSTEM' /SC ONSTART /TR "'%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe' -NoProfile -ExecutionPolicy Bypass -File C:\PsTemp\AtStartup.ps1" Start-Sleep -Seconds 20 # Restart-Computer does not have -force in 2008/win7 WMF2 if ((Get-Command Restart-Computer -Syntax) -like '*[force]*') { Restart-Computer -Verbose -Force } else { shutdown.exe /r /t 0 /f } Stop-Transcript } $AddScriptFilesBlock = { if (-not (Test-Path "$($driveLetter):\PsTemp")) { $null = mkdir "$($driveLetter):\PsTemp" -ErrorAction SilentlyContinue } $null = New-Item -Path "$($driveLetter):\PsTemp" -Name FirstBoot.ps1 -ItemType 'file' -Value $FirstBootContent } Write-Verbose -Message "[$($MyInvocation.MyCommand)] : $target : Adding First Boot Script " MountVHDandRunBlock -vhd $target -block $AddScriptFilesBlock @ParametersToPass Write-Verbose -Message "[$($MyInvocation.MyCommand)] : $target : Finished " #endregion } } |