diff --git a/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 similarity index 97% rename from BuildFFUVM.ps1 rename to FFUDevelopment/BuildFFUVM.ps1 index 8cde6a3..70ddff5 100644 --- a/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1,54 +1,54 @@ -#Modify variables -$rand = get-random -$VMName = "_FFU-$rand" -$VMPath = "c:\VM\$VMName" -$VHDPath = "$VMPath\$VMName.vhdx" -$ISOPath = "E:\software\ISOs\Windows\Windows 11\22H2\en-us_windows_11_consumer_editions_version_22h2_updated_jan_2023_x64_dvd_aafaf7fa.iso" -$memory = 8GB -$processors = 4 - -# 0. Delete old VMs - -$vms = get-vm _ffu-* | ? {$_.state -ne 'running'} - -If($null -ne $vms){ - Foreach ($vm in $vms){ - $OldVMName = $vm.VMName - Remove-VM -Name $vm.name -Force -ErrorAction SilentlyContinue - Remove-Item -Path "C:\VM\$OldVMName" -Force -Recurse -ErrorAction SilentlyContinue - } -} - -# 1. Create Dynamic Hard Disk -mkdir -Path $VMPath -Force -#New-VHD -Path $VHDPath -Fixed -SizeBytes 30GB -New-VHD -path $VHDPath -SizeBytes 128000000000 -LogicalSectorSizeBytes 512 -Dynamic - -# 2. Create VM -# - Name: _FFU -# - Location: C:\VM\_FFU -# - Generation: 2 -# - Memory: 4GB static -# - Networking: Not connected -# - Connect to existing VHD: c:\VM\_FFU\ -# - Mount ISO -New-VM -Name $VMName -Path $VMPath -MemoryStartupBytes $memory -VHDPath $VHDPath -Generation 2 -Set-VMProcessor -VMName $VMName -Count $processors -Add-VMDvdDrive -VMName $VMName -Path $ISOPath -$VMDVDDrive = Get-VMDvdDrive -VMName $VMName - -#Use for Win11 -Set-VMFirmware -VMName $VMName -FirstBootDevice $VMDVDDrive -If(Get-HgsGuardian -Name 'Guardian'){ - Remove-HgsGuardian -Name 'Guardian' - Get-ChildItem -Path 'Cert:\LocalMachine\Shielded VM Local Certificates\' | Remove-Item -} -New-HgsGuardian -Name 'Guardian' -GenerateCertificates -$owner = get-hgsguardian -Name 'Guardian' -$kp = New-HgsKeyProtector -Owner $owner -AllowUntrustedRoot -Set-VMKeyProtector -VMName $VMName -KeyProtector $kp.RawData -Enable-VMTPM -VMName $VMName - -#Use if creating for MDT/SCCM Builds -#Get-VM $VMName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName "Intel(R) I350 Gigabit Network Connection - Virtual Switch" +#Modify variables +$rand = get-random +$VMName = "_FFU-$rand" +$VMPath = "c:\VM\$VMName" +$VHDPath = "$VMPath\$VMName.vhdx" +$ISOPath = "E:\software\ISOs\Windows\Windows 11\22H2\en-us_windows_11_consumer_editions_version_22h2_updated_jan_2023_x64_dvd_aafaf7fa.iso" +$memory = 8GB +$processors = 4 + +# 0. Delete old VMs + +$vms = get-vm _ffu-* | ? {$_.state -ne 'running'} + +If($null -ne $vms){ + Foreach ($vm in $vms){ + $OldVMName = $vm.VMName + Remove-VM -Name $vm.name -Force -ErrorAction SilentlyContinue + Remove-Item -Path "C:\VM\$OldVMName" -Force -Recurse -ErrorAction SilentlyContinue + } +} + +# 1. Create Dynamic Hard Disk +mkdir -Path $VMPath -Force +#New-VHD -Path $VHDPath -Fixed -SizeBytes 30GB +New-VHD -path $VHDPath -SizeBytes 128000000000 -LogicalSectorSizeBytes 512 -Dynamic + +# 2. Create VM +# - Name: _FFU +# - Location: C:\VM\_FFU +# - Generation: 2 +# - Memory: 4GB static +# - Networking: Not connected +# - Connect to existing VHD: c:\VM\_FFU\ +# - Mount ISO +New-VM -Name $VMName -Path $VMPath -MemoryStartupBytes $memory -VHDPath $VHDPath -Generation 2 +Set-VMProcessor -VMName $VMName -Count $processors +Add-VMDvdDrive -VMName $VMName -Path $ISOPath +$VMDVDDrive = Get-VMDvdDrive -VMName $VMName + +#Use for Win11 +Set-VMFirmware -VMName $VMName -FirstBootDevice $VMDVDDrive +If(Get-HgsGuardian -Name 'Guardian'){ + Remove-HgsGuardian -Name 'Guardian' + Get-ChildItem -Path 'Cert:\LocalMachine\Shielded VM Local Certificates\' | Remove-Item +} +New-HgsGuardian -Name 'Guardian' -GenerateCertificates +$owner = get-hgsguardian -Name 'Guardian' +$kp = New-HgsKeyProtector -Owner $owner -AllowUntrustedRoot +Set-VMKeyProtector -VMName $VMName -KeyProtector $kp.RawData +Enable-VMTPM -VMName $VMName + +#Use if creating for MDT/SCCM Builds +#Get-VM $VMName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName "Intel(R) I350 Gigabit Network Connection - Virtual Switch" #$networkAdapter = Get-VMNetworkAdapter -VMName $VMName \ No newline at end of file diff --git a/FFUDevelopment/BuildWinPECaptureMedia.cmd b/FFUDevelopment/BuildWinPECaptureMedia.cmd new file mode 100644 index 0000000..6cd695d --- /dev/null +++ b/FFUDevelopment/BuildWinPECaptureMedia.cmd @@ -0,0 +1,20 @@ +rd c:\WinPE /S /Q +md c:\WinPEOutput +cmd /c copype amd64 c:\WinPE +Dism /Mount-Image /ImageFile:"c:\WinPE\media\sources\boot.wim" /Index:1 /MountDir:"c:\WinPE\mount" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-WMI.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-NetFX.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-Scripting.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-PowerShell.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-PowerShell_en-us.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-StorageWMI.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-StorageWMI_en-us.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-DismCmdlets.cab" +Dism /Add-Package /Image:"c:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-DismCmdlets_en-us.cab" +xcopy "C:\FFU Development\WinPECaptureFFUFiles" c:\WinPE\mount /Y /E +Dism /Unmount-Image /MountDir:c:\WinPE\mount /Commit +MakeWinPEMedia /ISO /F c:\WinPE "C:\WinPEOutput\WinPE_FFU_Capture.iso" + diff --git a/FFUDevelopment/BuildWinPEDeploymentMedia.cmd b/FFUDevelopment/BuildWinPEDeploymentMedia.cmd new file mode 100644 index 0000000..4b57713 --- /dev/null +++ b/FFUDevelopment/BuildWinPEDeploymentMedia.cmd @@ -0,0 +1,19 @@ +rd c:\WinPE /S /Q +cmd /c copype amd64 C:\WinPE +Dism /Mount-Image /ImageFile:"C:\WinPE\media\sources\boot.wim" /Index:1 /MountDir:"C:\WinPE\mount" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-WMI.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-NetFX.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-Scripting.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-PowerShell.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-PowerShell_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-StorageWMI.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-StorageWMI_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-DismCmdlets.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-DismCmdlets_en-us.cab" +REM Dism /image:"C:\WinPE\mount" /add-driver /Driver:C:\temp\drivers\LGO\Battery /recurse +xcopy "C:\FFU Development\WinPEDeployFFUFiles" c:\WinPE\mount /Y /E +Dism /Unmount-Image /MountDir:C:\WinPE\mount /Commit +MakeWinPEMedia /ISO /F C:\WinPE "C:\FFU Development\PE\WinPE_22H2.iso" \ No newline at end of file diff --git a/FFUDevelopment/BuildWinPEDeploymentMediaVM.cmd b/FFUDevelopment/BuildWinPEDeploymentMediaVM.cmd new file mode 100644 index 0000000..c70e512 --- /dev/null +++ b/FFUDevelopment/BuildWinPEDeploymentMediaVM.cmd @@ -0,0 +1,19 @@ +rd c:\WinPE /S /Q +cmd /c copype amd64 C:\WinPE +Dism /Mount-Image /ImageFile:"C:\WinPE\media\sources\boot.wim" /Index:1 /MountDir:"C:\WinPE\mount" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-WMI.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-NetFX.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-Scripting.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-PowerShell.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-PowerShell_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-StorageWMI.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-StorageWMI_en-us.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-DismCmdlets.cab" +Dism /Add-Package /Image:"C:\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-DismCmdlets_en-us.cab" +REM Dism /image:"C:\WinPE\mount" /add-driver /Driver:C:\temp\drivers\LGO\Battery /recurse +xcopy "C:\FFU Development\WinPEDeployFFUFilesVM" c:\WinPE\mount /Y /E +Dism /Unmount-Image /MountDir:C:\WinPE\mount /Commit +MakeWinPEMedia /ISO /F C:\WinPE "C:\FFU Development\PE\WinPE_21H2VM.iso" \ No newline at end of file diff --git a/FFUDevelopment/CaptureFFU.ps1 b/FFUDevelopment/CaptureFFU.ps1 new file mode 100644 index 0000000..bd539b9 --- /dev/null +++ b/FFUDevelopment/CaptureFFU.ps1 @@ -0,0 +1,19 @@ +#Modify variables +$ISOPath = 'C:\ffu\WinPE_FFU_Capture.iso' + +$vms = get-vm _ffu* | ? {$_.state -ne 'running'} + + +If($null -ne $vms){ + Foreach ($vm in $vms){ + $VMName = $vm.name + $VMDVDDrive = Get-VMDvdDrive -VMName $VMName + Set-VMFirmware -VMName $VMName -FirstBootDevice $VMDVDDrive + Set-VMDvdDrive -VMName $VMName -Path $ISOPath + $VMSwitch = Get-VMSwitch -name *intel* + get-vm $VMName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $VMSwitch.Name + } +} + + + diff --git a/FFUDevelopment/Office/FFU.xml b/FFUDevelopment/Office/FFU.xml new file mode 100644 index 0000000..6977fb3 --- /dev/null +++ b/FFUDevelopment/Office/FFU.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/InstallOfficeandSysprep.cmd b/FFUDevelopment/Office/InstallOfficeandSysprep.cmd similarity index 98% rename from InstallOfficeandSysprep.cmd rename to FFUDevelopment/Office/InstallOfficeandSysprep.cmd index 0d8f102..1bc6810 100644 --- a/InstallOfficeandSysprep.cmd +++ b/FFUDevelopment/Office/InstallOfficeandSysprep.cmd @@ -1,3 +1,3 @@ -d:\setup.exe /configure d:\FFU.xml -taskkill /IM sysprep.exe +d:\setup.exe /configure d:\FFU.xml +taskkill /IM sysprep.exe c:\windows\system32\sysprep\sysprep.exe /quiet /generalize /oobe \ No newline at end of file diff --git a/WinPECaptureFFUFiles/AssignDriveLetter.txt b/FFUDevelopment/WinPECaptureFFUFiles/AssignDriveLetter.txt similarity index 93% rename from WinPECaptureFFUFiles/AssignDriveLetter.txt rename to FFUDevelopment/WinPECaptureFFUFiles/AssignDriveLetter.txt index fe8628f..a18948f 100644 --- a/WinPECaptureFFUFiles/AssignDriveLetter.txt +++ b/FFUDevelopment/WinPECaptureFFUFiles/AssignDriveLetter.txt @@ -1,4 +1,4 @@ -select disk 0 -select partition 3 -Assign letter="M" -exit +select disk 0 +select partition 3 +Assign letter="M" +exit diff --git a/WinPECaptureFFUFiles/CaptureFFU.ps1 b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 similarity index 97% rename from WinPECaptureFFUFiles/CaptureFFU.ps1 rename to FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 index ea19629..850389a 100644 --- a/WinPECaptureFFUFiles/CaptureFFU.ps1 +++ b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 @@ -1,58 +1,58 @@ -#Modify the net use path to map the W: drive to the location you want to copy the FFU file to -net use W: \\192.168.1.2\c$\temp /user:administrator p@ssw0rd - -$AssignDriveLetter = 'x:\AssignDriveLetter.txt' -Start-Process -FilePath diskpart.exe -ArgumentList "/S $AssignDriveLetter" -Wait -ErrorAction Stop | Out-Null -#Load Registry Hive -$Software = 'M:\Windows\System32\config\software' -reg load "HKLM\FFU" $Software - -#Find Windows version values - -$SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID' -[int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild' -$DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion' -$BuildDate = Get-Date -uformat %b%Y - -$SKU = switch ($SKU){ - Home {'Home'} - Professional {'Pro'} - ProfessionalEducation {'Pro_Edu'} - Enterprise {'Ent'} -} - -if($CurrentBuild -ge 22000){ - $Name = 'Win11' -} -else{ - $Name = 'Win10' -} - -#If Office is installed, modify the file name of the FFU -$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue | Out-Null -if($Office){ - $ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Office`_$BuildDate.ffu" - $dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" - - -} -else{ - $ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_$BuildDate.ffu" - $dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" - -} - -#Unload Registry -Set-Location X:\ -Remove-Variable SKU -Remove-Variable CurrentBuild -Remove-Variable DisplayVersion -Remove-Variable Office -reg unload "HKLM\FFU" - -Start-Process -FilePath dism.exe -ArgumentList $dismArgs -Wait -PassThru -ErrorAction Stop | Out-Null -$dismOptArgs = "/optimize-ffu /imagefile:$ffuFilePath" -Start-Process -FilePath dism.exe -ArgumentList $dismOptArgs -Wait -PassThru -ErrorAction Stop | Out-Null -#Copy DISM log to Host -xcopy X:\Windows\logs\dism\dism.log W:\ /Y | Out-Null -shutdown /p +#Modify the net use path to map the W: drive to the location you want to copy the FFU file to +net use W: \\192.168.1.2\c$\temp /user:administrator p@ssw0rd + +$AssignDriveLetter = 'x:\AssignDriveLetter.txt' +Start-Process -FilePath diskpart.exe -ArgumentList "/S $AssignDriveLetter" -Wait -ErrorAction Stop | Out-Null +#Load Registry Hive +$Software = 'M:\Windows\System32\config\software' +reg load "HKLM\FFU" $Software + +#Find Windows version values + +$SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID' +[int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild' +$DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion' +$BuildDate = Get-Date -uformat %b%Y + +$SKU = switch ($SKU){ + Home {'Home'} + Professional {'Pro'} + ProfessionalEducation {'Pro_Edu'} + Enterprise {'Ent'} +} + +if($CurrentBuild -ge 22000){ + $Name = 'Win11' +} +else{ + $Name = 'Win10' +} + +#If Office is installed, modify the file name of the FFU +$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue | Out-Null +if($Office){ + $ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Office`_$BuildDate.ffu" + $dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" + + +} +else{ + $ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_$BuildDate.ffu" + $dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" + +} + +#Unload Registry +Set-Location X:\ +Remove-Variable SKU +Remove-Variable CurrentBuild +Remove-Variable DisplayVersion +Remove-Variable Office +reg unload "HKLM\FFU" + +Start-Process -FilePath dism.exe -ArgumentList $dismArgs -Wait -PassThru -ErrorAction Stop | Out-Null +$dismOptArgs = "/optimize-ffu /imagefile:$ffuFilePath" +Start-Process -FilePath dism.exe -ArgumentList $dismOptArgs -Wait -PassThru -ErrorAction Stop | Out-Null +#Copy DISM log to Host +xcopy X:\Windows\logs\dism\dism.log W:\ /Y | Out-Null +shutdown /p diff --git a/WinPECaptureFFUFiles/Windows/System32/startnet.cmd b/FFUDevelopment/WinPECaptureFFUFiles/Windows/System32/startnet.cmd similarity index 96% rename from WinPECaptureFFUFiles/Windows/System32/startnet.cmd rename to FFUDevelopment/WinPECaptureFFUFiles/Windows/System32/startnet.cmd index 0faedbc..ed54781 100644 --- a/WinPECaptureFFUFiles/Windows/System32/startnet.cmd +++ b/FFUDevelopment/WinPECaptureFFUFiles/Windows/System32/startnet.cmd @@ -1,5 +1,5 @@ -wpeinit -powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c -powershell -Noprofile -ExecutionPolicy Bypass -File x:\CaptureFFU.ps1 -exit - +wpeinit +powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c +powershell -Noprofile -ExecutionPolicy Bypass -File x:\CaptureFFU.ps1 +exit + diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 new file mode 100644 index 0000000..509ec57 --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 @@ -0,0 +1,579 @@ +function Get-USBDrive(){ + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Removable' -and $_.FileSystemType -eq 'NTFS'}).DriveLetter + if ($null -eq $USBDriveLetter){ + #Must be using a fixed USB drive - difficult to grab drive letter from win32_diskdrive. Assume user followed instructions and used Deploy as the friendly name for partition + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Fixed' -and $_.FileSystemType -eq 'NTFS' -and $_.FileSystemLabel -eq 'Deploy'}).DriveLetter + #If we didn't get the drive letter, stop the script. + if ($null -eq $USBDriveLetter){ + WriteLog 'Cannot find USB drive letter - most likely using a fixed USB drive. Name the 2nd partition with the FFU files as Deploy so the script can grab the drive letter. Exiting' + Exit + } + + } + $USBDriveLetter = $USBDriveLetter + ":\" + return $USBDriveLetter +} + +function Get-HardDrive(){ + $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID + return $DeviceID +} + +function WriteLog($LogText){ + Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" +} + +function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){ + (Get-Content $DiskpartFile).Replace('disk 0', "disk $DiskID") | Set-Content -Path $DiskpartFile +} + +function Set-Computername($computername){ + [xml]$xml = Get-Content $UnattendFile + if($xml.unattend.settings.component.Count -ge 2){ + #Assumes that Computername is the first component element + $xml.unattend.settings.component[0].ComputerName = $computername + }else{ + $xml.unattend.settings.component.ComputerName = $computername + } + $xml.Save($UnattendFile) + return $computername +} + +function Invoke-Process { + [CmdletBinding(SupportsShouldProcess)] + param + ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$FilePath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$ArgumentList + ) + + $ErrorActionPreference = 'Stop' + + try { + $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)" + $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)" + + $startProcessParams = @{ + FilePath = $FilePath + ArgumentList = $ArgumentList + RedirectStandardError = $stdErrTempFile + RedirectStandardOutput = $stdOutTempFile + Wait = $true; + PassThru = $true; + NoNewWindow = $false; + } + if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) { + $cmd = Start-Process @startProcessParams + $cmdOutput = Get-Content -Path $stdOutTempFile -Raw + $cmdError = Get-Content -Path $stdErrTempFile -Raw + if ($cmd.ExitCode -ne 0) { + if ($cmdError) { + throw $cmdError.Trim() + } + if ($cmdOutput) { + throw $cmdOutput.Trim() + } + } else { + if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) { + WriteLog $cmdOutput + } + } + } + } catch { + #$PSCmdlet.ThrowTerminatingError($_) + WriteLog $_ + Write-Host 'Script failed - check scriptlog.txt on the USB drive for more info' + throw $_ + + } finally { + Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore + + } + +} + +# This function can be used in instances where battery level might matter (e.g. installing firmware for Surface). The problem is that WinPE doesn't have +# a driver for the battery installed, so you'll need to inject drivers, which can be tricky because just injecting the battery driver might not be enough, +# you might also need other drivers that the battery driver is dependent on. +# function Get-Battery(){ +# while (($BattLev = (Get-CimInstance win32_battery).EstimatedChargeRemaining) -lt "35") +# { +# WriteLog "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Write-Host "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Start-Sleep 60 +# } + +# WriteLog "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# Write-Host "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# } + +#Get USB Drive and create log file +$LogFileName = 'ScriptLog.txt' +$USBDrive = Get-USBDrive +New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null +$LogFile = $USBDrive + $LogFilename +WriteLog 'Begin Logging' + +#Find PhysicalDrive +$PhysicalDeviceID = Get-HardDrive +WriteLog "Physical DeviceID is $PhysicalDeviceID" + +#Parse DiskID Number +$DiskID = $PhysicalDeviceID.substring($PhysicalDeviceID.length - 1,1) +WriteLog "DiskID is $DiskID" + +#Modify diskpart answer files if DiskID not 0 +# $UEFIFFUPartitions = 'x:\CreateUEFI-FFU-Partitions.txt' +# $ExtendPartition = 'x:\ExtendPartition-UEFI.txt' + +# If ($DiskID -ne '0'){ +# WriteLog 'DiskID is not 0. Need to modify diskpart answer files' +# try { +# Set-DiskpartAnswerFiles $UEFIFFUPartitions $DiskID +# } +# catch { +# WriteLog "Modifying $UEFIFFUPartitions failed with error: $_" +# } + +# try { +# Set-DiskpartAnswerFiles $ExtendPartition $DiskID +# } +# catch { +# WriteLog "Modifying $ExtendPartition failed with error: $_" +# } +# } + +#Find FFU Files +[array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu) +$FFUCount = $FFUFiles.Count + +#If multiple FFUs found, ask which to install +If ($FFUCount -gt 1) { + WriteLog "Found $FFUCount FFU Files" + $array = @() + + for($i=0;$i -le $FFUCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; FFUFile = $FFUFiles[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, FFUFile + do { + try { + $var = $true + [int]$FFUSelected = Read-Host 'Enter the FFU number to install' + $FFUSelected = $FFUSelected -1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid FFU number' + $var = $false + } + } until (($FFUSelected -le $FFUCount -1) -and $var) + + $FFUFileToInstall = $array[$FFUSelected].FFUFile + WriteLog "$FFUFileToInstall was selected" +} +elseif ($FFUCount -eq 1) { + WriteLog "Found $FFUCount FFU File" + $FFUFileToInstall = $FFUFiles[0].FullName + WriteLog "$FFUFileToInstall will be installed" +} +else { + Writelog 'No FFU files found' + Write-Host 'No FFU files found' + Exit +} + +#FindAP +$APFolder = $USBDrive + "Autopilot\" +If (Test-Path -Path $APFolder){ + [array]$APFiles = @(Get-ChildItem -Path $APFolder*.json) + $APFilesCount = $APFiles.Count + if ($APFilesCount -ge 1){ + $autopilot = $true + } +} + + +#FindPPKG +$PPKGFolder = $USBDrive + "PPKG\" +if (Test-Path -Path $PPKGFolder){ + [array]$PPKGFiles = @(Get-ChildItem -Path $PPKGFolder*.ppkg) + $PPKGFilesCount = $PPKGFiles.Count + if ($PPKGFilesCount -ge 1){ + $PPKG = $true + } +} + +#FindUnattend +$UnattendFolder = $USBDrive + "unattend\" +$UnattendFilePath = $UnattendFolder + "unattend.xml" +$UnattendPrefixPath = $UnattendFolder + "prefixes.txt" +If (Test-Path -Path $UnattendFilePath){ + $UnattendFile = Get-ChildItem -Path $UnattendFilePath + If ($UnattendFile){ + $Unattend = $true + } +} +If (Test-Path -Path $UnattendPrefixPath){ + $UnattendPrefixFile = Get-ChildItem -Path $UnattendPrefixPath + If ($UnattendPrefixFile){ + $UnattendPrefix = $true + } +} + +#Ask for device name if unattend exists +if ($Unattend -and $UnattendPrefix){ + Writelog 'Unattend file found with prefixes.txt. Getting prefixes.' + $UnattendPrefixes = @(Get-content $UnattendPrefixFile) + $UnattendPrefixCount = $UnattendPrefixes.Count + If ($UnattendPrefixCount -gt 1) { + WriteLog "Found $UnattendPrefixCount Prefixes" + $array = @() + for($i=0;$i -le $UnattendPrefixCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; DeviceNamePrefix = $UnattendPrefixes[$i]} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, DeviceNamePrefix + do { + try { + $var = $true + [int]$PrefixSelected = Read-Host 'Enter the prefix number to use for the device name' + $PrefixSelected = $PrefixSelected -1 + } + catch { + Write-Host 'Input was not in correct format. Please enter a valid prefix number' + $var = $false + } + } until (($PrefixSelected -le $UnattendPrefixCount -1) -and $var) + $PrefixToUse = $array[$PrefixSelected].DeviceNamePrefix + WriteLog "$PrefixToUse was selected" + } + elseif ($UnattendPrefixCount -eq 1) { + WriteLog "Found $UnattendPrefixCount Prefix" + $PrefixToUse = $UnattendPrefixes[0] + WriteLog "Will use $PrefixToUse as device name prefix" + } + #Get serial number to append. This can make names longer than 15 characters. Trim any leading or trailing whitespace + $serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim() + #Combine prefix with serial + $computername = $PrefixToUse + $serial + #If computername is longer than 15 characters, reduce to 15. Sysprep/unattend doesn't like ComputerName being longer than 15 characters even though Windows accepts it + If ($computername.Length -gt 15){ + $computername = $computername.substring(0,15) + } + $computername = Set-Computername($computername) + Writelog "Computer name set to $computername" +} +elseif($Unattend){ + Writelog 'Unattend file found with no prefixes.txt, asking for name' + [string]$computername = Read-Host 'Enter device name' + Set-Computername($computername) + Writelog "Computer name set to $computername" +} +else { + WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.' +} + +#If both AP and PPKG folder found with files, ask which to use. +If($autopilot -eq $true -and $PPKG -eq $true){ + WriteLog 'Both PPKG and Autopilot json files found' + Write-Host 'Both Autopilot JSON files and Provisioning packages were found.' + do { + try { + $var = $true + [int]$APorPPKG = Read-Host 'Enter 1 for Autopilot or 2 for Provisioning Package' + } + + catch { + Write-Host 'Incorrect value. Please enter 1 for Autopilot or 2 for Provisioning Package' + $var = $false + } + } until (($APorPPKG -gt 0 -and $APorPPKG -lt 3) -and $var) + If ($APorPPKG -eq 1){ + $PPKG = $false + } + else{ + $autopilot = $false + } +} + +#If multiple AP json files found, ask which to install +If ($APFilesCount -gt 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount Autopilot json Files" + $array = @() + + for($i=0;$i -le $APFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; APFile = $APFiles[$i].FullName; APFileName = $APFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, APFileName + do { + try { + $var = $true + [int]$APFileSelected = Read-Host 'Enter the AP json file number to install' + $APFileSelected = $APFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid AP json file number' + $var = $false + } + } until (($APFileSelected -le $APFilesCount -1) -and $var) + + $APFileToInstall = $array[$APFileSelected].APFile + $APFileName = $array[$APFileSelected].APFileName + WriteLog "$APFileToInstall was selected" +} +elseif ($APFilesCount -eq 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount AP File" + $APFileToInstall = $APFiles[0].FullName + $APFileName = $APFiles[0].Name + WriteLog "$APFileToInstall will be copied" +} +else { + Writelog 'No AP files found or AP was not selected' +} + +#If multiple PPKG files found, ask which to install +If ($PPKGFilesCount -gt 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG Files" + $array = @() + + for($i=0;$i -le $PPKGFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; PPKGFile = $PPKGFiles[$i].FullName; PPKGFileName = $PPKGFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, PPKGFileName + do { + try { + $var = $true + [int]$PPKGFileSelected = Read-Host 'Enter the PPKG file number to install' + $PPKGFileSelected = $PPKGFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid PPKG file number' + $var = $false + } + } until (($PPKGFileSelected -le $PPKGFilesCount -1) -and $var) + + $PPKGFileToInstall = $array[$PPKGFileSelected].PPKGFile + WriteLog "$PPKGFileToInstall was selected" +} +elseif ($PPKGFilesCount -eq 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG File" + $PPKGFileToInstall = $PPKGFiles[0].FullName + WriteLog "$PPKGFileToInstall will be used" +} +else { + Writelog 'No PPKG files found or PPKG not selected.' +} + +#Find Drivers +$Drivers = $USBDrive + "Drivers" +If (Test-Path -Path $Drivers) +{ + #Check if multiple driver folders found, if so, just select one folder to save time/space + $DriverFolders = Get-ChildItem -Path $Drivers + $DriverFoldersCount = $DriverFolders.count + If ($DriverFoldersCount -gt 1) + { + WriteLog "Found $DriverFoldersCount driver folders" + $array = @() + + for($i=0; $i -le $DriverFoldersCount -1; $i++){ + $Properties = [ordered]@{Number = $i + 1; Drivers = $DriverFolders[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, Drivers + do { + try { + $var = $true + [int]$DriversSelected = Read-Host 'Enter the set of drivers to install' + $DriversSelected = $DriversSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid driver folder number' + $var = $false + } + } until (($DriversSelected -le $DriverFoldersCount -1) -and $var) + + $Drivers = $array[$DriversSelected].Drivers + WriteLog "$Drivers was selected" + } + elseif ($DriverFoldersCount -eq 1) { + WriteLog "Found $DriverFoldersCount driver folder" + $Drivers = $DriverFolders.FullName + WriteLog "$Drivers will be installed" + } + else { + Writelog 'No driver folders found' + } +} + +#If you want to enable battery level checking, uncomment the line below as well as the Get-Battery function near the top of the script +#Get-Battery + +#Partition drive +Writelog 'Clean Disk' +#Start-Process -FilePath diskpart.exe -ArgumentList "/S $UEFIFFUPartitions" -Wait -ErrorAction Stop | Out-File $Logfile -Append +#Invoke-Process diskpart.exe "/S $UEFIFFUPartitions" +try { + $Disk = Get-Disk -Number $DiskID + $Disk | clear-disk -RemoveData -RemoveOEM -Confirm:$false +} +catch { + WriteLog 'Cleaning disk failed. Exiting' + throw $_ +} + +Writelog 'Cleaning Disk succeeded' + +#Apply FFU +WriteLog "Applying FFU to $PhysicalDeviceID" +WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID" +#In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen. +dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID +if($LASTEXITCODE -eq 0){ + WriteLog 'Successfully applied FFU' +} +else{ + Writelog "Failed to apply FFU - LastExitCode = $LASTEXITCODE also check dism.log on the USB drive for more info" + #Copy DISM log to USBDrive + invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" + exit +} + +#Remove recovery partition - this is needed in order to extend the Windows partition so it uses the full disk size. If dism /optimize-ffu worked, this wouldn't be needed +# $disk = get-disk -Number $DiskID +# $RecoveryPartition = $disk | get-partition | Where-Object {$_.type -eq 'Recovery'} +# if ($RecoveryPartition){ +# $RecoveryPartitionNumber = $RecoveryPartition.PartitionNumber +# if ($RecoveryPartitionNumber -eq 4){ +# try { +# WriteLog 'Removing recovery partition' +# Remove-partition -DiskNumber $DiskID -PartitionNumber $RecoveryPartitionNumber -Confirm:$false +# } +# catch { +# WriteLog 'Error removing recovery partition, exiting' +# throw $_ +# } +# } +# else{ +# WriteLog 'Recovery partition not partition 4. Script will exit. Please create the FFU with the recovery partition as the last partition. This is the default and recommended way.' +# exit +# } +# } + +#Extend Windows partition and create recovery partition +# Writelog 'Extending Windows partition' +# Invoke-Process diskpart.exe "/S $ExtendPartition" +# if($LASTEXITCODE -eq 0){ +# WriteLog 'Successfully extended Windows partition and created recovery partition' +# } +# else{ +# Writelog "Failed to extend Windows partition and/or create recovery partition - LastExitCode = $LASTEXITCODE" +# } + +#Copy modified WinRE if folder exists, else copy inbox WinRE +$WinRE = $USBDrive + "WinRE\winre.wim" +If (Test-Path -Path $WinRE) +{ + WriteLog 'Copying modified WinRE to Recovery directory' + Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y" + WriteLog 'Copying WinRE to Recovery directory succeeded' + WriteLog 'Registering location of recovery tools' + Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" + WriteLog 'Registering location of recovery tools succeeded' +} +# else +# { +# WriteLog 'Copying default WinRE to Recovery directory' +# Invoke-Process xcopy.exe "/h W:\Windows\System32\Recovery\Winre.wim R:\Recovery\WindowsRE\ /Y" +# WriteLog 'Copying WinRE to Recovery directory succeeded' +# WriteLog 'Registering location of recovery tools' +# Invoke-process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" +# WriteLog 'Registering location of recovery tools succeeded' +# } + +#Autopilot JSON +If ($APFileToInstall){ + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot" + Invoke-process xcopy.exe "$APFileToInstall W:\Windows\provisioning\autopilot\" + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot succeeded" + # Rename file in W:\Windows\Provisioning\Autopilot to AutoPilotConfigurationFile.json + try { + Rename-Item -Path "W:\Windows\Provisioning\Autopilot\$APFileName" -NewName 'W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json' + WriteLog "Renamed W:\Windows\Provisioning\Autopilot\$APFilename to W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json" + } + + catch{ + Writelog "Copying $APFileToInstall to W:\windows\provisioning\autopilot failed with error: $_" + throw $_ + } +} +#Apply PPKG +If ($PPKGFileToInstall){ + try { + #Make sure to delete any existing PPKG on the USB drive + Get-Childitem -Path $USBDrive\*.ppkg | ForEach-Object { + Remove-item -Path $_.FullName + } + WriteLog "Copying $PPKGFileToInstall to $USBDrive" + Invoke-process xcopy.exe "$PPKGFileToInstall $USBDrive" + WriteLog "Copying $PPKGFileToInstall to $USBDrive succeeded" + } + + catch{ + Writelog "Copying $PPKGFileToInstall to $USBDrive failed with error: $_" + throw $_ + } +} +#Set DeviceName +If ($PrefixToUse){ + try{ + $PantherDir = 'w:\windows\panther' + If (Test-Path -Path $PantherDir){ + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-process xcopy "$UnattendFile $PantherDir /Y" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + else{ + Writelog "$PantherDir doesn't exist, creating it" + New-Item -Path $PantherDir -ItemType Directory -Force + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-Process xcopy.exe "$UnattendFile $PantherDir" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + } + catch{ + WriteLog "Copying Unattend.xml to name device failed" + throw $_ + } +} + +#Add Drivers +#Some drivers can sometimes fail to copy and dism ends up with a non-zero error code. Invoke-process will throw and terminate in these instances. +If (Test-Path -Path $Drivers) +{ + WriteLog 'Copying drivers' + Write-Warning 'Copying Drivers - dism will pop a window with no progress. This can take a few minutes to complete. This is done so drivers are logged to the scriptlog.txt file. Please be patient.' + Invoke-process dism.exe "/image:W:\ /Add-Driver /Driver:""$Drivers"" /Recurse" + WriteLog 'Copying drivers succeeded' +} + +#Copy DISM log to USBDrive +WriteLog "Copying dism log to $USBDrive" +invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" +WriteLog "Copying dism log to $USBDrive succeeded" + + + + diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFUVM.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFUVM.ps1 new file mode 100644 index 0000000..3c74201 --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFUVM.ps1 @@ -0,0 +1,544 @@ +function Get-USBDrive(){ + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Removable' -and $_.FileSystemType -eq 'NTFS'}).DriveLetter + if ($null -eq $USBDriveLetter){ + #Must be using a fixed USB drive - difficult to grab drive letter from win32_diskdrive. Assume user followed instructions and used Deploy as the friendly name for partition + $USBDriveLetter = (Get-Volume | Where-Object {$_.FileSystemLabel -eq 'Deploy'}).DriveLetter + #If we didn't get the drive letter, stop the script. + if ($null -eq $USBDriveLetter){ + # WriteLog 'Cannot find USB drive letter - most likely using a fixed USB drive. Name the 2nd partition with the FFU files as Deploy so the script can grab the drive letter. Exiting' + Exit + } + + } + $USBDriveLetter = $USBDriveLetter + ":\" + return $USBDriveLetter +} + +function Get-HardDrive(){ + $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID + return $DeviceID +} + +function WriteLog($LogText){ + Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" +} + +function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){ + (Get-Content $DiskpartFile).Replace('disk 0', "disk $DiskID") | Set-Content -Path $DiskpartFile +} + +function Set-Computername($computername){ + [xml]$xml = Get-Content $UnattendFile + if($xml.unattend.settings.component.Count -ge 2){ + #Assumes that Computername is the first component element + $xml.unattend.settings.component[0].ComputerName = $computername + }else{ + $xml.unattend.settings.component.ComputerName = $computername + } + $xml.Save($UnattendFile) + return $computername +} + +function Invoke-Process { + [CmdletBinding(SupportsShouldProcess)] + param + ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$FilePath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$ArgumentList + ) + + $ErrorActionPreference = 'Stop' + + try { + $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)" + $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)" + + $startProcessParams = @{ + FilePath = $FilePath + ArgumentList = $ArgumentList + RedirectStandardError = $stdErrTempFile + RedirectStandardOutput = $stdOutTempFile + Wait = $true; + PassThru = $true; + NoNewWindow = $false; + } + if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) { + $cmd = Start-Process @startProcessParams + $cmdOutput = Get-Content -Path $stdOutTempFile -Raw + $cmdError = Get-Content -Path $stdErrTempFile -Raw + if ($cmd.ExitCode -ne 0) { + if ($cmdError) { + throw $cmdError.Trim() + } + if ($cmdOutput) { + throw $cmdOutput.Trim() + } + } else { + if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) { + # WriteLog $cmdOutput + } + } + } + } catch { + #$PSCmdlet.ThrowTerminatingError($_) + # WriteLog $_ + Write-Host 'Script failed - check scriptlog.txt on the USB drive for more info' + throw $_ + + } finally { + Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore + + } + +} + +# This function can be used in instances where battery level might matter (e.g. installing firmware for Surface). The problem is that WinPE doesn't have +# a driver for the battery installed, so you'll need to inject drivers, which can be tricky in that just injecting the battery driver might not be enough, +# you might also need other drivers that the battery driver is dependent on. +# function Get-Battery(){ +# while (($BattLev = (Get-CimInstance win32_battery).EstimatedChargeRemaining) -lt "35") +# { +# WriteLog "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Write-Host "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Start-Sleep 60 +# } + +# WriteLog "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# Write-Host "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# } + +#Get USB Drive and create log file +$LogFileName = 'ScriptLog.txt' +$USBDrive = Get-USBDrive +New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null +$LogFile = $USBDrive + $LogFilename +# WriteLog 'Begin Logging' + +#Find PhysicalDrive +$PhysicalDeviceID = Get-HardDrive +# WriteLog "Physical DeviceID is $PhysicalDeviceID" + +#Parse DiskID Number +$DiskID = $PhysicalDeviceID.substring($PhysicalDeviceID.length - 1,1) +# WriteLog "DiskID is $DiskID" + +#Modify diskpart answer files if DiskID not 0 +$UEFIFFUPartitions = 'x:\CreateUEFI-FFU-Partitions.txt' +$ExtendPartition = 'x:\ExtendPartition-UEFI.txt' + +If ($DiskID -ne '0'){ + # WriteLog 'DiskID is not 0. Need to modify diskpart answer files' + try { + Set-DiskpartAnswerFiles $UEFIFFUPartitions $DiskID + } + catch { + # WriteLog "Modifying $UEFIFFUPartitions failed with error: $_" + } + + try { + Set-DiskpartAnswerFiles $ExtendPartition $DiskID + } + catch { + # WriteLog "Modifying $ExtendPartition failed with error: $_" + } +} + +#Partition drive +# Writelog 'Creating partitions' +#Start-Process -FilePath diskpart.exe -ArgumentList "/S $UEFIFFUPartitions" -Wait -ErrorAction Stop | Out-File $Logfile -Append +Invoke-Process diskpart.exe "/S $UEFIFFUPartitions" +# Writelog 'Creating partitions succeeded' + +#Find FFU Files +[array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu) +$FFUCount = $FFUFiles.Count + +#If multiple FFUs found, ask which to install +If ($FFUCount -gt 1) { + # WriteLog "Found $FFUCount FFU Files" + $array = @() + + for($i=0;$i -le $FFUCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; FFUFile = $FFUFiles[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, FFUFile + do { + try { + $var = $true + [int]$FFUSelected = Read-Host 'Enter the FFU number to install' + $FFUSelected = $FFUSelected -1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid FFU number' + $var = $false + } + } until (($FFUSelected -le $FFUCount -1) -and $var) + + $FFUFileToInstall = $array[$FFUSelected].FFUFile + # WriteLog "$FFUFileToInstall was selected" +} +elseif ($FFUCount -eq 1) { + # WriteLog "Found $FFUCount FFU File" + $FFUFileToInstall = $FFUFiles[0].FullName + # WriteLog "$FFUFileToInstall will be installed" +} +else { + # Writelog 'No FFU files found' + Write-Host 'No FFU files found' + Exit +} + +#FindAP +$APFolder = $USBDrive + "Autopilot\" +If (Test-Path -Path $APFolder){ + [array]$APFiles = @(Get-ChildItem -Path $APFolder*.json) + $APFilesCount = $APFiles.Count + if ($APFilesCount -ge 1){ + $autopilot = $true + } +} + + +#FindPPKG +$PPKGFolder = $USBDrive + "PPKG\" +if (Test-Path -Path $PPKGFolder){ + [array]$PPKGFiles = @(Get-ChildItem -Path $PPKGFolder*.ppkg) + $PPKGFilesCount = $PPKGFiles.Count + if ($PPKGFilesCount -ge 1){ + $PPKG = $true + } +} + +#FindUnattend +$UnattendFolder = $USBDrive + "unattend\" +$UnattendFilePath = $UnattendFolder + "unattend.xml" +$UnattendPrefixPath = $UnattendFolder + "prefixes.txt" +If (Test-Path -Path $UnattendFilePath){ + $UnattendFile = Get-ChildItem -Path $UnattendFilePath + If ($UnattendFile){ + $Unattend = $true + } +} +If (Test-Path -Path $UnattendPrefixPath){ + $UnattendPrefixFile = Get-ChildItem -Path $UnattendPrefixPath + If ($UnattendPrefixFile){ + $UnattendPrefix = $true + } +} + +#Ask for device name if unattend exists +if ($Unattend -and $UnattendPrefix){ + # Writelog 'Unattend file found with prefixes.txt. Getting prefixes.' + $UnattendPrefixes = @(Get-content $UnattendPrefixFile) + $UnattendPrefixCount = $UnattendPrefixes.Count + If ($UnattendPrefixCount -gt 1) { + # WriteLog "Found $UnattendPrefixCount Prefixes" + $array = @() + for($i=0;$i -le $UnattendPrefixCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; DeviceNamePrefix = $UnattendPrefixes[$i]} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, DeviceNamePrefix + do { + try { + $var = $true + [int]$PrefixSelected = Read-Host 'Enter the prefix number to use for the device name' + $PrefixSelected = $PrefixSelected -1 + } + catch { + Write-Host 'Input was not in correct format. Please enter a valid prefix number' + $var = $false + } + } until (($PrefixSelected -le $UnattendPrefixCount -1) -and $var) + $PrefixToUse = $array[$PrefixSelected].DeviceNamePrefix + # WriteLog "$PrefixToUse was selected" + } + elseif ($UnattendPrefixCount -eq 1) { + # WriteLog "Found $UnattendPrefixCount Prefix" + $PrefixToUse = $UnattendPrefixes[0] + # WriteLog "Will use $PrefixToUse as device name prefix" + } + #Get serial number to append. This can make names longer than 15 characters. Trim any leading or trailing whitespace + $serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim() + #Combine prefix with serial + $computername = $PrefixToUse + $serial + #If computername is longer than 15 characters, reduce to 15. Sysprep/unattend doesn't like ComputerName being longer than 15 characters even though Windows accepts it + If ($computername.Length -gt 15){ + $computername = $computername.substring(0,15) + } + $computername = Set-Computername($computername) + # Writelog "Computer name set to $computername" +} +elseif($Unattend){ + # Writelog 'Unattend file found with no prefixes.txt, asking for name' + [string]$computername = Read-Host 'Enter device name' + Set-Computername($computername) + # Writelog "Computer name set to $computername" +} +else { + # WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.' +} + +#If both AP and PPKG folder found with files, ask which to use. +If($autopilot -eq $true -and $PPKG -eq $true){ + # WriteLog 'Both PPKG and Autopilot json files found' + Write-Host 'Both Autopilot JSON files and Provisioning packages were found.' + do { + try { + $var = $true + [int]$APorPPKG = Read-Host 'Enter 1 for Autopilot or 2 for Provisioning Package' + } + + catch { + Write-Host 'Incorrect value. Please enter 1 for Autopilot or 2 for Provisioning Package' + $var = $false + } + } until (($APorPPKG -gt 0 -and $APorPPKG -lt 3) -and $var) + If ($APorPPKG -eq 1){ + $PPKG = $false + } + else{ + $autopilot = $false + } +} + +#If multiple AP json files found, ask which to install +If ($APFilesCount -gt 1 -and $autopilot -eq $true) { + # WriteLog "Found $APFilesCount Autopilot json Files" + $array = @() + + for($i=0;$i -le $APFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; APFile = $APFiles[$i].FullName; APFileName = $APFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, APFileName + do { + try { + $var = $true + [int]$APFileSelected = Read-Host 'Enter the AP json file number to install' + $APFileSelected = $APFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid AP json file number' + $var = $false + } + } until (($APFileSelected -le $APFilesCount -1) -and $var) + + $APFileToInstall = $array[$APFileSelected].APFile + $APFileName = $array[$APFileSelected].APFileName + # WriteLog "$APFileToInstall was selected" +} +elseif ($APFilesCount -eq 1 -and $autopilot -eq $true) { + # WriteLog "Found $APFilesCount AP File" + $APFileToInstall = $APFiles[0].FullName + $APFileName = $APFiles[0].Name + # WriteLog "$APFileToInstall will be copied" +} +else { + # Writelog 'No AP files found or AP was not selected' +} + +#If multiple PPKG files found, ask which to install +If ($PPKGFilesCount -gt 1 -and $PPKG -eq $true) { + # WriteLog "Found $PPKGFilesCount PPKG Files" + $array = @() + + for($i=0;$i -le $PPKGFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; PPKGFile = $PPKGFiles[$i].FullName; PPKGFileName = $PPKGFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, PPKGFileName + do { + try { + $var = $true + [int]$PPKGFileSelected = Read-Host 'Enter the PPKG file number to install' + $PPKGFileSelected = $PPKGFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid PPKG file number' + $var = $false + } + } until (($PPKGFileSelected -le $PPKGFilesCount -1) -and $var) + + $PPKGFileToInstall = $array[$PPKGFileSelected].PPKGFile + # WriteLog "$PPKGFileToInstall was selected" +} +elseif ($PPKGFilesCount -eq 1 -and $PPKG -eq $true) { + # WriteLog "Found $PPKGFilesCount PPKG File" + $PPKGFileToInstall = $PPKGFiles[0].FullName + # WriteLog "$PPKGFileToInstall will be used" +} +else { + # Writelog 'No PPKG files found or PPKG not selected.' +} + +#Find Drivers +$Drivers = $USBDrive + "Drivers" +If (Test-Path -Path $Drivers) +{ + #Check if multiple driver folders found, if so, just select one folder to save time/space + $DriverFolders = Get-ChildItem -Path $Drivers + $DriverFoldersCount = $DriverFolders.count + If ($DriverFoldersCount -gt 1) + { + # WriteLog "Found $DriverFoldersCount driver folders" + $array = @() + + for($i=0; $i -le $DriverFoldersCount -1; $i++){ + $Properties = [ordered]@{Number = $i + 1; Drivers = $DriverFolders[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, Drivers + do { + try { + $var = $true + [int]$DriversSelected = Read-Host 'Enter the set of drivers to install' + $DriversSelected = $DriversSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid driver folder number' + $var = $false + } + } until (($DriversSelected -le $DriverFoldersCount -1) -and $var) + + $Drivers = $array[$DriversSelected].Drivers + # WriteLog "$Drivers was selected" + } + elseif ($DriverFoldersCount -eq 1) { + # WriteLog "Found $DriverFoldersCount driver folder" + $Drivers = $DriverFolders.FullName + # WriteLog "$Drivers will be installed" + } + else { + # Writelog 'No driver folders found' + } +} + +#If you want to enable battery level checking, uncomment the line below as well as the Get-Battery function near the top of the script +#Get-Battery + +#Apply FFU +# WriteLog "Applying FFU to $PhysicalDeviceID" +# WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID" +#In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen. +dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID +if($LASTEXITCODE -eq 0){ + # WriteLog 'Successfully applied FFU' +} +else{ + # Writelog "Failed to apply FFU - LastExitCode = $LASTEXITCODE also check dism.log on the USB drive for more info" + #Copy DISM log to USBDrive + invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" + exit +} + +#Extend Windows partition and create recovery partition +# Writelog 'Extending Windows partition' +Invoke-Process diskpart.exe "/S $ExtendPartition" +# Writelog 'Extending Windows partition succeeded' + + +#Copy modified WinRE if folder exists, else copy inbox WinRE +$WinRE = $USBDrive + "WinRE\winre.wim" +If (Test-Path -Path $WinRE) +{ + # WriteLog 'Copying modified WinRE to Recovery directory' + Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y" + # WriteLog 'Copying WinRE to Recovery directory succeeded' + # WriteLog 'Registering location of recovery tools' + Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" + # WriteLog 'Registering location of recovery tools succeeded' +} +else { + # WriteLog 'Copying default WinRE to Recovery directory' + Invoke-Process xcopy.exe "/h W:\Windows\System32\Recovery\Winre.wim R:\Recovery\WindowsRE\ /Y" + # WriteLog 'Copying WinRE to Recovery directory succeeded' + # WriteLog 'Registering location of recovery tools' + Invoke-process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" + # WriteLog 'Registering location of recovery tools succeeded' +} + +#Autopilot JSON +If ($APFileToInstall){ + # WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot" + Invoke-process xcopy.exe "$APFileToInstall W:\Windows\provisioning\autopilot\" + # WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot succeeded" + # Rename file in W:\Windows\Provisioning\Autopilot to AutoPilotConfigurationFile.json + try { + Rename-Item -Path "W:\Windows\Provisioning\Autopilot\$APFileName" -NewName 'W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json' + # WriteLog "Renamed W:\Windows\Provisioning\Autopilot\$APFilename to W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json" + } + + catch{ + # Writelog "Copying $APFileToInstall to W:\windows\provisioning\autopilot failed with error: $_" + throw $_ + } +} +#Apply PPKG +If ($PPKGFileToInstall){ + try { + #Make sure to delete any existing PPKG on the USB drive + Get-Childitem -Path $USBDrive\*.ppkg | ForEach-Object { + Remove-item -Path $_.FullName + } + # WriteLog "Copying $PPKGFileToInstall to $USBDrive" + Invoke-process xcopy.exe "$PPKGFileToInstall $USBDrive" + # WriteLog "Copying $PPKGFileToInstall to $USBDrive succeeded" + } + + catch{ + # Writelog "Copying $PPKGFileToInstall to $USBDrive failed with error: $_" + throw $_ + } +} +#Set DeviceName +If ($PrefixToUse){ + try{ + $PantherDir = 'w:\windows\panther' + If (Test-Path -Path $PantherDir){ + # Writelog "Copying $UnattendFile to $PantherDir" + Invoke-process xcopy "$UnattendFile $PantherDir /Y" + # WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + else{ + # Writelog "$PantherDir doesn't exist, creating it" + New-Item -Path $PantherDir -ItemType Directory -Force + # Writelog "Copying $UnattendFile to $PantherDir" + Invoke-Process xcopy.exe "$UnattendFile $PantherDir" + # WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + } + catch{ + # WriteLog "Copying Unattend.xml to name device failed" + throw $_ + } +} + +#Add Drivers +#Some drivers can sometimes fail to copy and dism ends up with a non-zero error code. Invoke-process will throw and terminate in these instances. +If (Test-Path -Path $Drivers) +{ + # WriteLog 'Copying drivers' + Write-Host 'Copying Drivers - dism will pop a window with no progress. This can take a few minutes to complete. Please be patient. ' + Invoke-process dism.exe "/image:W:\ /Add-Driver /Driver:""$Drivers"" /Recurse" + # WriteLog 'Copying drivers succeeded' +} + +#Copy DISM log to USBDrive +# WriteLog "Copying dism log to $USBDrive" +# invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" +# WriteLog "Copying dism log to $USBDrive succeeded" + + + + diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU_last.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU_last.ps1 new file mode 100644 index 0000000..b93e7bb --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU_last.ps1 @@ -0,0 +1,579 @@ +function Get-USBDrive(){ + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Removable' -and $_.FileSystemType -eq 'NTFS'}).DriveLetter + if ($null -eq $USBDriveLetter){ + #Must be using a fixed USB drive - difficult to grab drive letter from win32_diskdrive. Assume user followed instructions and used Deploy as the friendly name for partition + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Fixed' -and $_.FileSystemType -eq 'NTFS' -and $_.FileSystemLabel -eq 'Deploy'}).DriveLetter + #If we didn't get the drive letter, stop the script. + if ($null -eq $USBDriveLetter){ + WriteLog 'Cannot find USB drive letter - most likely using a fixed USB drive. Name the 2nd partition with the FFU files as Deploy so the script can grab the drive letter. Exiting' + Exit + } + + } + $USBDriveLetter = $USBDriveLetter + ":\" + return $USBDriveLetter +} + +function Get-HardDrive(){ + $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID + return $DeviceID +} + +function WriteLog($LogText){ + Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" +} + +function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){ + (Get-Content $DiskpartFile).Replace('disk 0', "disk $DiskID") | Set-Content -Path $DiskpartFile +} + +function Set-Computername($computername){ + [xml]$xml = Get-Content $UnattendFile + if($xml.unattend.settings.component.Count -ge 2){ + #Assumes that Computername is the first component element + $xml.unattend.settings.component[0].ComputerName = $computername + }else{ + $xml.unattend.settings.component.ComputerName = $computername + } + $xml.Save($UnattendFile) + return $computername +} + +function Invoke-Process { + [CmdletBinding(SupportsShouldProcess)] + param + ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$FilePath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$ArgumentList + ) + + $ErrorActionPreference = 'Stop' + + try { + $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)" + $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)" + + $startProcessParams = @{ + FilePath = $FilePath + ArgumentList = $ArgumentList + RedirectStandardError = $stdErrTempFile + RedirectStandardOutput = $stdOutTempFile + Wait = $true; + PassThru = $true; + NoNewWindow = $false; + } + if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) { + $cmd = Start-Process @startProcessParams + $cmdOutput = Get-Content -Path $stdOutTempFile -Raw + $cmdError = Get-Content -Path $stdErrTempFile -Raw + if ($cmd.ExitCode -ne 0) { + if ($cmdError) { + throw $cmdError.Trim() + } + if ($cmdOutput) { + throw $cmdOutput.Trim() + } + } else { + if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) { + WriteLog $cmdOutput + } + } + } + } catch { + #$PSCmdlet.ThrowTerminatingError($_) + WriteLog $_ + Write-Host 'Script failed - check scriptlog.txt on the USB drive for more info' + throw $_ + + } finally { + Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore + + } + +} + +# This function can be used in instances where battery level might matter (e.g. installing firmware for Surface). The problem is that WinPE doesn't have +# a driver for the battery installed, so you'll need to inject drivers, which can be tricky because just injecting the battery driver might not be enough, +# you might also need other drivers that the battery driver is dependent on. +# function Get-Battery(){ +# while (($BattLev = (Get-CimInstance win32_battery).EstimatedChargeRemaining) -lt "35") +# { +# WriteLog "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Write-Host "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Start-Sleep 60 +# } + +# WriteLog "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# Write-Host "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# } + +#Get USB Drive and create log file +$LogFileName = 'ScriptLog.txt' +$USBDrive = Get-USBDrive +New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null +$LogFile = $USBDrive + $LogFilename +WriteLog 'Begin Logging' + +#Find PhysicalDrive +$PhysicalDeviceID = Get-HardDrive +WriteLog "Physical DeviceID is $PhysicalDeviceID" + +#Parse DiskID Number +$DiskID = $PhysicalDeviceID.substring($PhysicalDeviceID.length - 1,1) +WriteLog "DiskID is $DiskID" + +#Modify diskpart answer files if DiskID not 0 +$UEFIFFUPartitions = 'x:\CreateUEFI-FFU-Partitions.txt' +$ExtendPartition = 'x:\ExtendPartition-UEFI.txt' + +If ($DiskID -ne '0'){ + WriteLog 'DiskID is not 0. Need to modify diskpart answer files' + try { + Set-DiskpartAnswerFiles $UEFIFFUPartitions $DiskID + } + catch { + WriteLog "Modifying $UEFIFFUPartitions failed with error: $_" + } + + try { + Set-DiskpartAnswerFiles $ExtendPartition $DiskID + } + catch { + WriteLog "Modifying $ExtendPartition failed with error: $_" + } +} + +#Find FFU Files +[array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu) +$FFUCount = $FFUFiles.Count + +#If multiple FFUs found, ask which to install +If ($FFUCount -gt 1) { + WriteLog "Found $FFUCount FFU Files" + $array = @() + + for($i=0;$i -le $FFUCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; FFUFile = $FFUFiles[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, FFUFile + do { + try { + $var = $true + [int]$FFUSelected = Read-Host 'Enter the FFU number to install' + $FFUSelected = $FFUSelected -1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid FFU number' + $var = $false + } + } until (($FFUSelected -le $FFUCount -1) -and $var) + + $FFUFileToInstall = $array[$FFUSelected].FFUFile + WriteLog "$FFUFileToInstall was selected" +} +elseif ($FFUCount -eq 1) { + WriteLog "Found $FFUCount FFU File" + $FFUFileToInstall = $FFUFiles[0].FullName + WriteLog "$FFUFileToInstall will be installed" +} +else { + Writelog 'No FFU files found' + Write-Host 'No FFU files found' + Exit +} + +#FindAP +$APFolder = $USBDrive + "Autopilot\" +If (Test-Path -Path $APFolder){ + [array]$APFiles = @(Get-ChildItem -Path $APFolder*.json) + $APFilesCount = $APFiles.Count + if ($APFilesCount -ge 1){ + $autopilot = $true + } +} + + +#FindPPKG +$PPKGFolder = $USBDrive + "PPKG\" +if (Test-Path -Path $PPKGFolder){ + [array]$PPKGFiles = @(Get-ChildItem -Path $PPKGFolder*.ppkg) + $PPKGFilesCount = $PPKGFiles.Count + if ($PPKGFilesCount -ge 1){ + $PPKG = $true + } +} + +#FindUnattend +$UnattendFolder = $USBDrive + "unattend\" +$UnattendFilePath = $UnattendFolder + "unattend.xml" +$UnattendPrefixPath = $UnattendFolder + "prefixes.txt" +If (Test-Path -Path $UnattendFilePath){ + $UnattendFile = Get-ChildItem -Path $UnattendFilePath + If ($UnattendFile){ + $Unattend = $true + } +} +If (Test-Path -Path $UnattendPrefixPath){ + $UnattendPrefixFile = Get-ChildItem -Path $UnattendPrefixPath + If ($UnattendPrefixFile){ + $UnattendPrefix = $true + } +} + +#Ask for device name if unattend exists +if ($Unattend -and $UnattendPrefix){ + Writelog 'Unattend file found with prefixes.txt. Getting prefixes.' + $UnattendPrefixes = @(Get-content $UnattendPrefixFile) + $UnattendPrefixCount = $UnattendPrefixes.Count + If ($UnattendPrefixCount -gt 1) { + WriteLog "Found $UnattendPrefixCount Prefixes" + $array = @() + for($i=0;$i -le $UnattendPrefixCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; DeviceNamePrefix = $UnattendPrefixes[$i]} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, DeviceNamePrefix + do { + try { + $var = $true + [int]$PrefixSelected = Read-Host 'Enter the prefix number to use for the device name' + $PrefixSelected = $PrefixSelected -1 + } + catch { + Write-Host 'Input was not in correct format. Please enter a valid prefix number' + $var = $false + } + } until (($PrefixSelected -le $UnattendPrefixCount -1) -and $var) + $PrefixToUse = $array[$PrefixSelected].DeviceNamePrefix + WriteLog "$PrefixToUse was selected" + } + elseif ($UnattendPrefixCount -eq 1) { + WriteLog "Found $UnattendPrefixCount Prefix" + $PrefixToUse = $UnattendPrefixes[0] + WriteLog "Will use $PrefixToUse as device name prefix" + } + #Get serial number to append. This can make names longer than 15 characters. Trim any leading or trailing whitespace + $serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim() + #Combine prefix with serial + $computername = $PrefixToUse + $serial + #If computername is longer than 15 characters, reduce to 15. Sysprep/unattend doesn't like ComputerName being longer than 15 characters even though Windows accepts it + If ($computername.Length -gt 15){ + $computername = $computername.substring(0,15) + } + $computername = Set-Computername($computername) + Writelog "Computer name set to $computername" +} +elseif($Unattend){ + Writelog 'Unattend file found with no prefixes.txt, asking for name' + [string]$computername = Read-Host 'Enter device name' + Set-Computername($computername) + Writelog "Computer name set to $computername" +} +else { + WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.' +} + +#If both AP and PPKG folder found with files, ask which to use. +If($autopilot -eq $true -and $PPKG -eq $true){ + WriteLog 'Both PPKG and Autopilot json files found' + Write-Host 'Both Autopilot JSON files and Provisioning packages were found.' + do { + try { + $var = $true + [int]$APorPPKG = Read-Host 'Enter 1 for Autopilot or 2 for Provisioning Package' + } + + catch { + Write-Host 'Incorrect value. Please enter 1 for Autopilot or 2 for Provisioning Package' + $var = $false + } + } until (($APorPPKG -gt 0 -and $APorPPKG -lt 3) -and $var) + If ($APorPPKG -eq 1){ + $PPKG = $false + } + else{ + $autopilot = $false + } +} + +#If multiple AP json files found, ask which to install +If ($APFilesCount -gt 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount Autopilot json Files" + $array = @() + + for($i=0;$i -le $APFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; APFile = $APFiles[$i].FullName; APFileName = $APFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, APFileName + do { + try { + $var = $true + [int]$APFileSelected = Read-Host 'Enter the AP json file number to install' + $APFileSelected = $APFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid AP json file number' + $var = $false + } + } until (($APFileSelected -le $APFilesCount -1) -and $var) + + $APFileToInstall = $array[$APFileSelected].APFile + $APFileName = $array[$APFileSelected].APFileName + WriteLog "$APFileToInstall was selected" +} +elseif ($APFilesCount -eq 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount AP File" + $APFileToInstall = $APFiles[0].FullName + $APFileName = $APFiles[0].Name + WriteLog "$APFileToInstall will be copied" +} +else { + Writelog 'No AP files found or AP was not selected' +} + +#If multiple PPKG files found, ask which to install +If ($PPKGFilesCount -gt 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG Files" + $array = @() + + for($i=0;$i -le $PPKGFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; PPKGFile = $PPKGFiles[$i].FullName; PPKGFileName = $PPKGFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, PPKGFileName + do { + try { + $var = $true + [int]$PPKGFileSelected = Read-Host 'Enter the PPKG file number to install' + $PPKGFileSelected = $PPKGFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid PPKG file number' + $var = $false + } + } until (($PPKGFileSelected -le $PPKGFilesCount -1) -and $var) + + $PPKGFileToInstall = $array[$PPKGFileSelected].PPKGFile + WriteLog "$PPKGFileToInstall was selected" +} +elseif ($PPKGFilesCount -eq 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG File" + $PPKGFileToInstall = $PPKGFiles[0].FullName + WriteLog "$PPKGFileToInstall will be used" +} +else { + Writelog 'No PPKG files found or PPKG not selected.' +} + +#Find Drivers +$Drivers = $USBDrive + "Drivers" +If (Test-Path -Path $Drivers) +{ + #Check if multiple driver folders found, if so, just select one folder to save time/space + $DriverFolders = Get-ChildItem -Path $Drivers + $DriverFoldersCount = $DriverFolders.count + If ($DriverFoldersCount -gt 1) + { + WriteLog "Found $DriverFoldersCount driver folders" + $array = @() + + for($i=0; $i -le $DriverFoldersCount -1; $i++){ + $Properties = [ordered]@{Number = $i + 1; Drivers = $DriverFolders[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, Drivers + do { + try { + $var = $true + [int]$DriversSelected = Read-Host 'Enter the set of drivers to install' + $DriversSelected = $DriversSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid driver folder number' + $var = $false + } + } until (($DriversSelected -le $DriverFoldersCount -1) -and $var) + + $Drivers = $array[$DriversSelected].Drivers + WriteLog "$Drivers was selected" + } + elseif ($DriverFoldersCount -eq 1) { + WriteLog "Found $DriverFoldersCount driver folder" + $Drivers = $DriverFolders.FullName + WriteLog "$Drivers will be installed" + } + else { + Writelog 'No driver folders found' + } +} + +#If you want to enable battery level checking, uncomment the line below as well as the Get-Battery function near the top of the script +#Get-Battery + +#Partition drive +Writelog 'Clean Disk' +#Start-Process -FilePath diskpart.exe -ArgumentList "/S $UEFIFFUPartitions" -Wait -ErrorAction Stop | Out-File $Logfile -Append +#Invoke-Process diskpart.exe "/S $UEFIFFUPartitions" +try { + $Disk = Get-Disk -Number $DiskID + $Disk | clear-disk -RemoveData -RemoveOEM -Confirm:$false +} +catch { + WriteLog 'Cleaning disk failed. Exiting' + throw $_ +} + +Writelog 'Cleaning Disk succeeded' + +#Apply FFU +WriteLog "Applying FFU to $PhysicalDeviceID" +WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID" +#In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen. +dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID +if($LASTEXITCODE -eq 0){ + WriteLog 'Successfully applied FFU' +} +else{ + Writelog "Failed to apply FFU - LastExitCode = $LASTEXITCODE also check dism.log on the USB drive for more info" + #Copy DISM log to USBDrive + invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" + exit +} + +#Remove recovery partition - this is needed in order to extend the Windows partition so it uses the full disk size. If dism /optimize-ffu worked, this wouldn't be needed +$disk = get-disk -Number $DiskID +$RecoveryPartition = $disk | get-partition | Where-Object {$_.type -eq 'Recovery'} +if ($RecoveryPartition){ + $RecoveryPartitionNumber = $RecoveryPartition.PartitionNumber + if ($RecoveryPartitionNumber -eq 4){ + try { + WriteLog 'Removing recovery partition' + Remove-partition -DiskNumber $DiskID -PartitionNumber $RecoveryPartitionNumber -Confirm:$false + } + catch { + WriteLog 'Error removing recovery partition, exiting' + throw $_ + } + } + else{ + WriteLog 'Recovery partition not partition 4. Script will exit. Please create the FFU with the recovery partition as the last partition. This is the default and recommended way.' + exit + } +} + +#Extend Windows partition and create recovery partition +Writelog 'Extending Windows partition' +Invoke-Process diskpart.exe "/S $ExtendPartition" +if($LASTEXITCODE -eq 0){ + WriteLog 'Successfully extended Windows partition and created recovery partition' +} +else{ + Writelog "Failed to extend Windows partition and/or create recovery partition - LastExitCode = $LASTEXITCODE" +} + +#Copy modified WinRE if folder exists, else copy inbox WinRE +$WinRE = $USBDrive + "WinRE\winre.wim" +If (Test-Path -Path $WinRE) +{ + WriteLog 'Copying modified WinRE to Recovery directory' + Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y" + WriteLog 'Copying WinRE to Recovery directory succeeded' + WriteLog 'Registering location of recovery tools' + Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" + WriteLog 'Registering location of recovery tools succeeded' +} +else +{ + WriteLog 'Copying default WinRE to Recovery directory' + Invoke-Process xcopy.exe "/h W:\Windows\System32\Recovery\Winre.wim R:\Recovery\WindowsRE\ /Y" + WriteLog 'Copying WinRE to Recovery directory succeeded' + WriteLog 'Registering location of recovery tools' + Invoke-process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" + WriteLog 'Registering location of recovery tools succeeded' +} + +#Autopilot JSON +If ($APFileToInstall){ + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot" + Invoke-process xcopy.exe "$APFileToInstall W:\Windows\provisioning\autopilot\" + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot succeeded" + # Rename file in W:\Windows\Provisioning\Autopilot to AutoPilotConfigurationFile.json + try { + Rename-Item -Path "W:\Windows\Provisioning\Autopilot\$APFileName" -NewName 'W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json' + WriteLog "Renamed W:\Windows\Provisioning\Autopilot\$APFilename to W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json" + } + + catch{ + Writelog "Copying $APFileToInstall to W:\windows\provisioning\autopilot failed with error: $_" + throw $_ + } +} +#Apply PPKG +If ($PPKGFileToInstall){ + try { + #Make sure to delete any existing PPKG on the USB drive + Get-Childitem -Path $USBDrive\*.ppkg | ForEach-Object { + Remove-item -Path $_.FullName + } + WriteLog "Copying $PPKGFileToInstall to $USBDrive" + Invoke-process xcopy.exe "$PPKGFileToInstall $USBDrive" + WriteLog "Copying $PPKGFileToInstall to $USBDrive succeeded" + } + + catch{ + Writelog "Copying $PPKGFileToInstall to $USBDrive failed with error: $_" + throw $_ + } +} +#Set DeviceName +If ($PrefixToUse){ + try{ + $PantherDir = 'w:\windows\panther' + If (Test-Path -Path $PantherDir){ + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-process xcopy "$UnattendFile $PantherDir /Y" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + else{ + Writelog "$PantherDir doesn't exist, creating it" + New-Item -Path $PantherDir -ItemType Directory -Force + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-Process xcopy.exe "$UnattendFile $PantherDir" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + } + catch{ + WriteLog "Copying Unattend.xml to name device failed" + throw $_ + } +} + +#Add Drivers +#Some drivers can sometimes fail to copy and dism ends up with a non-zero error code. Invoke-process will throw and terminate in these instances. +If (Test-Path -Path $Drivers) +{ + WriteLog 'Copying drivers' + Write-Warning 'Copying Drivers - dism will pop a window with no progress. This can take a few minutes to complete. This is done so drivers are logged to the scriptlog.txt file. Please be patient.' + Invoke-process dism.exe "/image:W:\ /Add-Driver /Driver:""$Drivers"" /Recurse" + WriteLog 'Copying drivers succeeded' +} + +#Copy DISM log to USBDrive +WriteLog "Copying dism log to $USBDrive" +invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" +WriteLog "Copying dism log to $USBDrive succeeded" + + + + diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFUv2.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFUv2.ps1 new file mode 100644 index 0000000..66274de --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFUv2.ps1 @@ -0,0 +1,555 @@ +function Get-USBDrive(){ + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Removable' -and $_.FileSystemType -eq 'NTFS'}).DriveLetter + if ($null -eq $USBDriveLetter){ + #Must be using a fixed USB drive - difficult to grab drive letter from win32_diskdrive. Assume user followed instructions and used Deploy as the friendly name for partition + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Fixed' -and $_.FileSystemType -eq 'NTFS' -and $_.FileSystemLabel -eq 'Deploy'}).DriveLetter + #If we didn't get the drive letter, stop the script. + if ($null -eq $USBDriveLetter){ + WriteLog 'Cannot find USB drive letter - most likely using a fixed USB drive. Name the 2nd partition with the FFU files as Deploy so the script can grab the drive letter. Exiting' + Exit + } + + } + $USBDriveLetter = $USBDriveLetter + ":\" + return $USBDriveLetter +} + +function Get-HardDrive(){ + $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID + return $DeviceID +} + +function WriteLog($LogText){ + Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" +} + +function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){ + (Get-Content $DiskpartFile).Replace('disk 0', "disk $DiskID") | Set-Content -Path $DiskpartFile +} + +function Set-Computername($computername){ + [xml]$xml = Get-Content $UnattendFile + if($xml.unattend.settings.component.Count -ge 2){ + #Assumes that Computername is the first component element + $xml.unattend.settings.component[0].ComputerName = $computername + }else{ + $xml.unattend.settings.component.ComputerName = $computername + } + $xml.Save($UnattendFile) + return $computername +} + +function Invoke-Process { + [CmdletBinding(SupportsShouldProcess)] + param + ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$FilePath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$ArgumentList + ) + + $ErrorActionPreference = 'Stop' + + try { + $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)" + $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)" + + $startProcessParams = @{ + FilePath = $FilePath + ArgumentList = $ArgumentList + RedirectStandardError = $stdErrTempFile + RedirectStandardOutput = $stdOutTempFile + Wait = $true; + PassThru = $true; + NoNewWindow = $false; + } + if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) { + $cmd = Start-Process @startProcessParams + $cmdOutput = Get-Content -Path $stdOutTempFile -Raw + $cmdError = Get-Content -Path $stdErrTempFile -Raw + if ($cmd.ExitCode -ne 0) { + if ($cmdError) { + throw $cmdError.Trim() + } + if ($cmdOutput) { + throw $cmdOutput.Trim() + } + } else { + if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) { + WriteLog $cmdOutput + } + } + } + } catch { + #$PSCmdlet.ThrowTerminatingError($_) + WriteLog $_ + Write-Host 'Script failed - check scriptlog.txt on the USB drive for more info' + throw $_ + + } finally { + Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore + + } + +} + +# This function can be used in instances where battery level might matter (e.g. installing firmware for Surface). The problem is that WinPE doesn't have +# a driver for the battery installed, so you'll need to inject drivers, which can be tricky in that just injecting the battery driver might not be enough, +# you might also need other drivers that the battery driver is dependent on. +# function Get-Battery(){ +# while (($BattLev = (Get-CimInstance win32_battery).EstimatedChargeRemaining) -lt "35") +# { +# WriteLog "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Write-Host "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Start-Sleep 60 +# } + +# WriteLog "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# Write-Host "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# } + +#Get USB Drive and create log file +$LogFileName = 'ScriptLog.txt' +$USBDrive = Get-USBDrive +New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null +$LogFile = $USBDrive + $LogFilename +WriteLog 'Begin Logging' + +#Find PhysicalDrive +$PhysicalDeviceID = Get-HardDrive +WriteLog "Physical DeviceID is $PhysicalDeviceID" + +#Parse DiskID Number +$DiskID = $PhysicalDeviceID.substring($PhysicalDeviceID.length - 1,1) +WriteLog "DiskID is $DiskID" + +#Modify diskpart answer files if DiskID not 0 +$UEFIFFUPartitions = 'x:\CreateUEFI-FFU-Partitions.txt' +$ExtendPartition = 'x:\ExtendPartition-UEFI.txt' +$RecoveryPartition = 'x:\RecoveryPartition-UEFI.txt' + +If ($DiskID -ne '0'){ + WriteLog 'DiskID is not 0. Need to modify diskpart answer files' + try { + Set-DiskpartAnswerFiles $UEFIFFUPartitions $DiskID + } + catch { + WriteLog "Modifying $UEFIFFUPartitions failed with error: $_" + } + + try { + Set-DiskpartAnswerFiles $ExtendPartition $DiskID + } + catch { + WriteLog "Modifying $ExtendPartition failed with error: $_" + } + try { + Set-DiskpartAnswerFiles $RecoveryPartition $DiskID + } + catch { + WriteLog "Modifying $RecoveryPartition failed with error: $_" + } +} + +#Partition drive +Writelog 'Creating partitions' +#Start-Process -FilePath diskpart.exe -ArgumentList "/S $UEFIFFUPartitions" -Wait -ErrorAction Stop | Out-File $Logfile -Append +Invoke-Process diskpart.exe "/S $UEFIFFUPartitions" +Writelog 'Creating partitions succeeded' + +#Find FFU Files +[array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu) +$FFUCount = $FFUFiles.Count + +#If multiple FFUs found, ask which to install +If ($FFUCount -gt 1) { + WriteLog "Found $FFUCount FFU Files" + $array = @() + + for($i=0;$i -le $FFUCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; FFUFile = $FFUFiles[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, FFUFile + do { + try { + $var = $true + [int]$FFUSelected = Read-Host 'Enter the FFU number to install' + $FFUSelected = $FFUSelected -1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid FFU number' + $var = $false + } + } until (($FFUSelected -le $FFUCount -1) -and $var) + + $FFUFileToInstall = $array[$FFUSelected].FFUFile + WriteLog "$FFUFileToInstall was selected" +} +elseif ($FFUCount -eq 1) { + WriteLog "Found $FFUCount FFU File" + $FFUFileToInstall = $FFUFiles[0].FullName + WriteLog "$FFUFileToInstall will be installed" +} +else { + Writelog 'No FFU files found' + Write-Host 'No FFU files found' + Exit +} + +#FindAP +$APFolder = $USBDrive + "Autopilot\" +If (Test-Path -Path $APFolder){ + [array]$APFiles = @(Get-ChildItem -Path $APFolder*.json) + $APFilesCount = $APFiles.Count + if ($APFilesCount -ge 1){ + $autopilot = $true + } +} + + +#FindPPKG +$PPKGFolder = $USBDrive + "PPKG\" +if (Test-Path -Path $PPKGFolder){ + [array]$PPKGFiles = @(Get-ChildItem -Path $PPKGFolder*.ppkg) + $PPKGFilesCount = $PPKGFiles.Count + if ($PPKGFilesCount -ge 1){ + $PPKG = $true + } +} + +#FindUnattend +$UnattendFolder = $USBDrive + "unattend\" +$UnattendFilePath = $UnattendFolder + "unattend.xml" +$UnattendPrefixPath = $UnattendFolder + "prefixes.txt" +If (Test-Path -Path $UnattendFilePath){ + $UnattendFile = Get-ChildItem -Path $UnattendFilePath + If ($UnattendFile){ + $Unattend = $true + } +} +If (Test-Path -Path $UnattendPrefixPath){ + $UnattendPrefixFile = Get-ChildItem -Path $UnattendPrefixPath + If ($UnattendPrefixFile){ + $UnattendPrefix = $true + } +} + +#Ask for device name if unattend exists +if ($Unattend -and $UnattendPrefix){ + Writelog 'Unattend file found with prefixes.txt. Getting prefixes.' + $UnattendPrefixes = @(Get-content $UnattendPrefixFile) + $UnattendPrefixCount = $UnattendPrefixes.Count + If ($UnattendPrefixCount -gt 1) { + WriteLog "Found $UnattendPrefixCount Prefixes" + $array = @() + for($i=0;$i -le $UnattendPrefixCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; DeviceNamePrefix = $UnattendPrefixes[$i]} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, DeviceNamePrefix + do { + try { + $var = $true + [int]$PrefixSelected = Read-Host 'Enter the prefix number to use for the device name' + $PrefixSelected = $PrefixSelected -1 + } + catch { + Write-Host 'Input was not in correct format. Please enter a valid prefix number' + $var = $false + } + } until (($PrefixSelected -le $UnattendPrefixCount -1) -and $var) + $PrefixToUse = $array[$PrefixSelected].DeviceNamePrefix + WriteLog "$PrefixToUse was selected" + } + elseif ($UnattendPrefixCount -eq 1) { + WriteLog "Found $UnattendPrefixCount Prefix" + $PrefixToUse = $UnattendPrefixes[0] + WriteLog "Will use $PrefixToUse as device name prefix" + } + #Get serial number to append. This can make names longer than 15 characters. Trim any leading or trailing whitespace + $serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim() + #Combine prefix with serial + $computername = $PrefixToUse + $serial + #If computername is longer than 15 characters, reduce to 15. Sysprep/unattend doesn't like ComputerName being longer than 15 characters even though Windows accepts it + If ($computername.Length -gt 15){ + $computername = $computername.substring(0,15) + } + $computername = Set-Computername($computername) + Writelog "Computer name set to $computername" +} +elseif($Unattend){ + Writelog 'Unattend file found with no prefixes.txt, asking for name' + [string]$computername = Read-Host 'Enter device name' + Set-Computername($computername) + Writelog "Computer name set to $computername" +} +else { + WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.' +} + +#If both AP and PPKG folder found with files, ask which to use. +If($autopilot -eq $true -and $PPKG -eq $true){ + WriteLog 'Both PPKG and Autopilot json files found' + Write-Host 'Both Autopilot JSON files and Provisioning packages were found.' + do { + try { + $var = $true + [int]$APorPPKG = Read-Host 'Enter 1 for Autopilot or 2 for Provisioning Package' + } + + catch { + Write-Host 'Incorrect value. Please enter 1 for Autopilot or 2 for Provisioning Package' + $var = $false + } + } until (($APorPPKG -gt 0 -and $APorPPKG -lt 3) -and $var) + If ($APorPPKG -eq 1){ + $PPKG = $false + } + else{ + $autopilot = $false + } +} + +#If multiple AP json files found, ask which to install +If ($APFilesCount -gt 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount Autopilot json Files" + $array = @() + + for($i=0;$i -le $APFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; APFile = $APFiles[$i].FullName; APFileName = $APFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, APFileName + do { + try { + $var = $true + [int]$APFileSelected = Read-Host 'Enter the AP json file number to install' + $APFileSelected = $APFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid AP json file number' + $var = $false + } + } until (($APFileSelected -le $APFilesCount -1) -and $var) + + $APFileToInstall = $array[$APFileSelected].APFile + $APFileName = $array[$APFileSelected].APFileName + WriteLog "$APFileToInstall was selected" +} +elseif ($APFilesCount -eq 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount AP File" + $APFileToInstall = $APFiles[0].FullName + $APFileName = $APFiles[0].Name + WriteLog "$APFileToInstall will be copied" +} +else { + Writelog 'No AP files found or AP was not selected' +} + +#If multiple PPKG files found, ask which to install +If ($PPKGFilesCount -gt 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG Files" + $array = @() + + for($i=0;$i -le $PPKGFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; PPKGFile = $PPKGFiles[$i].FullName; PPKGFileName = $PPKGFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, PPKGFileName + do { + try { + $var = $true + [int]$PPKGFileSelected = Read-Host 'Enter the PPKG file number to install' + $PPKGFileSelected = $PPKGFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid PPKG file number' + $var = $false + } + } until (($PPKGFileSelected -le $PPKGFilesCount -1) -and $var) + + $PPKGFileToInstall = $array[$PPKGFileSelected].PPKGFile + WriteLog "$PPKGFileToInstall was selected" +} +elseif ($PPKGFilesCount -eq 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG File" + $PPKGFileToInstall = $PPKGFiles[0].FullName + WriteLog "$PPKGFileToInstall will be used" +} +else { + Writelog 'No PPKG files found or PPKG not selected.' +} + +#Find Drivers +$Drivers = $USBDrive + "Drivers" +If (Test-Path -Path $Drivers) +{ + #Check if multiple driver folders found, if so, just select one folder to save time/space + $DriverFolders = Get-ChildItem -Path $Drivers + $DriverFoldersCount = $DriverFolders.count + If ($DriverFoldersCount -gt 1) + { + WriteLog "Found $DriverFoldersCount driver folders" + $array = @() + + for($i=0; $i -le $DriverFoldersCount -1; $i++){ + $Properties = [ordered]@{Number = $i + 1; Drivers = $DriverFolders[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, Drivers + do { + try { + $var = $true + [int]$DriversSelected = Read-Host 'Enter the set of drivers to install' + $DriversSelected = $DriversSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid driver folder number' + $var = $false + } + } until (($DriversSelected -le $DriverFoldersCount -1) -and $var) + + $Drivers = $array[$DriversSelected].Drivers + WriteLog "$Drivers was selected" + } + elseif ($DriverFoldersCount -eq 1) { + WriteLog "Found $DriverFoldersCount driver folder" + $Drivers = $DriverFolders.FullName + WriteLog "$Drivers will be installed" + } + else { + Writelog 'No driver folders found' + } +} + +#If you want to enable battery level checking, uncomment the line below as well as the Get-Battery function near the top of the script +#Get-Battery + +#Apply FFU +WriteLog "Applying FFU to $PhysicalDeviceID" +WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID" +#In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen. +dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID +if($LASTEXITCODE -eq 0){ + WriteLog 'Successfully applied FFU' +} +else{ + Writelog "Failed to apply FFU - LastExitCode = $LASTEXITCODE also check dism.log on the USB drive for more info" + #Copy DISM log to USBDrive + invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" + exit +} +#Change code to allow for optimized FFUs + +#Extend Windows partition and create recovery partition +# Writelog 'Extending Windows partition' +# Invoke-Process diskpart.exe "/S $ExtendPartition" +# Writelog 'Extending Windows partition succeeded' + + +#Copy modified WinRE if folder exists, else copy inbox WinRE +$WinRE = $USBDrive + "WinRE\winre.wim" +If (Test-Path -Path $WinRE) +{ + WriteLog 'Assigning recovery partition drive letter R' + Invoke-Process diskpart.exe "/S $RecoveryPartition" + WriteLog 'Assigned recovery partition drive letter R successfully' + WriteLog 'Copying modified WinRE to Recovery directory' + Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y" + WriteLog 'Copying WinRE to Recovery directory succeeded' + WriteLog 'Registering location of recovery tools' + Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" + WriteLog 'Registering location of recovery tools succeeded' +} +# else { +# WriteLog 'Copying default WinRE to Recovery directory' +# Invoke-Process xcopy.exe "/h W:\Windows\System32\Recovery\Winre.wim R:\Recovery\WindowsRE\ /Y" +# WriteLog 'Copying WinRE to Recovery directory succeeded' +# WriteLog 'Registering location of recovery tools' +# Invoke-process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" +# WriteLog 'Registering location of recovery tools succeeded' +# } + +#Autopilot JSON +If ($APFileToInstall){ + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot" + Invoke-process xcopy.exe "$APFileToInstall W:\Windows\provisioning\autopilot\" + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot succeeded" + # Rename file in W:\Windows\Provisioning\Autopilot to AutoPilotConfigurationFile.json + try { + Rename-Item -Path "W:\Windows\Provisioning\Autopilot\$APFileName" -NewName 'W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json' + WriteLog "Renamed W:\Windows\Provisioning\Autopilot\$APFilename to W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json" + } + + catch{ + Writelog "Copying $APFileToInstall to W:\windows\provisioning\autopilot failed with error: $_" + throw $_ + } +} +#Apply PPKG +If ($PPKGFileToInstall){ + try { + #Make sure to delete any existing PPKG on the USB drive + Get-Childitem -Path $USBDrive\*.ppkg | ForEach-Object { + Remove-item -Path $_.FullName + } + WriteLog "Copying $PPKGFileToInstall to $USBDrive" + Invoke-process xcopy.exe "$PPKGFileToInstall $USBDrive" + WriteLog "Copying $PPKGFileToInstall to $USBDrive succeeded" + } + + catch{ + Writelog "Copying $PPKGFileToInstall to $USBDrive failed with error: $_" + throw $_ + } +} +#Set DeviceName +If ($PrefixToUse){ + try{ + $PantherDir = 'w:\windows\panther' + If (Test-Path -Path $PantherDir){ + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-process xcopy "$UnattendFile $PantherDir /Y" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + else{ + Writelog "$PantherDir doesn't exist, creating it" + New-Item -Path $PantherDir -ItemType Directory -Force + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-Process xcopy.exe "$UnattendFile $PantherDir" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + } + catch{ + WriteLog "Copying Unattend.xml to name device failed" + throw $_ + } +} + +#Add Drivers +#Some drivers can sometimes fail to copy and dism ends up with a non-zero error code. Invoke-process will throw and terminate in these instances. +If (Test-Path -Path $Drivers) +{ + WriteLog 'Copying drivers' + Write-Host 'Copying Drivers - dism will pop a window with no progress. This can take a few minutes to complete. Please be patient. ' + Invoke-process dism.exe "/image:W:\ /Add-Driver /Driver:""$Drivers"" /Recurse" + WriteLog 'Copying drivers succeeded' +} + +#Copy DISM log to USBDrive +WriteLog "Copying dism log to $USBDrive" +invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" +WriteLog "Copying dism log to $USBDrive succeeded" + + + + diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFUv3.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFUv3.ps1 new file mode 100644 index 0000000..2b288b5 --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFUv3.ps1 @@ -0,0 +1,554 @@ +function Get-USBDrive(){ + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Removable' -and $_.FileSystemType -eq 'NTFS'}).DriveLetter + if ($null -eq $USBDriveLetter){ + #Must be using a fixed USB drive - difficult to grab drive letter from win32_diskdrive. Assume user followed instructions and used Deploy as the friendly name for partition + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Fixed' -and $_.FileSystemType -eq 'NTFS' -and $_.FileSystemLabel -eq 'Deploy'}).DriveLetter + #If we didn't get the drive letter, stop the script. + if ($null -eq $USBDriveLetter){ + WriteLog 'Cannot find USB drive letter - most likely using a fixed USB drive. Name the 2nd partition with the FFU files as Deploy so the script can grab the drive letter. Exiting' + Exit + } + + } + $USBDriveLetter = $USBDriveLetter + ":\" + return $USBDriveLetter +} + +function Get-HardDrive(){ + $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID + return $DeviceID +} + +function WriteLog($LogText){ + Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" +} + +function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){ + (Get-Content $DiskpartFile).Replace('disk 0', "disk $DiskID") | Set-Content -Path $DiskpartFile +} + +function Set-Computername($computername){ + [xml]$xml = Get-Content $UnattendFile + if($xml.unattend.settings.component.Count -ge 2){ + #Assumes that Computername is the first component element + $xml.unattend.settings.component[0].ComputerName = $computername + }else{ + $xml.unattend.settings.component.ComputerName = $computername + } + $xml.Save($UnattendFile) + return $computername +} + +function Invoke-Process { + [CmdletBinding(SupportsShouldProcess)] + param + ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$FilePath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$ArgumentList + ) + + $ErrorActionPreference = 'Stop' + + try { + $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)" + $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)" + + $startProcessParams = @{ + FilePath = $FilePath + ArgumentList = $ArgumentList + RedirectStandardError = $stdErrTempFile + RedirectStandardOutput = $stdOutTempFile + Wait = $true; + PassThru = $true; + NoNewWindow = $false; + } + if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) { + $cmd = Start-Process @startProcessParams + $cmdOutput = Get-Content -Path $stdOutTempFile -Raw + $cmdError = Get-Content -Path $stdErrTempFile -Raw + if ($cmd.ExitCode -ne 0) { + if ($cmdError) { + throw $cmdError.Trim() + } + if ($cmdOutput) { + throw $cmdOutput.Trim() + } + } else { + if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) { + WriteLog $cmdOutput + } + } + } + } catch { + #$PSCmdlet.ThrowTerminatingError($_) + WriteLog $_ + Write-Host 'Script failed - check scriptlog.txt on the USB drive for more info' + throw $_ + + } finally { + Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore + + } + +} + +# This function can be used in instances where battery level might matter (e.g. installing firmware for Surface). The problem is that WinPE doesn't have +# a driver for the battery installed, so you'll need to inject drivers, which can be tricky in that just injecting the battery driver might not be enough, +# you might also need other drivers that the battery driver is dependent on. +# function Get-Battery(){ +# while (($BattLev = (Get-CimInstance win32_battery).EstimatedChargeRemaining) -lt "35") +# { +# WriteLog "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Write-Host "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Start-Sleep 60 +# } + +# WriteLog "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# Write-Host "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# } + +#Get USB Drive and create log file +$LogFileName = 'ScriptLog.txt' +$USBDrive = Get-USBDrive +New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null +$LogFile = $USBDrive + $LogFilename +WriteLog 'Begin Logging' + +#Find PhysicalDrive +$PhysicalDeviceID = Get-HardDrive +WriteLog "Physical DeviceID is $PhysicalDeviceID" + +#Parse DiskID Number +$DiskID = $PhysicalDeviceID.substring($PhysicalDeviceID.length - 1,1) +WriteLog "DiskID is $DiskID" + +#Modify diskpart answer files if DiskID not 0 +$UEFIFFUPartitions = 'x:\CreateUEFI-FFU-Partitions.txt' +$ExtendPartition = 'x:\ExtendPartition-UEFI.txt' +$RecoveryPartition = 'x:\RecoveryPartition-UEFI.txt' + +If ($DiskID -ne '0'){ + WriteLog 'DiskID is not 0. Need to modify diskpart answer files' + try { + Set-DiskpartAnswerFiles $UEFIFFUPartitions $DiskID + } + catch { + WriteLog "Modifying $UEFIFFUPartitions failed with error: $_" + } + + try { + Set-DiskpartAnswerFiles $ExtendPartition $DiskID + } + catch { + WriteLog "Modifying $ExtendPartition failed with error: $_" + } + try { + Set-DiskpartAnswerFiles $RecoveryPartition $DiskID + } + catch { + WriteLog "Modifying $RecoveryPartition failed with error: $_" + } +} + +#Find FFU Files +[array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu) +$FFUCount = $FFUFiles.Count + +#If multiple FFUs found, ask which to install +If ($FFUCount -gt 1) { + WriteLog "Found $FFUCount FFU Files" + $array = @() + + for($i=0;$i -le $FFUCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; FFUFile = $FFUFiles[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, FFUFile + do { + try { + $var = $true + [int]$FFUSelected = Read-Host 'Enter the FFU number to install' + $FFUSelected = $FFUSelected -1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid FFU number' + $var = $false + } + } until (($FFUSelected -le $FFUCount -1) -and $var) + + $FFUFileToInstall = $array[$FFUSelected].FFUFile + WriteLog "$FFUFileToInstall was selected" +} +elseif ($FFUCount -eq 1) { + WriteLog "Found $FFUCount FFU File" + $FFUFileToInstall = $FFUFiles[0].FullName + WriteLog "$FFUFileToInstall will be installed" +} +else { + Writelog 'No FFU files found' + Write-Host 'No FFU files found' + Exit +} + +#FindAP +$APFolder = $USBDrive + "Autopilot\" +If (Test-Path -Path $APFolder){ + [array]$APFiles = @(Get-ChildItem -Path $APFolder*.json) + $APFilesCount = $APFiles.Count + if ($APFilesCount -ge 1){ + $autopilot = $true + } +} + + +#FindPPKG +$PPKGFolder = $USBDrive + "PPKG\" +if (Test-Path -Path $PPKGFolder){ + [array]$PPKGFiles = @(Get-ChildItem -Path $PPKGFolder*.ppkg) + $PPKGFilesCount = $PPKGFiles.Count + if ($PPKGFilesCount -ge 1){ + $PPKG = $true + } +} + +#FindUnattend +$UnattendFolder = $USBDrive + "unattend\" +$UnattendFilePath = $UnattendFolder + "unattend.xml" +$UnattendPrefixPath = $UnattendFolder + "prefixes.txt" +If (Test-Path -Path $UnattendFilePath){ + $UnattendFile = Get-ChildItem -Path $UnattendFilePath + If ($UnattendFile){ + $Unattend = $true + } +} +If (Test-Path -Path $UnattendPrefixPath){ + $UnattendPrefixFile = Get-ChildItem -Path $UnattendPrefixPath + If ($UnattendPrefixFile){ + $UnattendPrefix = $true + } +} + +#Ask for device name if unattend exists +if ($Unattend -and $UnattendPrefix){ + Writelog 'Unattend file found with prefixes.txt. Getting prefixes.' + $UnattendPrefixes = @(Get-content $UnattendPrefixFile) + $UnattendPrefixCount = $UnattendPrefixes.Count + If ($UnattendPrefixCount -gt 1) { + WriteLog "Found $UnattendPrefixCount Prefixes" + $array = @() + for($i=0;$i -le $UnattendPrefixCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; DeviceNamePrefix = $UnattendPrefixes[$i]} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, DeviceNamePrefix + do { + try { + $var = $true + [int]$PrefixSelected = Read-Host 'Enter the prefix number to use for the device name' + $PrefixSelected = $PrefixSelected -1 + } + catch { + Write-Host 'Input was not in correct format. Please enter a valid prefix number' + $var = $false + } + } until (($PrefixSelected -le $UnattendPrefixCount -1) -and $var) + $PrefixToUse = $array[$PrefixSelected].DeviceNamePrefix + WriteLog "$PrefixToUse was selected" + } + elseif ($UnattendPrefixCount -eq 1) { + WriteLog "Found $UnattendPrefixCount Prefix" + $PrefixToUse = $UnattendPrefixes[0] + WriteLog "Will use $PrefixToUse as device name prefix" + } + #Get serial number to append. This can make names longer than 15 characters. Trim any leading or trailing whitespace + $serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim() + #Combine prefix with serial + $computername = $PrefixToUse + $serial + #If computername is longer than 15 characters, reduce to 15. Sysprep/unattend doesn't like ComputerName being longer than 15 characters even though Windows accepts it + If ($computername.Length -gt 15){ + $computername = $computername.substring(0,15) + } + $computername = Set-Computername($computername) + Writelog "Computer name set to $computername" +} +elseif($Unattend){ + Writelog 'Unattend file found with no prefixes.txt, asking for name' + [string]$computername = Read-Host 'Enter device name' + Set-Computername($computername) + Writelog "Computer name set to $computername" +} +else { + WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.' +} + +#If both AP and PPKG folder found with files, ask which to use. +If($autopilot -eq $true -and $PPKG -eq $true){ + WriteLog 'Both PPKG and Autopilot json files found' + Write-Host 'Both Autopilot JSON files and Provisioning packages were found.' + do { + try { + $var = $true + [int]$APorPPKG = Read-Host 'Enter 1 for Autopilot or 2 for Provisioning Package' + } + + catch { + Write-Host 'Incorrect value. Please enter 1 for Autopilot or 2 for Provisioning Package' + $var = $false + } + } until (($APorPPKG -gt 0 -and $APorPPKG -lt 3) -and $var) + If ($APorPPKG -eq 1){ + $PPKG = $false + } + else{ + $autopilot = $false + } +} + +#If multiple AP json files found, ask which to install +If ($APFilesCount -gt 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount Autopilot json Files" + $array = @() + + for($i=0;$i -le $APFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; APFile = $APFiles[$i].FullName; APFileName = $APFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, APFileName + do { + try { + $var = $true + [int]$APFileSelected = Read-Host 'Enter the AP json file number to install' + $APFileSelected = $APFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid AP json file number' + $var = $false + } + } until (($APFileSelected -le $APFilesCount -1) -and $var) + + $APFileToInstall = $array[$APFileSelected].APFile + $APFileName = $array[$APFileSelected].APFileName + WriteLog "$APFileToInstall was selected" +} +elseif ($APFilesCount -eq 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount AP File" + $APFileToInstall = $APFiles[0].FullName + $APFileName = $APFiles[0].Name + WriteLog "$APFileToInstall will be copied" +} +else { + Writelog 'No AP files found or AP was not selected' +} + +#If multiple PPKG files found, ask which to install +If ($PPKGFilesCount -gt 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG Files" + $array = @() + + for($i=0;$i -le $PPKGFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; PPKGFile = $PPKGFiles[$i].FullName; PPKGFileName = $PPKGFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, PPKGFileName + do { + try { + $var = $true + [int]$PPKGFileSelected = Read-Host 'Enter the PPKG file number to install' + $PPKGFileSelected = $PPKGFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid PPKG file number' + $var = $false + } + } until (($PPKGFileSelected -le $PPKGFilesCount -1) -and $var) + + $PPKGFileToInstall = $array[$PPKGFileSelected].PPKGFile + WriteLog "$PPKGFileToInstall was selected" +} +elseif ($PPKGFilesCount -eq 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG File" + $PPKGFileToInstall = $PPKGFiles[0].FullName + WriteLog "$PPKGFileToInstall will be used" +} +else { + Writelog 'No PPKG files found or PPKG not selected.' +} + +#Find Drivers +$Drivers = $USBDrive + "Drivers" +If (Test-Path -Path $Drivers) +{ + #Check if multiple driver folders found, if so, just select one folder to save time/space + $DriverFolders = Get-ChildItem -Path $Drivers + $DriverFoldersCount = $DriverFolders.count + If ($DriverFoldersCount -gt 1) + { + WriteLog "Found $DriverFoldersCount driver folders" + $array = @() + + for($i=0; $i -le $DriverFoldersCount -1; $i++){ + $Properties = [ordered]@{Number = $i + 1; Drivers = $DriverFolders[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, Drivers + do { + try { + $var = $true + [int]$DriversSelected = Read-Host 'Enter the set of drivers to install' + $DriversSelected = $DriversSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid driver folder number' + $var = $false + } + } until (($DriversSelected -le $DriverFoldersCount -1) -and $var) + + $Drivers = $array[$DriversSelected].Drivers + WriteLog "$Drivers was selected" + } + elseif ($DriverFoldersCount -eq 1) { + WriteLog "Found $DriverFoldersCount driver folder" + $Drivers = $DriverFolders.FullName + WriteLog "$Drivers will be installed" + } + else { + Writelog 'No driver folders found' + } +} + +#If you want to enable battery level checking, uncomment the line below as well as the Get-Battery function near the top of the script +#Get-Battery + +#Partition drive +Writelog 'Creating partitions' +#Start-Process -FilePath diskpart.exe -ArgumentList "/S $UEFIFFUPartitions" -Wait -ErrorAction Stop | Out-File $Logfile -Append +Invoke-Process diskpart.exe "/S $UEFIFFUPartitions" +Writelog 'Creating partitions succeeded' +#Apply FFU +WriteLog "Applying FFU to $PhysicalDeviceID" +WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID" +#In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen. +dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID +if($LASTEXITCODE -eq 0){ + WriteLog 'Successfully applied FFU' +} +else{ + Writelog "Failed to apply FFU - LastExitCode = $LASTEXITCODE also check dism.log on the USB drive for more info" + #Copy DISM log to USBDrive + invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" + exit +} +#Change code to allow for optimized FFUs + +#Extend Windows partition and create recovery partition +# Writelog 'Extending Windows partition' +# Invoke-Process diskpart.exe "/S $ExtendPartition" +# Writelog 'Extending Windows partition succeeded' + + +#Copy modified WinRE if folder exists, else copy inbox WinRE +$WinRE = $USBDrive + "WinRE\winre.wim" +If (Test-Path -Path $WinRE) +{ + WriteLog 'Assigning recovery partition drive letter R' + Invoke-Process diskpart.exe "/S $RecoveryPartition" + WriteLog 'Assigned recovery partition drive letter R successfully' + WriteLog 'Copying modified WinRE to Recovery directory' + Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y" + WriteLog 'Copying WinRE to Recovery directory succeeded' + WriteLog 'Registering location of recovery tools' + Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" + WriteLog 'Registering location of recovery tools succeeded' +} +# else { +# WriteLog 'Copying default WinRE to Recovery directory' +# Invoke-Process xcopy.exe "/h W:\Windows\System32\Recovery\Winre.wim R:\Recovery\WindowsRE\ /Y" +# WriteLog 'Copying WinRE to Recovery directory succeeded' +# WriteLog 'Registering location of recovery tools' +# Invoke-process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" +# WriteLog 'Registering location of recovery tools succeeded' +# } + +#Autopilot JSON +If ($APFileToInstall){ + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot" + Invoke-process xcopy.exe "$APFileToInstall W:\Windows\provisioning\autopilot\" + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot succeeded" + # Rename file in W:\Windows\Provisioning\Autopilot to AutoPilotConfigurationFile.json + try { + Rename-Item -Path "W:\Windows\Provisioning\Autopilot\$APFileName" -NewName 'W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json' + WriteLog "Renamed W:\Windows\Provisioning\Autopilot\$APFilename to W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json" + } + + catch{ + Writelog "Copying $APFileToInstall to W:\windows\provisioning\autopilot failed with error: $_" + throw $_ + } +} +#Apply PPKG +If ($PPKGFileToInstall){ + try { + #Make sure to delete any existing PPKG on the USB drive + Get-Childitem -Path $USBDrive\*.ppkg | ForEach-Object { + Remove-item -Path $_.FullName + } + WriteLog "Copying $PPKGFileToInstall to $USBDrive" + Invoke-process xcopy.exe "$PPKGFileToInstall $USBDrive" + WriteLog "Copying $PPKGFileToInstall to $USBDrive succeeded" + } + + catch{ + Writelog "Copying $PPKGFileToInstall to $USBDrive failed with error: $_" + throw $_ + } +} +#Set DeviceName +If ($PrefixToUse){ + try{ + $PantherDir = 'w:\windows\panther' + If (Test-Path -Path $PantherDir){ + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-process xcopy "$UnattendFile $PantherDir /Y" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + else{ + Writelog "$PantherDir doesn't exist, creating it" + New-Item -Path $PantherDir -ItemType Directory -Force + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-Process xcopy.exe "$UnattendFile $PantherDir" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + } + catch{ + WriteLog "Copying Unattend.xml to name device failed" + throw $_ + } +} + +#Add Drivers +#Some drivers can sometimes fail to copy and dism ends up with a non-zero error code. Invoke-process will throw and terminate in these instances. +If (Test-Path -Path $Drivers) +{ + WriteLog 'Copying drivers' + Write-Host 'Copying Drivers - dism will pop a window with no progress. This can take a few minutes to complete. Please be patient. ' + Invoke-process dism.exe "/image:W:\ /Add-Driver /Driver:""$Drivers"" /Recurse" + WriteLog 'Copying drivers succeeded' +} + +#Copy DISM log to USBDrive +WriteLog "Copying dism log to $USBDrive" +invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" +WriteLog "Copying dism log to $USBDrive succeeded" + + + + diff --git a/FFUDevelopment/WinPEDeployFFUFiles/CreateUEFI-FFU-Partitions.txt b/FFUDevelopment/WinPEDeployFFUFiles/CreateUEFI-FFU-Partitions.txt new file mode 100644 index 0000000..2773059 --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFiles/CreateUEFI-FFU-Partitions.txt @@ -0,0 +1,3 @@ +select disk 0 +clean +exit diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFI.txt b/FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFI.txt new file mode 100644 index 0000000..cf2a9ff --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFI.txt @@ -0,0 +1,11 @@ +select disk 0 +select partition 3 +Assign letter="W" +extend +shrink minimum=650 +create partition primary +format quick fs=ntfs label="Recovery" +assign letter="R" +set id="de94bba4-06d1-4d40-a16a-bfd50179d6ac" +gpt attributes=0x8000000000000001 +exit diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFIold.txt b/FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFIold.txt new file mode 100644 index 0000000..cf2a9ff --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFIold.txt @@ -0,0 +1,11 @@ +select disk 0 +select partition 3 +Assign letter="W" +extend +shrink minimum=650 +create partition primary +format quick fs=ntfs label="Recovery" +assign letter="R" +set id="de94bba4-06d1-4d40-a16a-bfd50179d6ac" +gpt attributes=0x8000000000000001 +exit diff --git a/FFUDevelopment/WinPEDeployFFUFiles/Windows/System32/startnet.cmd b/FFUDevelopment/WinPEDeployFFUFiles/Windows/System32/startnet.cmd new file mode 100644 index 0000000..12519ba --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFiles/Windows/System32/startnet.cmd @@ -0,0 +1,5 @@ +wpeinit +powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c +powershell -Noprofile -ExecutionPolicy Bypass -File x:\ApplyFFU.ps1 +exit +