diff --git a/CHANGELOG.md b/CHANGELOG.md index abab6b2d5..99ec866ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. +## 26.8.5.2 - August 5, 2026 + +### Added + +- **Dedicated OSDCloud IPU public function scripts** — Added `Public/OSDCloudIPU/Invoke-IPUPreInstallNotification.ps1`, `Public/OSDCloudIPU/Invoke-IPUPreInstallNotificationLauncher.ps1`, `Public/OSDCloudIPU/Invoke-OSDCloudIPU.ps1`, `Public/OSDCloudIPU/New-OSDCloudOSWimFile.ps1`, and `Public/OSDCloudIPU/Set-Win11ReqBypassRegValues.ps1` to split IPU functionality into focused, discoverable command files. + +### Changed + +- **OSDCloud IPU public layout modernization** — Reorganized the IPU implementation from `Public/OSDCloud/OSDCloudIPU.ps1` into the `Public/OSDCloudIPU` folder structure for cleaner command ownership and easier maintenance. +- **Module manifest version bump** (`OSD.psd1`) — Updated module version to `26.8.5.2`. + +### Removed + +- **Legacy monolithic IPU script** — Removed `Public/OSDCloud/OSDCloudIPU.ps1` after migrating its function surface to dedicated script files. + ## 26.8.5.1 - August 5, 2026 ### Added diff --git a/OSD.psd1 b/OSD.psd1 index 485f09134..cf07999cb 100644 --- a/OSD.psd1 +++ b/OSD.psd1 @@ -8,7 +8,7 @@ @{ # --- Identity --- RootModule = 'OSD.psm1' - ModuleVersion = '26.8.5.1' + ModuleVersion = '26.8.5.2' CompatiblePSEditions = @('Core', 'Desktop') GUID = '9fe5b9b6-0224-4d87-9018-a8978529f6f5' diff --git a/Public/OSDCloud/OSDCloudIPU.ps1 b/Public/OSDCloud/OSDCloudIPU.ps1 deleted file mode 100644 index 5d8e16c75..000000000 --- a/Public/OSDCloud/OSDCloudIPU.ps1 +++ /dev/null @@ -1,1746 +0,0 @@ -function Invoke-IPUPreInstallNotifications { -<# -.SYNOPSIS - Shows the pre-install notification toast during an in-place upgrade. - -.DESCRIPTION - Starts the upgrade notification workflow, records start and finish markers, waits for Windows Setup progress to be available, and keeps the user informed with toast updates until the upgrade completes or the timeout is reached. - -.INPUTS - None. - -.OUTPUTS - None. - -.NOTES - Author: David Segura - Recast Software - 2026-07-10 - Standardized comment-based help metadata and links. - -.LINK - https://github.com/OSDeploy/OSD/tree/master/docs - -.LINK - https://garytown.com - -.LINK - https://www.recastsoftware.com -#> - -## Set script requirements -#Requires -Version 3.0 - -##*============================================= -##* VARIABLE DECLARATION -##*============================================= -#region VariableDeclaration - -## Get script path and name -[string]$ScriptPath = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -[string]$ScriptName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Definition) - - -Start-Transcript -Write-Output -------------------------------------- -Write-Output $ScriptPath $ScriptName -Get-Date - -#Registry Path that will get Tagged -$registryPath = "HKLM:\SOFTWARE\WaaS" -$TimeStamp = Get-Date -f s -$keynameStart = "CA_PreInstallNotification_Start" -$keynameFinish = "CA_PreInstallNotification_Finish" -New-ItemProperty -Path $registryPath -Name $keynameStart -Value $TimeStamp -Force - - -#Logfile generated by this script -$WaaSFolder = "$($env:ProgramData)\WaaS" -$logfile = "$WaaSFolder\CustomActions.log" - -$whoami = whoami - -# Load some required namespaces -$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] -$null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] -Add-Type -Path "$PSScriptRoot\Microsoft.Toolkit.Uwp.Notifications.dll" -if (!($Global:ModuleBase = (Get-Module -Name OSD).ModuleBase)){Import-Module -Name OSD} -if ($Global:ModuleBase = (Get-Module -Name OSD).ModuleBase){ - $Global:NotificationsPath = "$Global:ModuleBase\Projects\assembly\Microsoft.Toolkit.Uwp.Notifications.dll" - -Write-Host "Adding Type Notifications: $Global:NotificationsPath" -Add-Type -Path $Global:NotificationsPath -} - - -#endregion -##*============================================= -##* END VARIABLE DECLARATION -##*============================================= - -##*============================================= -##* FUNCTION LISTINGS -##*============================================= -#region FunctionListings - -#CMTraceLog Function formats logging in CMTrace style - function CMTraceLog { - [CmdletBinding()] - Param ( - [Parameter(Mandatory=$false)] - $Message, - - [Parameter(Mandatory=$false)] - $ErrorMessage, - - [Parameter(Mandatory=$false)] - $Component = "Notification", - - [Parameter(Mandatory=$false)] - [int]$Type, - - [Parameter(Mandatory=$true)] - $LogFile - ) - <# - Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) - #> - $Time = Get-Date -Format "HH:mm:ss.ffffff" - $Date = Get-Date -Format "MM-dd-yyyy" - - if ($ErrorMessage -ne $null) {$Type = 3} - if ($Component -eq $null) {$Component = " "} - if ($Type -eq $null) {$Type = 1} - - $LogMessage = "" - $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile - } - - - - -##*============================================= -##* END FUNCTION LISTINGS -##*============================================= - -##*============================================= -##* SCRIPT BODY -##*============================================= -#region ScriptBody - - -CMTraceLog -Message "--------------------------" -Type 1 -LogFile $LogFile -CMTraceLog -Message "Starting $ScriptName" -Type 1 -LogFile $LogFile -CMTraceLog -Message "Running as: $whoami" -Type 1 -LogFile $LogFile - -$SetupProgressPath = "HKLM:System\Setup\mosetup\volatile" -CMTraceLog -Message "Waiting For SetupProgress Value to be populated..." -Type 1 -LogFile $LogFile -$Minutes = 1 -DO - { - $SetupProgress = Get-ItemPropertyValue -Path $SetupProgressPath -Name "SetupProgress" -ErrorAction SilentlyContinue - if (!($SetupProgress)) - { - $Minutes += 1 - Start-Sleep -Seconds 60 - } - if ($Minutes -eq 20) - { - CMTraceLog -Message "Waited $Minutes Minutes, exiting script with Exit 20, I'm tired of waiting" -Type 3 -LogFile $LogFile - $TimeStamp = Get-Date -f s - New-ItemProperty -Path $registryPath -Name $keynameFinish -Value $TimeStamp -Force - exit 20 - } - } -Until ($SetupProgress) - -$ToastTag = "PowerShell" -$ToastGroup = "PowerShell" - -$ToastContentBuilder = [Microsoft.Toolkit.Uwp.Notifications.ToastContentBuilder]::new() -$ProgressBar = [Microsoft.Toolkit.Uwp.Notifications.AdaptiveProgressBar]@{ - Title = [Microsoft.Toolkit.Uwp.Notifications.BindableString]::new("progressTitle") - Value = [Microsoft.Toolkit.Uwp.Notifications.BindableProgressBarValue]::new("progressValue") - ValueStringOverride = [Microsoft.Toolkit.Uwp.Notifications.BindableString]::new("progressValueString") - Status = [Microsoft.Toolkit.Uwp.Notifications.BindableString]::new("progressStatus") -} - - -$ToastContent = $ToastContentBuilder.AddText("Upgrading Windows 10..."). - AddVisualChild($ProgressBar). - AddText("Please do not reboot until you're notified..."). - GetToastContent() -$Toast = [Windows.UI.Notifications.ToastNotification]::new($ToastContent.GetXml()) -$Toast.Tag = $ToastTag -$Toast.Group = $ToastGroup - -$dict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]' -$dict.Add("progressValue","$SetupProgressNumber") -$dict.Add("progressValueString","$SetupProgress% Complete") -$dict.Add("progressStatus","Installing...") -$dict.Add("progressTitle","Processing Feature Update of Windows to 20H2") - -$Toast.Data = [Windows.UI.Notifications.NotificationData]::new($dict, 0) - -CMTraceLog -Message "Triggering Toast" -Type 1 -LogFile $LogFile -$Notification = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Windows.SystemToast.SecurityAndMaintenance") -$Notification.Show($Toast) -### Run to here first to display - -Start-Sleep -Seconds 2 - -do - { - Start-Sleep -Seconds 5 - $SetupProgress = Get-ItemPropertyValue -Path $SetupProgressPath -Name "SetupProgress" -ErrorAction SilentlyContinue - $SetupProgressNumber = $SetupProgress / 100 - ### Update the Toast! (Run this separately to update) - $dict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]' - $dict.Add("progressValue","$SetupProgressNumber") - $dict.Add("progressValueString","$SetupProgress% Complete") - $dict.Add("progressStatus","Installing...") - $dict.Add("progressTitle","Processing Feature Update of Windows to 20H2") - $NotificationData = [Windows.UI.Notifications.NotificationData]::new($dict, 0) - [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Windows.SystemToast.SecurityAndMaintenance").Update($NotificationData, $ToastTag, $ToastGroup) - - - } -Until ($SetupProgress -eq "100") - -if ($SetupProgress -eq "100") - { - ### Update the Toast! (Run this separately to update) - $dict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]' - $dict.Add("progressValue","$SetupProgressNumber") - $dict.Add("progressValueString","$SetupProgress% Complete") - $dict.Add("progressStatus","Waiting for Restart") - $dict.Add("progressTitle","First Phase of Feature Update of Windows to 20H2 Complete") - $NotificationData = [Windows.UI.Notifications.NotificationData]::new($dict, 0) - [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Windows.SystemToast.SecurityAndMaintenance").Update($NotificationData, $ToastTag, $ToastGroup) - } - -$TimeStamp = Get-Date -f s -New-ItemProperty -Path $registryPath -Name $keynameFinish -Value $TimeStamp -Force -CMTraceLog -Message "Finished $ScriptName" -Type 1 -LogFile $LogFile -exit $exitcode -#endregion -##*============================================= -##* END SCRIPT BODY -##*============================================= - -} -function Invoke-IPUPreInstallNotificationLauncher { - -<# -.SYNOPSIS - Launches the pre-install notification script in the logged-on user context. - -.DESCRIPTION - Creates a process as the active user and starts the pre-install notification workflow so the toast UI runs in the same interactive session as the upgrade target. - -.INPUTS - None. - -.OUTPUTS - None. - -.NOTES - Author: David Segura - Recast Software - 2026-07-10 - Standardized comment-based help metadata and links. - -.LINK - https://github.com/OSDeploy/OSD/tree/master/docs - -.LINK - https://garytown.com - -.LINK - https://www.recastsoftware.com -#> - -## Set script requirements -#Requires -Version 3.0 - -##*============================================= -##* VARIABLE DECLARATION -##*============================================= -#region VariableDeclaration - -## Get script path and name -[string]$ScriptPath = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) -[string]$ScriptName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Definition) - - -Start-Transcript -Write-Output -------------------------------------- -Write-Output $ScriptPath $ScriptName -Get-Date - -#Registry Path that will get Tagged -$registryPath = "HKLM:\SOFTWARE\WaaS" -$TimeStamp = Get-Date -f s -$keynameStart = "CA_PreInstallNotificationLauncher" -New-ItemProperty -Path $registryPath -Name $keynameStart -Value $TimeStamp -Force - - -#Logfile generated by this script -$WaaSFolder = "$($env:ProgramData)\WaaS" -$logfile = "$WaaSFolder\CustomActions.log" - -$whoami = whoami - - -# Load some required namespaces -$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] -$null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] -$Null = [System.Security.AccessControl.FileSystemAccessRule] -Add-Type -Path "$PSScriptRoot\Microsoft.Toolkit.Uwp.Notifications.dll" - -#endregion -##*============================================= -##* END VARIABLE DECLARATION -##*============================================= - -##*============================================= -##* FUNCTION LISTINGS -##*============================================= -#region FunctionListings - -#CMTraceLog Function formats logging in CMTrace style - function CMTraceLog { - [CmdletBinding()] - Param ( - [Parameter(Mandatory=$false)] - $Message, - - [Parameter(Mandatory=$false)] - $ErrorMessage, - - [Parameter(Mandatory=$false)] - $Component = "NotificationLauncher", - - [Parameter(Mandatory=$false)] - [int]$Type, - - [Parameter(Mandatory=$true)] - $LogFile - ) - <# - Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) - #> - $Time = Get-Date -Format "HH:mm:ss.ffffff" - $Date = Get-Date -Format "MM-dd-yyyy" - - if ($ErrorMessage -ne $null) {$Type = 3} - if ($Component -eq $null) {$Component = " "} - if ($Type -eq $null) {$Type = 1} - - $LogMessage = "" - $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile - } - -#Create a Process as Logged-On-User from PowerShell -#https://rzander.azurewebsites.net/create-a-process-as-loggedon-user/ -#https://github.com/murrayju/CreateProcessAsUser - -$Source = @" - -using System; -using System.Runtime.InteropServices; - -namespace murrayju.ProcessExtensions -{ - public static class ProcessExtensions - { - #region Win32 Constants - - private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; - private const int CREATE_NO_WINDOW = 0x08000000; - - private const int CREATE_NEW_CONSOLE = 0x00000010; - - private const uint INVALID_SESSION_ID = 0xFFFFFFFF; - private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; - - #endregion - - #region DllImports - - [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] - private static extern bool CreateProcessAsUser( - IntPtr hToken, - String lpApplicationName, - String lpCommandLine, - IntPtr lpProcessAttributes, - IntPtr lpThreadAttributes, - bool bInheritHandle, - uint dwCreationFlags, - IntPtr lpEnvironment, - String lpCurrentDirectory, - ref STARTUPINFO lpStartupInfo, - out PROCESS_INFORMATION lpProcessInformation); - - [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] - private static extern bool DuplicateTokenEx( - IntPtr ExistingTokenHandle, - uint dwDesiredAccess, - IntPtr lpThreadAttributes, - int TokenType, - int ImpersonationLevel, - ref IntPtr DuplicateTokenHandle); - - [DllImport("userenv.dll", SetLastError = true)] - private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); - - [DllImport("userenv.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool CloseHandle(IntPtr hSnapshot); - - [DllImport("kernel32.dll")] - private static extern uint WTSGetActiveConsoleSessionId(); - - [DllImport("Wtsapi32.dll")] - private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken); - - [DllImport("wtsapi32.dll", SetLastError = true)] - private static extern int WTSEnumerateSessions( - IntPtr hServer, - int Reserved, - int Version, - ref IntPtr ppSessionInfo, - ref int pCount); - - #endregion - - #region Win32 Structs - - private enum SW - { - SW_HIDE = 0, - SW_SHOWNORMAL = 1, - SW_NORMAL = 1, - SW_SHOWMINIMIZED = 2, - SW_SHOWMAXIMIZED = 3, - SW_MAXIMIZE = 3, - SW_SHOWNOACTIVATE = 4, - SW_SHOW = 5, - SW_MINIMIZE = 6, - SW_SHOWMINNOACTIVE = 7, - SW_SHOWNA = 8, - SW_RESTORE = 9, - SW_SHOWDEFAULT = 10, - SW_MAX = 10 - } - - private enum WTS_CONNECTSTATE_CLASS - { - WTSActive, - WTSConnected, - WTSConnectQuery, - WTSShadow, - WTSDisconnected, - WTSIdle, - WTSListen, - WTSReset, - WTSDown, - WTSInit - } - - [StructLayout(LayoutKind.Sequential)] - private struct PROCESS_INFORMATION - { - public IntPtr hProcess; - public IntPtr hThread; - public uint dwProcessId; - public uint dwThreadId; - } - - private enum SECURITY_IMPERSONATION_LEVEL - { - SecurityAnonymous = 0, - SecurityIdentification = 1, - SecurityImpersonation = 2, - SecurityDelegation = 3, - } - - [StructLayout(LayoutKind.Sequential)] - private struct STARTUPINFO - { - public int cb; - public String lpReserved; - public String lpDesktop; - public String lpTitle; - public uint dwX; - public uint dwY; - public uint dwXSize; - public uint dwYSize; - public uint dwXCountChars; - public uint dwYCountChars; - public uint dwFillAttribute; - public uint dwFlags; - public short wShowWindow; - public short cbReserved2; - public IntPtr lpReserved2; - public IntPtr hStdInput; - public IntPtr hStdOutput; - public IntPtr hStdError; - } - - private enum TOKEN_TYPE - { - TokenPrimary = 1, - TokenImpersonation = 2 - } - - [StructLayout(LayoutKind.Sequential)] - private struct WTS_SESSION_INFO - { - public readonly UInt32 SessionID; - - [MarshalAs(UnmanagedType.LPStr)] - public readonly String pWinStationName; - - public readonly WTS_CONNECTSTATE_CLASS State; - } - - #endregion - - // Gets the user token from the currently active session - private static bool GetSessionUserToken(ref IntPtr phUserToken) - { - var bResult = false; - var hImpersonationToken = IntPtr.Zero; - var activeSessionId = INVALID_SESSION_ID; - var pSessionInfo = IntPtr.Zero; - var sessionCount = 0; - - // Get a handle to the user access token for the current active session. - if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0) - { - var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); - var current = pSessionInfo; - - for (var i = 0; i < sessionCount; i++) - { - var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO)); - current += arrayElementSize; - - if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) - { - activeSessionId = si.SessionID; - } - } - } - - // If enumerating did not work, fall back to the old method - if (activeSessionId == INVALID_SESSION_ID) - { - activeSessionId = WTSGetActiveConsoleSessionId(); - } - - if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0) - { - // Convert the impersonation token to a primary token - bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero, - (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, - ref phUserToken); - - CloseHandle(hImpersonationToken); - } - - return bResult; - } - - public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true) - { - var hUserToken = IntPtr.Zero; - var startInfo = new STARTUPINFO(); - var procInfo = new PROCESS_INFORMATION(); - var pEnv = IntPtr.Zero; - int iResultOfCreateProcessAsUser; - - startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); - - try - { - if (!GetSessionUserToken(ref hUserToken)) - { - throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed."); - } - - uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); - startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); - startInfo.lpDesktop = "winsta0\\default"; - - if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false)) - { - throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed."); - } - - if (!CreateProcessAsUser(hUserToken, - appPath, // Application Name - cmdLine, // Command Line - IntPtr.Zero, - IntPtr.Zero, - false, - dwCreationFlags, - pEnv, - workDir, // Working directory - ref startInfo, - out procInfo)) - { - iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); - throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser); - } - - iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); - } - finally - { - CloseHandle(hUserToken); - if (pEnv != IntPtr.Zero) - { - DestroyEnvironmentBlock(pEnv); - } - CloseHandle(procInfo.hThread); - CloseHandle(procInfo.hProcess); - } - - return true; - } - - } -} - -"@ -Add-Type -ReferencedAssemblies 'System', 'System.Runtime.InteropServices' -TypeDefinition $Source -Language CSharp - - - - - - - -##*============================================= -##* END FUNCTION LISTINGS -##*============================================= - -##*============================================= -##* SCRIPT BODY -##*============================================= -#region ScriptBody - - -CMTraceLog -Message "--------------------------" -Type 1 -LogFile $LogFile -CMTraceLog -Message "Starting $ScriptName" -Type 1 -LogFile $LogFile -CMTraceLog -Message "Running as: $whoami" -Type 1 -LogFile $LogFile - - -#$EXE = "cmd.exe" -#$ARG = '/c start /MIN powershell.exe -ExecutionPolicy ByPass -File C:\ProgramData\WaaS\PreInstall\PreInstallNotification.ps1' -#[murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser($EXE, $ARG) - -$EXE = "c:\windows\system32\cmd.exe" -$ARG = '/c start /MIN c:\windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass -windowstyle hidden -File C:\ProgramData\WaaS\PreInstall\PreInstallNotification.ps1' -[murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser($EXE, $ARG) - -#$EXE = "powershell.exe" -#$ARG = ' -ExecutionPolicy ByPass -File C:\ProgramData\WaaS\PreInstall\PreInstallNotification.ps1' -#[murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser($EXE, $ARG) - - -#[murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser("cmd.exe /c start /MIN powershell.exe -ExecutionPolicy ByPass -File C:\ProgramData\WaaS\PreInstall\PreInstallNotification.ps1") - - - -CMTraceLog -Message "Finished $ScriptName" -Type 1 -LogFile $LogFile -exit $exitcode -#endregion -##*============================================= -##* END SCRIPT BODY -##*============================================= -} -function Invoke-OSDCloudIPU { - <# - .SYNOPSIS - Starts an OSDCloud in-place upgrade workflow. - - .DESCRIPTION - Validates elevation, inspects the current device and operating system, resolves the target feature update image, prepares any required driver pack content, and launches Windows Setup with the requested upgrade options. - - .PARAMETER OSName - Specifies the target feature update image to download and install. - - .PARAMETER Silent - Runs Windows Setup with the quiet UI mode. - - .PARAMETER SkipDriverPack - Prevents driver pack download and integration even when a recommended driver pack is available. - - .PARAMETER NoReboot - Prevents Windows Setup from rebooting after the down-level phase completes. - - .PARAMETER DownloadOnly - Stops after downloading and preparing upgrade content without launching Setup. - - .PARAMETER DiagnosticPrompt - Enables the Windows Setup diagnostic command prompt. - - .PARAMETER SkipFinalize - Starts setup operations on the down-level OS without immediately initiating the offline phase. - - .PARAMETER Finalize - Completes previously started setup operations and immediately reboots to start the offline phase. - - .PARAMETER DynamicUpdate - Enables Windows Setup Dynamic Update so setup can search for and install updates during the upgrade. - - .EXAMPLE - Invoke-OSDCloudIPU -OSName 'Windows 11 24H2 x64' -Silent -DynamicUpdate - Downloads the 24H2 x64 image and starts the upgrade with a quiet setup experience and Dynamic Update enabled. - - .NOTES - Author: David Segura - Recast Software - 2026-07-10 - Standardized comment-based help metadata and links. - - .LINK - https://github.com/OSDeploy/OSD/tree/master/docs - - .LINK - https://learn.microsoft.com/en-us/windows/deployment/upgrade/log-files - - .LINK - https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11 - #> - - [CmdletBinding(DefaultParameterSetName = 'Default')] - param ( - - [Parameter(ParameterSetName = 'Default')] - [ValidateSet( - 'Windows 11 24H2 x64', - 'Windows 11 24H2 ARM64', - 'Windows 11 23H2 x64', - 'Windows 11 23H2 ARM64', - 'Windows 11 22H2 x64', - 'Windows 11 21H2 x64', - 'Windows 10 22H2 x64', - 'Windows 10 22H2 ARM64')] - [System.String] - $OSName = 'Windows 11 24H2 x64', - - [switch] - $Silent, - - [switch] - $SkipDriverPack, - - [switch] - $NoReboot, - - [switch] - $DownloadOnly, - - [switch] - $DiagnosticPrompt, - - [switch] - $SkipFinalize, - - [switch] - $Finalize, - - [switch] - $DynamicUpdate - ) - #region Admin Elevation - $whoiam = [system.security.principal.windowsidentity]::getcurrent().name - $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") - if ($isElevated) { - Write-Host -ForegroundColor Green "[+] Running as $whoiam and IS Admin Elevated" - } - else { - Write-Warning "[-] Running as $whoiam and is NOT Admin Elevated" - Break - } - - #============================================================================ - #region Functions - #============================================================================ - function Get-TPMVer { - $Manufacturer = (Get-WmiObject -Class:Win32_ComputerSystem).Manufacturer - if ($Manufacturer -match "HP") - { - if ($((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion) -match "1.2") - { - $versionInfo = (Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).ManufacturerVersionInfo - $verMaj = [Convert]::ToInt32($versionInfo[0..1] -join '', 16) - $verMin = [Convert]::ToInt32($versionInfo[2..3] -join '', 16) - $verBuild = [Convert]::ToInt32($versionInfo[4..6] -join '', 16) - $verRevision = 0 - [version]$ver = "$verMaj`.$verMin`.$verBuild`.$verRevision" - Write-Output "TPM Version: $ver | Spec: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion)" - } - else {Write-Output "TPM Version: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).ManufacturerVersion) | Spec: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion)"} - } - - else - { - if ($((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion) -match "1.2") - { - Write-Output "TPM Version: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).ManufacturerVersion) | Spec: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion)" - } - else {Write-Output "TPM Version: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).ManufacturerVersion) | Spec: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion)"} - } - } - - #endregion Functions - - #============================================================================ - #region Device Info - #============================================================================ - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Starting Invoke-OSDCloudIPU" - Write-Host -ForegroundColor Gray "Looking of Details about this device...." - - - $BIOSInfo = Get-WmiObject -Class 'Win32_Bios' - - # Get the current BIOS release date and format it to datetime - $CurrentBIOSDate = [System.Management.ManagementDateTimeConverter]::ToDatetime($BIOSInfo.ReleaseDate).ToUniversalTime() - - $Manufacturer = (Get-WmiObject -Class:Win32_ComputerSystem).Manufacturer - $ManufacturerBaseBoard = (Get-CimInstance -Namespace root/cimv2 -ClassName Win32_BaseBoard).Manufacturer - $ComputerModel = (Get-WmiObject -Class:Win32_ComputerSystem).Model - if ($ManufacturerBaseBoard -eq "Intel Corporation") - { - $ComputerModel = (Get-CimInstance -Namespace root/cimv2 -ClassName Win32_BaseBoard).Product - } - $HPProdCode = (Get-CimInstance -Namespace root/cimv2 -ClassName Win32_BaseBoard).Product - $Serial = (Get-WmiObject -class:win32_bios).SerialNumber - $cpuDetails = @(Get-WmiObject -Class Win32_Processor)[0] - - Write-Output "Computer Name: $env:computername" - $CurrentOSInfo = Get-Item -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' - $WindowsRelease = $CurrentOSInfo.GetValue('ReleaseId') - if ($WindowsRelease -eq "2009"){$WindowsRelease = $CurrentOSInfo.GetValue('DisplayVersion')} - $Build = $($CurrentOSInfo.GetValue('CurrentBuild')) - $BuildUBR_CurrentOS = $Build +"."+$($CurrentOSInfo.GetValue('UBR')) - if ($Build -le 19045){$WinVer = "10"} - else {$WinVer = "11"} - Write-Output "Windows $WinVer $WindowsRelease | $BuildUBR_CurrentOS" - Write-Output "Architecture ('env:PROCESSOR_ARCHITECTURE'): $env:PROCESSOR_ARCHITECTURE " - Write-Output "Architecture (Get-NativeMatchineImage): $((Get-NativeMatchineImage).NativeMachine)" - Write-Output "Computer Model: $ComputerModel" - Write-Output "Serial: $Serial" - if ($Manufacturer -like "HP" -or $Manufacturer -like "Hewlett"){Write-Output "Computer Product Code: $HPProdCode"} - Write-Output $cpuDetails.Name - Write-Output "Current BIOS Level: $($BIOSInfo.SMBIOSBIOSVersion) From Date: $CurrentBIOSDate" - Get-TPMVer - $TimeUTC = [System.DateTime]::UtcNow - $TimeCLT = get-date - Write-Output "Current Client Time: $TimeCLT" - Write-Output "Current Client UTC: $TimeUTC" - Write-Output "Time Zone: $(Get-TimeZone)" - $Locale = Get-WinSystemLocale - if ($Locale -ne "en-US"){Write-Output "WinSystemLocale: $locale"} - $FreeSpace = (Get-CimInstance win32_LogicalDisk -Filter "DeviceID='C:'").FreeSpace/1GB -as [int] - $DiskSize = (Get-CimInstance win32_LogicalDisk -Filter "DeviceID='C:'").Size/1GB -as [int] - Write-Output "C:\ Drive Size: $DiskSize, Free Space: $FreeSpace" - - if ($Build -le 19045){ - $Win11 = Get-Win11Readiness - if ($Win11.Return -eq "CAPABLE"){ - Write-Host -ForegroundColor Green "Device is Windows 11 CAPABLE" - } - else { - Write-Host -ForegroundColor Yellow "Device is !NOT! Windows 11 CAPABLE" - if ($Build -eq 19045){ - write-host -ForegroundColor Yellow "This Device is already at the latest supported Version of Windows for this Hardware" - } - elseif ($Build -lt 19045){ - write-host -ForegroundColor Green "But.. You can upgrade it to Windows 10 22H2" - } - } - } - - #$OSVersion = "Windows $($OSName.split(" ")[1])" - #$OSReleaseID = $OSName.split(" ")[2] - #$Product = (Get-MyComputerProduct) - - $DriverPack = Get-OSDCloudDriverPack # -Product $Product -OSVersion $OSVersion -OSReleaseID $OSReleaseID - if ($DriverPack){ - Write-host -ForegroundColor Gray "Recommended Driverpack for upgrade: $($DriverPack.Name)" - if ($SkipDriverPack){ - write-host -ForegroundColor Yellow "Skipping Download and Integration [-SkipDriverPack]" - } - } - - #endregion Device Info - - #============================================================================ - #region Current Activation - #============================================================================ - - if (!($OSEdition)){ - $OSEdition = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name "EditionID" - } - if (!($OSLanguage)){ - $OSLanguage = (Get-WinSystemLocale).Name - } - if (!($OSActivation)){ - $OSActivation = (Get-CimInstance SoftwareLicensingProduct -Filter "Name like 'Windows%'" | Where-Object { $_.PartialProductKey }).ProductKeyChannel - } - if ($OSActivation -match "OEM"){ - $OSActivation = "Retail" - } - $OSArch = $env:PROCESSOR_ARCHITECTURE - if ($OSArch -eq "AMD64"){$OSArch = 'x64'} - #endregion Current Activation - - if ($OSArch -eq "ARM64"){ - #================================================= - # OSEditionId and OSActivation ARM64 - #================================================= - if (($OSEdition -eq 'Home') -or ($OSEdition -eq 'Core')) { - $OSEditionId = 'Core' - $OSActivation = 'Retail' - $OSImageIndex = 4 - } - if ($OSEdition -eq 'Home Single Language') { - $OSEditionId = 'CoreSingleLanguage' - $OSActivation = 'Retail' - $OSImageIndex = 5 - } - if (($OSEdition -eq 'Pro') -or ($OSEdition -eq 'Professional')) { - $OSEditionId = 'Professional' - if ($OSActivation -eq 'Retail') {$OSImageIndex = 6} - if ($OSActivation -eq 'Volume') {$OSImageIndex = 8} - } - } - else { - #================================================= - # OSEditionId and OSActivation x64 (AMD64) - #================================================= - if (($OSEdition -eq 'Home') -or ($OSEdition -eq 'Core')) { - $OSEditionId = 'Core' - $OSActivation = 'Retail' - $OSImageIndex = 4 - } - if (($OSEdition -eq 'Home N') -or ($OSEdition -eq 'CoreN')) { - $OSEditionId = 'CoreN' - $OSActivation = 'Retail' - $OSImageIndex = 5 - } - if ($OSEdition -eq 'Home Single Language') { - $OSEditionId = 'CoreSingleLanguage' - $OSActivation = 'Retail' - $OSImageIndex = 6 - } - if ($OSEdition -eq 'Enterprise') { - $OSEditionId = 'Enterprise' - $OSActivation = 'Volume' - $OSImageIndex = 6 - } - if (($OSEdition -eq 'Enterprise N') -or ($OSEdition -eq 'EnterpriseN')) { - $OSEditionId = 'EnterpriseN' - $OSActivation = 'Volume' - $OSImageIndex = 7 - } - if ($OSEdition -eq 'Education') { - $OSEditionId = 'Education' - if ($OSActivation -eq 'Retail') {$OSImageIndex = 7} - if ($OSActivation -eq 'Volume') {$OSImageIndex = 4} - } - if (($OSEdition -eq 'Education N') -or ($OSEdition -eq 'EducationN')) { - $OSEditionId = 'EducationN' - if ($OSActivation -eq 'Retail') {$OSImageIndex = 8} - if ($OSActivation -eq 'Volume') {$OSImageIndex = 5} - } - if (($OSEdition -eq 'Pro') -or ($OSEdition -eq 'Professional')) { - $OSEditionId = 'Professional' - if ($OSActivation -eq 'Retail') {$OSImageIndex = 9} - if ($OSActivation -eq 'Volume') {$OSImageIndex = 8} - } - if (($OSEdition -eq 'Pro N') -or ($OSEdition -eq 'ProfessionalN')) { - $OSEditionId = 'ProfessionalN' - if ($OSActivation -eq 'Retail') {$OSImageIndex = 10} - if ($OSActivation -eq 'Volume') {$OSImageIndex = 9} - } - } - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor DarkCyan "These are set automatically based on your current OS" - Write-Host -ForegroundColor Cyan "OSEditionId: " -NoNewline - Write-Host -ForegroundColor Green $OSEditionId - Write-Host -ForegroundColor Cyan "OSImageIndex: " -NoNewline - Write-Host -ForegroundColor Green $OSImageIndex - Write-Host -ForegroundColor Cyan "OSLanguage: " -NoNewline - Write-Host -ForegroundColor Green $OSLanguage - Write-Host -ForegroundColor Cyan "OSActivation: " -NoNewline - Write-Host -ForegroundColor Green $OSActivation - Write-Host -ForegroundColor Cyan "OSArch: " -NoNewline - Write-Host -ForegroundColor Green $OSArch - - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Starting Feature Update lookup and Download" - - #============================================================================ - #region Detect & Download ESD File - #============================================================================ - - $ScratchLocation = 'c:\OSDCloud\IPU' - $OSMediaLocation = 'c:\OSDCloud\OS' - $MediaLocation = "$ScratchLocation\Media" - if (!(Test-Path -Path $OSMediaLocation)){New-Item -Path $OSMediaLocation -ItemType Directory -Force | Out-Null} - if (!(Test-Path -Path $ScratchLocation)){New-Item -Path $ScratchLocation -ItemType Directory -Force | Out-Null} - if (Test-Path -Path $MediaLocation){Remove-Item -Path $MediaLocation -Force -Recurse} - New-Item -Path $MediaLocation -ItemType Directory -Force | Out-Null - - $ESD = Get-FeatureUpdate -OSName $OSName -OSActivation $OSActivation -OSLanguage $OSLanguage -OSArchitecture $OSArch - if (!($ESD)){ - Write-Host -ForegroundColor Red "Unable to Determine proper ESD Upgrade File" - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Unable to Determine proper ESD Upgrade File" - } - Write-Host -ForegroundColor Cyan "Name: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Name - Write-Host -ForegroundColor Cyan "Architecture: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Architecture - Write-Host -ForegroundColor Cyan "Activation: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Activation - Write-Host -ForegroundColor Cyan "Build: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Build - Write-Host -ForegroundColor Cyan "FileName: " -NoNewline - Write-Host -ForegroundColor Green $ESD.FileName - Write-Host -ForegroundColor Cyan "Url: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Url - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Getting Content for Upgrade Media" - - #Build Media Paths - $SubFolderName = "$($ESD.Version) $($ESD.ReleaseId)" - $ImageFolderPath = "$OSMediaLocation\$SubFolderName" - if (!(Test-Path -Path $ImageFolderPath)){New-Item -Path $ImageFolderPath -ItemType Directory -Force | Out-Null} - $ImagePath = "$ImageFolderPath\$($ESD.FileName)" - $ImageDownloadRequired = $true - - #Check Flash Drive for Media - $OSDCloudUSB = Get-Volume.usb | Where-Object {($_.FileSystemLabel -match 'OSDCloud') -or ($_.FileSystemLabel -match 'BHIMAGE')} | Select-Object -First 1 - if ($OSDCloudUSB){ - $USBImagePath = "$($OSDCloudUSB.DriveLetter):\OSDCloud\OS\$SubFolderName\$($ESD.FileName)" - if ((Test-Path -path $USBImagePath) -and (!(Test-Path -path $ImagePath))){ - Write-Host -ForegroundColor Green "Found media on OSDCloudUSB - Copying Local" - Copy-Item -Path $USBImagePath -Destination $ImagePath - } - } - - #Test for Media - if (Test-path -path $ImagePath){ - Write-Host -ForegroundColor Gray "Found previously downloaded media, getting SHA1 Hash" - $SHA1Hash = Get-FileHash $ImagePath -Algorithm SHA1 - if ($SHA1Hash.Hash -eq $esd.SHA1){ - Write-Host -ForegroundColor Gray "SHA1 Match on $ImagePath, skipping Download" - $ImageDownloadRequired = $false - } - else { - Write-Host -ForegroundColor Gray "SHA1 Match Failed on $ImagePath, removing content" - } - - } - if ($ImageDownloadRequired -eq $true){ - #Save-WebFile -SourceUrl $ESD.Url -DestinationDirectory $ScratchLocation -DestinationName $ESD.FileName - Write-Host -ForegroundColor Gray "Starting Download to $ImagePath, this takes awhile" - - <# This was taking way too long for some files - #Get ESD Size - $req = [System.Net.HttpWebRequest]::Create("$($ESD.Url)") - $res = $req.GetResponse() - (Invoke-WebRequest $ESD.Url -Method Head).Headers.'Content-Length' - $ESDSizeMB = $([Math]::Round($res.ContentLength /1000000)) - Write-Host "Total Size: $ESDSizeMB MB" - #> - - #Clear Out any Previous Attempts - $ExistingBitsJob = Get-BitsTransfer -Name "$($ESD.FileName)" -AllUsers -ErrorAction SilentlyContinue - If ($ExistingBitsJob) { - Remove-BitsTransfer -BitsJob $ExistingBitsJob - } - - if ((Get-Service -name BITS).Status -ne "Running"){ - Write-Host -ForegroundColor Yellow "BITS Service is not Running, which is required to download ESD File, attempting to Start" - $StartBITS = Start-Service -Name BITS -PassThru - Start-Sleep -Seconds 2 - if ($StartBITS.Status -ne "Running"){ - - } - } - #Start Download using BITS - Write-Host -ForegroundColor DarkGray "Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName $($ESD.FileName) -Description 'Windows Media Download' -RetryInterval 60" - $BitsJob = Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName "$($ESD.FileName)" -Description "Windows Media Download" -RetryInterval 60 - If ($BitsJob.JobState -eq "Error"){ - write-Host "BITS transfer failed: $($BitsJob.ErrorDescription)" - } - - } - - #endregion Detect & Download ESD File - - #============================================================================ - #region Extract of ESD file to create Setup Content - #============================================================================ - - - #Grab ESD File and create bootable ISO - if ((!(Test-Path -Path $ImagePath)) -or (!(Test-Path -Path $MediaLocation))){ - if (!(Test-Path -Path $ImagePath)){ - Write-Host -ForegroundColor Red "Missing $ImagePath, double check download process" - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Failed to find $ImagePath, double check download process" - } - if (!(Test-Path -Path $MediaLocation)){ - Write-Host -ForegroundColor Red "Missing $MediaLocation, double check folder exist" - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Faield to find $MediaLocation, double check folder exist" - } - } - if ((Test-Path -Path $ImagePath) -and (Test-Path -Path $MediaLocation)){ - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Starting Extract of ESD file to create Setup Content" - $ApplyPath = $MediaLocation - Write-Host -ForegroundColor Gray "Expanding $ImagePath Index 1 to $ApplyPath" - $Expand = Expand-WindowsImage -ImagePath $ImagePath -Index 1 -ApplyPath $ApplyPath - ##Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 2 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType max -CheckIntegrity - ##Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 3 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType max -CheckIntegrity -Setbootable - Write-Host -ForegroundColor Gray "Expanding $ImagePath Index $OSImageIndex to $ApplyPath\Sources\install.wim" - $Expand = Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex $OSImageIndex -DestinationImagePath "$ApplyPath\Sources\install.wim" -CheckIntegrity - ##Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 5 -DestinationImagePath "$ApplyPath\Sources\install.wim" -CompressionType max -CheckIntegrity - $null = $Expand - } - - #endregion Extract of ESD file to create Setup Content - - if (!(Test-Path -Path "$MediaLocation\Setup.exe")){ - Write-Host -ForegroundColor Red "Setup.exe not found, something went wrong" - throw - } - if (!(Test-Path -Path "$MediaLocation\sources\install.wim")){ - Write-Host -ForegroundColor Red "install.wim not found, something went wrong" - throw - } - - - if (($DriverPack) -and (!($SkipDriverPack))){ - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Getting Driver Pack for IPU Integration" - $DriverPackDownloadRequired = $true - if (!(Test-Path -Path "C:\Drivers")){New-Item -Path "C:\Drivers" -ItemType Directory -Force | Out-Null} - $DriverPackPath = "C:\Drivers\$($DriverPack.FileName)" - if (Test-path -path $DriverPackPath){ - Write-Host -ForegroundColor Gray "Found previously downloaded DriverPack File, getting MD5 Hash" - $MD5Hash = Get-FileHash $DriverPackPath -Algorithm MD5 - if ($MD5Hash.Hash -eq $DriverPack.HashMD5){ - Write-Host -ForegroundColor Gray "MD5 Match on $DriverPackPath, skipping Download" - $DriverPackDownloadRequired = $false - } - else { - Write-Host -ForegroundColor Gray "MD5 Match Failed on $DriverPackPath, removing content" - } - } - - IF ($DriverPackDownloadRequired -eq $true){ - Write-Host -ForegroundColor Gray "Starting Download to $DriverPackPath, this takes awhile" - <# - #Get DrivePack Size - $req = [System.Net.HttpWebRequest]::Create("$($DriverPack.Url)") - $res = $req.GetResponse() - (Invoke-WebRequest $ESD.Url -Method Head).Headers.'Content-Length' - $SizeMB = $([Math]::Round($res.ContentLength /1000000)) - Write-Host "Total Size: $SizeMB MB" - #> - - #Clear Out any Previous Attempts - $ExistingBitsJob = Get-BitsTransfer -Name "$($DriverPack.FileName)" -AllUsers -ErrorAction SilentlyContinue - If ($ExistingBitsJob) { - Remove-BitsTransfer -BitsJob $ExistingBitsJob - } - - #Start Download using BITS - $BitsJob = Start-BitsTransfer -Source $DriverPack.Url -Destination $DriverPackPath -DisplayName "$($DriverPack.FileName)" -Description "Driver Pack Download" -RetryInterval 60 - If ($BitsJob.JobState -eq "Error"){ - write-Host "BITS tranfer failed: $($BitsJob.ErrorDescription)" - } - } - #Expand Driver Pack - if (Test-path -path $DriverPackPath){ - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Expanding DriverPack for Upgrade Media" - Expand-StagedDriverPack - $DriverPackFile = Get-ChildItem -Path $DriverPackPath -Filter $DriverPack.FileName - - if ($Manufacturer -like "LENOVO"){ - $DriverPackExpandPath = "$($DriverPackFile.Directory)\SCCM\$($DriverPackFile.BaseName)" - } - else{ - $DriverPackExpandPath = Join-Path $DriverPackFile.Directory $DriverPackFile.BaseName - } - if (Test-Path -Path $DriverPackExpandPath){ - Write-Host -ForegroundColor Green "Confirmed Driver Pack Expanded to $DriverPackExpandPath" - } - else { - Write-Host -ForegroundColor Red "Driver Pack Failed to Expand to $DriverPackExpandPath" - if ($Silent){ - Write-Host -ForegroundColor Red "Continuing without Driver Pack integration" - } - else { - $DriverContinueInput = Read-Host "Do you want to continue without Driver Pack? (Y/N)" - if ($DriverContinueInput -eq 'Y' -or $DriverContinueInput -eq 'y') { - Write-Host -ForegroundColor Red "Continuing without Driver Pack integration" - } elseif ($DriverContinueInput -eq 'N' -or $DriverContinueInput -eq 'n') { - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Driver Pack Failed to Expand to $DriverPackExpandPath" - } else { - Write-Output "Invalid input. Please enter Y or N." - } - } - } - } - } - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Triggering Windows Upgrade Setup" - - if ($DownloadOnly){ - Write-Host -ForegroundColor Yellow "Download Complete, exiting script before install based on 'DownloadOnly' switch" - } - else { - #============================================================================ - #region Creating Arguments based on Parameters - #============================================================================ - #Driver Integration - Adds .inf-style drivers to the new Windows 10 installation. - if ($DriverPack){ - if ($DriverPackPath){ - if (Test-path -path $DriverPackPath){ - $driverarg = "/InstallDrivers $DriverPackExpandPath" - } - } - } - else { - $DriverArg = "" - } - - #Run Silently - This will suppress any Windows Setup user experience including the rollback user experience. - if ($Silent){ - $SilentArg = "/quiet" - } - else{ - $SilentArg = "" - } - - #Dynamic Updates - Specifies whether Windows Setup will perform Dynamic Update operations (search, download, and install updates). - if ($DynamicUpdate){ - $DynamicUpdateArg = "/DynamicUpdate Enable" - } - else{ - $DynamicUpdateArg = "/DynamicUpdate Disable" - } - - #Diagnostic Prompt - Specifies that the Command Prompt is available during Windows Setup. - if ($DiagnosticPrompt){ - $DiagnosticPromptArg = "/diagnosticprompt enable" - } - else{ - $DiagnosticPromptArg = "" - } - #Skip Finalize - Instructions setup to start update operations on the down-level OS without initiating a reboot to start the offline phase. - #https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11#skipfinalize - if ($SkipFinalize){ - $SkipFinalizeArg = "/SkipFinalize" - } - else{ - $SkipFinalizeArg = "" - } - #Finalize - Instructions Windows Setup to finish previously started update operations on the down-level OS, followed by an immediate reboot to start the offline phase. - #https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11#finalize - if ($Finalize){ - $FinalizeArg = "/Finalize" - } - else{ - $FinalizeArg = "" - } - - #No Reboot - Instructs Windows Setup not to restart the computer after the down-level phase of Windows Setup completes. - #https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11#noreboot - if ($NoReboot){ - $NoRebootArg = "/noreboot" - } - else{ - $NoRebootArg = "" - } - - $ParamStartProcess = @{ - FilePath = "$MediaLocation\Setup.exe" - ArgumentList = "/Auto Upgrade $DynamicUpdateArg /EULA accept $DriverArg /Priority High $SilentArg $DiagnosticPromptArg $NoRebootArg $SkipFinalizeArg $FinalizeArg" - } - - Write-Host -ForegroundColor Cyan "Setup Path: " -NoNewline - Write-Host -ForegroundColor Green $ParamStartProcess.FilePath - Write-Host -ForegroundColor Cyan "Arguments: " -NoNewline - Write-Host -ForegroundColor Green $ParamStartProcess.ArgumentList - - - #endregion Creating Arguments based on Parameters - - Start-Process @ParamStartProcess - } -} -function New-OSDCloudOSWimFile { - <# - .SYNOPSIS - Builds Windows setup media content for an OSDCloud feature update. - - .DESCRIPTION - Resolves the target operating system image, determines the correct image index for the requested edition, downloads or locates the matching ESD, expands the setup content, and optionally creates an ISO file. - - .PARAMETER OSName - Specifies the Windows release and architecture to build media for. - - .PARAMETER OSEdition - Specifies the Windows edition to package into the setup media. - - .PARAMETER OSLanguage - Specifies the language and culture of the Windows image. - - .PARAMETER OSActivation - Specifies whether the image should target Retail or Volume activation. - - .PARAMETER CreateISO - Creates an ISO file from the generated setup content after the image is prepared. - - .EXAMPLE - New-OSDCloudOSWimFile -OSName 'Windows 11 25H2 x64' -OSEdition Pro -OSLanguage en-us -OSActivation Retail -CreateISO - Prepares the Windows 11 25H2 x64 Pro retail media and builds an ISO file. - - .NOTES - Author: David Segura - Recast Software - 2026-07-10 - Standardized comment-based help metadata and links. - - .LINK - https://github.com/OSDeploy/OSD/tree/master/docs - - .LINK - https://learn.microsoft.com/en-us/windows/deployment/upgrade/log-files - - .LINK - https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11 - #> - - [CmdletBinding(DefaultParameterSetName = 'Default')] - param ( - - [Parameter(ParameterSetName = 'Default')] - [ValidateSet( - 'Windows 11 25H2 x64', - 'Windows 11 25H2 ARM64', - 'Windows 11 24H2 x64', - 'Windows 11 24H2 ARM64', - 'Windows 11 23H2 x64', - 'Windows 11 23H2 ARM64', - 'Windows 11 22H2 x64', - 'Windows 11 21H2 x64', - 'Windows 10 22H2 x64', - 'Windows 10 22H2 ARM64')] - [System.String] - $OSName = 'Windows 11 25H2 x64', - - #Operating System Edition of the Windows installation - #Alias = Edition - [Parameter(ParameterSetName = 'Default')] - [Parameter(ParameterSetName = 'Legacy')] - [ValidateSet('Home','Home N','Home Single Language','Education','Education N','Enterprise','Enterprise N','Pro','Pro N')] - [Alias('Edition')] - [System.String] - $OSEdition = 'Pro', - - #Operating System Language of the Windows installation - #Alias = Culture, OSCulture - [Parameter(ParameterSetName = 'Default')] - [Parameter(ParameterSetName = 'Legacy')] - [ValidateSet ( - 'ar-sa','bg-bg','cs-cz','da-dk','de-de','el-gr', - 'en-gb','en-us','es-es','es-mx','et-ee','fi-fi', - 'fr-ca','fr-fr','he-il','hr-hr','hu-hu','it-it', - 'ja-jp','ko-kr','lt-lt','lv-lv','nb-no','nl-nl', - 'pl-pl','pt-br','pt-pt','ro-ro','ru-ru','sk-sk', - 'sl-si','sr-latn-rs','sv-se','th-th','tr-tr', - 'uk-ua','zh-cn','zh-tw' - )] - [Alias('Culture','OSCulture')] - [System.String] - $OSLanguage = 'en-us', - - #License of the Windows Operating System - [Parameter(ParameterSetName = 'Default')] - [Parameter(ParameterSetName = 'Legacy')] - [ValidateSet('Retail','Volume')] - [Alias('License','OSLicense','Activation')] - [System.String] - $OSActivation = 'Retail', - - #Create ISO File - Requries Windows ADK (oscdimg.exe) - [Parameter(ParameterSetName = 'Default')] - [Switch] - $CreateISO - - ) - #region Admin Elevation - $whoiam = [system.security.principal.windowsidentity]::getcurrent().name - $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") - if ($isElevated) { - Write-Host -ForegroundColor Green "[+] Running as $whoiam and IS Admin Elevated" - } - else { - Write-Warning "[-] Running as $whoiam and is NOT Admin Elevated" - Break - } - #================================================ - # Get Index & OS Info - #================================================ - <# - if ($OSName -match "ARM64"){ - $OSArch = 'ARM64' - $IndexInfo = Get-OSDCloudOperatingSystemsIndexes -OSArch ARM64 | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} - } - else { - $OSArch = 'x64' - $IndexInfo = Get-OSDCloudOperatingSystemsIndexes | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} - } - #> - - if ($OSName -match "ARM64"){ - $OSArch = 'ARM64' - $OSDCloudOperatingSystem = (Get-OSDCloudOperatingSystems -OSArch arm64) | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} - $IndexMap = Get-OSDCloudOperatingSystemsIndexMap -OSArch arm64 | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} - } - else { - $OSArch = 'x64' - $OSDCloudOperatingSystem = Get-OSDCloudOperatingSystems -OSArch x64 | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} - $IndexMap = Get-OSDCloudOperatingSystemsIndexMap -OSArch x64 | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} - } - - $OSEditionID = "$($OSDCloudOperatingSystem.Version) $OSEdition" - $OSImageIndex = $IndexMap.Indexes.$OSEditionID - - if ($OSImageIndex -eq $null){ - Write-Host -ForegroundColor Red "Unable to determine OSImageIndex for Index $OSEdition" - Write-Host -ForegroundColor Yellow "Available Indexes are $($OSDCloudOperatingSystem.IndexNames.replace($(($OSDCloudOperatingSystem).Version),'') -join ', ')" - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Unable to determine OSImageIndex for $OSName $OSEdition $OSActivation $OSLanguage" - } - - #$OSBuild = $OSDCloudOperatingSystem.Build - #$OSReleaseID = $OSDCloudOperatingSystem.ReleaseID - #$OSVersion = $OSDCloudOperatingSystem.Version - - #$ImageFileName = $OSDCloudOperatingSystem.FileName - #$ImageFileUrl = $OSDCloudOperatingSystem.Url - - $ImageFileItem = Find-OSDCloudFile -Name $OSDCloudOperatingSystem.FileName -Path '\OSDCloud\OS\' | Sort-Object FullName | Where-Object {$_.Length -gt 3GB} - $ImageFileItem = $ImageFileItem | Where-Object {$_.FullName -notlike "C*"} | Where-Object {$_.FullName -notlike "X*"} | Select-Object -First 1 - - - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor DarkCyan "These are set based on your input parameters" - Write-Host -ForegroundColor Cyan "OSEditionId: " -NoNewline - Write-Host -ForegroundColor Green $OSEdition - Write-Host -ForegroundColor Cyan "OSImageIndex: " -NoNewline - Write-Host -ForegroundColor Green $OSImageIndex - Write-Host -ForegroundColor Cyan "OSLanguage: " -NoNewline - Write-Host -ForegroundColor Green $OSLanguage - Write-Host -ForegroundColor Cyan "OSActivation: " -NoNewline - Write-Host -ForegroundColor Green $OSActivation - Write-Host -ForegroundColor Cyan "OSArch: " -NoNewline - Write-Host -ForegroundColor Green $OSArch - - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Starting Feature Update lookup and Download" - - #============================================================================ - #region Detect & Download ESD File - #============================================================================ - - $ScratchLocation = 'c:\OSDCloud\IPU' - $OSMediaLocation = 'c:\OSDCloud\OS' - $MediaLocation = "$ScratchLocation\Media\$OSName" - if (!(Test-Path -Path $OSMediaLocation)){New-Item -Path $OSMediaLocation -ItemType Directory -Force | Out-Null} - if (!(Test-Path -Path $ScratchLocation)){New-Item -Path $ScratchLocation -ItemType Directory -Force | Out-Null} - if (Test-Path -Path $MediaLocation){Remove-Item -Path $MediaLocation -Force -Recurse} - New-Item -Path $MediaLocation -ItemType Directory -Force | Out-Null - - $ESD = Get-FeatureUpdate -OSName $OSName -OSActivation $OSActivation -OSLanguage $OSLanguage -OSArchitecture $OSArch - if (!($ESD)){ - Write-Host -ForegroundColor Red "Unable to Determine proper ESD Upgrade File" - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Unable to Determine proper ESD Upgrade File" - } - Write-Host -ForegroundColor Cyan "Name: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Name - Write-Host -ForegroundColor Cyan "Architecture: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Architecture - Write-Host -ForegroundColor Cyan "Activation: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Activation - Write-Host -ForegroundColor Cyan "Build: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Build - Write-Host -ForegroundColor Cyan "FileName: " -NoNewline - Write-Host -ForegroundColor Green $ESD.FileName - Write-Host -ForegroundColor Cyan "Url: " -NoNewline - Write-Host -ForegroundColor Green $ESD.Url - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Getting Content for Upgrade Media" - - <##> - #Build Media Paths - $SubFolderName = "$($ESD.Version) $($ESD.ReleaseId)" - $ImageFolderPath = "$OSMediaLocation\$SubFolderName" - if (!(Test-Path -Path $ImageFolderPath)){New-Item -Path $ImageFolderPath -ItemType Directory -Force | Out-Null} - $ImagePath = "$ImageFolderPath\$($ESD.FileName)" - $ImageDownloadRequired = $true - - #Check Flash Drive for Media - $OSDCloudUSB = Get-Volume.usb | Where-Object {($_.FileSystemLabel -match 'OSDCloud') -or ($_.FileSystemLabel -match 'BHIMAGE')} | Select-Object -First 1 - if ($OSDCloudUSB){ - $USBImagePath = "$($OSDCloudUSB.DriveLetter):\OSDCloud\OS\$SubFolderName\$($ESD.FileName)" - if ((Test-Path -path $USBImagePath) -and (!(Test-Path -path $ImagePath))){ - Write-Host -ForegroundColor Green "Found media on OSDCloudUSB - Copying Local" - Copy-Item -Path $USBImagePath -Destination $ImagePath - } - } - - #Test for Media - if (Test-path -path $ImagePath){ - Write-Host -ForegroundColor Gray "Found previously downloaded media: $ImagePath" - write-host -ForegroundColor Gray " ... Getting SHA1 Hash for validation" - $SHA1Hash = Get-FileHash $ImagePath -Algorithm SHA1 - if ($SHA1Hash.Hash -eq $esd.SHA1){ - Write-Host -ForegroundColor Gray "SHA1 Match $($SHA1Hash.Hash), skipping Download" - $ImageDownloadRequired = $false - } - else { - Write-Host -ForegroundColor Gray "SHA1 Match Failed on $ImagePath, removing content" - } - - } - if ($ImageDownloadRequired -eq $true){ - #Save-WebFile -SourceUrl $ESD.Url -DestinationDirectory $ScratchLocation -DestinationName $ESD.FileName - Write-Host -ForegroundColor Gray "Starting Download to $ImagePath, this takes awhile" - - <# This was taking way too long for some files - #Get ESD Size - $req = [System.Net.HttpWebRequest]::Create("$($ESD.Url)") - $res = $req.GetResponse() - (Invoke-WebRequest $ESD.Url -Method Head).Headers.'Content-Length' - $ESDSizeMB = $([Math]::Round($res.ContentLength /1000000)) - Write-Host "Total Size: $ESDSizeMB MB" - #> - - #Clear Out any Previous Attempts - $ExistingBitsJob = Get-BitsTransfer -Name "$($ESD.FileName)" -AllUsers -ErrorAction SilentlyContinue - If ($ExistingBitsJob) { - Remove-BitsTransfer -BitsJob $ExistingBitsJob - } - - if ((Get-Service -name BITS).Status -ne "Running"){ - Write-Host -ForegroundColor Yellow "BITS Service is not Running, which is required to download ESD File, attempting to Start" - $StartBITS = Start-Service -Name BITS -PassThru - Start-Sleep -Seconds 2 - if ($StartBITS.Status -ne "Running"){ - - } - } - #Start Download using BITS - Write-Host -ForegroundColor DarkGray "Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName $($ESD.FileName) -Description 'Windows Media Download' -RetryInterval 60" - $BitsJob = Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName "$($ESD.FileName)" -Description "Windows Media Download" -RetryInterval 60 - If ($BitsJob.JobState -eq "Error"){ - write-Host "BITS tranfer failed: $($BitsJob.ErrorDescription)" - } - - } - - #endregion Detect & Download ESD File - - #============================================================================ - #region Extract of ESD file to create Setup Content - #============================================================================ - - #https://www.deploymentresearch.com/how-to-really-create-a-windows-10-build-10041-iso-no-3rd-party-tools-needed/ - #Using info from Johan's process to export properly - #DISM commands are left for reference only. - - #Grab ESD File and create bootable ISO - if ((!(Test-Path -Path $ImagePath)) -or (!(Test-Path -Path $MediaLocation))){ - if (!(Test-Path -Path $ImagePath)){ - Write-Host -ForegroundColor Red "Missing $ImagePath, double check download process" - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Failed to find $ImagePath, double check download process" - } - if (!(Test-Path -Path $MediaLocation)){ - Write-Host -ForegroundColor Red "Missing $MediaLocation, double check folder exist" - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Failed to find $MediaLocation, double check folder exist" - } - } - if ((Test-Path -Path $ImagePath) -and (Test-Path -Path $MediaLocation)){ - Write-Host -ForegroundColor DarkGray "=========================================================================" - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Starting Extract of ESD file to create Setup Content" - $ApplyPath = $MediaLocation - Write-Host -ForegroundColor Gray "Expanding $ImagePath Index 1 to $ApplyPath" - $Expand = Expand-WindowsImage -ImagePath $ImagePath -Index 1 -ApplyPath $ApplyPath - - # Create empty boot.wim file with compression type set to maximum - $EmptyFolder = "$($env:TEMP)\EmptyFolder" - New-Item -ItemType Directory -Path $EmptyFolder -Force | Out-Null - #dism.exe /Capture-Image /ImageFile:$ISOMediaFolder\sources\boot.wim /CaptureDir:$EmptyFolder /Name:EmptyIndex /Compress:max - New-WindowsImage -ImagePath $ApplyPath\Sources\boot.wim -CapturePath $EmptyFolder -Name EmptyIndex -Description "Empty Index" -CompressionType Fast - - # Export base Windows PE to empty boot.wim file (creating a second index) - #dism.exe /Export-image /SourceImageFile:$ESDFile /SourceIndex:2 /DestinationImageFile:$ISOMediaFolder\sources\boot.wim /Compress:Recovery /Bootable - Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 2 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType Fast -CheckIntegrity -Setbootable - - # Delete the first empty index in boot.wim - #dism.exe /Delete-Image /ImageFile:$ISOMediaFolder\sources\boot.wim /Index:1 - Remove-WindowsImage -ImagePath $ApplyPath\Sources\boot.wim -Index 1 - - # Export Windows PE with Setup to boot.wim file - #dism.exe /Export-image /SourceImageFile:$ESDFile /SourceIndex:3 /DestinationImageFile:$ISOMediaFolder\sources\boot.wim /Compress:Recovery /Bootable - Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 3 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType Fast -CheckIntegrity -Setbootable - - # Create empty install.wim file with MDT/ConfigMgr friendly compression type (maximum) - #dism.exe /Capture-Image /ImageFile:$ISOMediaFolder\sources\install.wim /CaptureDir:C:\EmptyFolder /Name:EmptyIndex /Compress:max - New-WindowsImage -ImagePath $ApplyPath\Sources\install.wim -CapturePath $EmptyFolder -Name EmptyIndex -Description "Empty Index" -CompressionType Fast - - #Export the OS Image to the install.wim file - Write-Host -ForegroundColor Gray "Expanding $ImagePath Index $OSImageIndex to $ApplyPath\Sources\install.wim" - #dism.exe /Export-image /SourceImageFile:$ESDFile /SourceIndex:4 /DestinationImageFile:$ISOMediaFolder\sources\install.wim /Compress:Recovery - ##Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 5 -DestinationImagePath "$ApplyPath\Sources\install.wim" -CompressionType max -CheckIntegrity - $Expand = Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex $OSImageIndex -DestinationImagePath "$ApplyPath\Sources\install.wim" -CheckIntegrity -CompressionType Fast - $null = $Expand - - # Delete the first empty index in install.wim - #dism.exe /Delete-Image /ImageFile:$ISOMediaFolder\sources\install.wim /Index:1 - Remove-WindowsImage -ImagePath $ApplyPath\Sources\install.wim -Index 1 - } - - #endregion Extract of ESD file to create Setup Content - - if (!(Test-Path -Path "$MediaLocation\Setup.exe")){ - Write-Host -ForegroundColor Red "Setup.exe not found, something went wrong" - throw - } - if (!(Test-Path -Path "$MediaLocation\sources\install.wim")){ - Write-Host -ForegroundColor Red "install.wim not found, something went wrong" - throw - } - Write-Host -ForegroundColor DarkGray "=========================================================================" - if ($CreateISO){ - $PathToOscdimg = (Get-WindowsAdkPaths).oscdimgexe - if (!(Test-Path -Path $PathToOscdimg)){ - Write-Host -ForegroundColor Red "oscdimg.exe not found, unable to create ISO File" - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] oscdimg.exe not found, unable to create ISO File" - } - else { - Write-Host -ForegroundColor Cyan "[$(Get-Date -format s)] Creating ISO File" - $BootData='2#p0,e,b"{0}"#pEF,e,b"{1}"' -f "$ApplyPath\boot\etfsboot.com","$ApplyPath\efi\Microsoft\boot\efisys.bin" - - $ISOFile = "`"$ScratchLocation\$($ESD.Version) $($ESD.ReleaseId) $($ESD.Architecture).iso`"" - $ISOFilePath = "$ScratchLocation\$($ESD.Version) $($ESD.ReleaseId) $($ESD.Architecture).iso" - if (Test-Path -Path $ISOFilePath){Remove-Item -Path $ISOFilePath -Force} - $ISOMedia = "`"$ApplyPath`"" - $Proc = Start-Process -FilePath $PathToOscdimg -ArgumentList @("-bootdata:$BootData",'-u2','-udfver102',"$ISOMedia","$ISOFile") -PassThru -Wait -NoNewWindow - if($Proc.ExitCode -ne 0) - { - throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)]Failed to generate ISO with exitcode: $($Proc.ExitCode)" - } - if (Test-Path -Path $ISOFilePath){ - Write-Host -ForegroundColor Green "ISO File Created: $ISOFile" - } - else { - Write-Host -ForegroundColor Red "Failed to Create ISO File" - } - } - Write-Host -ForegroundColor DarkGray "=========================================================================" - } -} -function Set-Win11ReqBypassRegValues { - <# - .SYNOPSIS - Sets Windows 11 setup requirement bypass registry values. - - .DESCRIPTION - Detects whether the current environment is WinPE, OOBE, specialize, audit mode, or a running Windows installation and writes the LabConfig and MoSetup values needed to bypass Windows 11 hardware requirement checks during setup. - - .INPUTS - None. - - .OUTPUTS - None. - - .EXAMPLE - Set-Win11ReqBypassRegValues - Writes the appropriate bypass registry keys for the current phase. - - .NOTES - Author: David Segura - Recast Software - 2026-07-10 - Standardized comment-based help metadata and links. - - .LINK - https://github.com/OSDeploy/OSD/tree/master/docs - - .LINK - https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11 - #> - if ($env:SystemDrive -eq 'X:') { - $WindowsPhase = 'WinPE' - } - else { - $ImageState = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State' -ErrorAction Ignore).ImageState - if ($env:UserName -eq 'defaultuser0') {$WindowsPhase = 'OOBE'} - elseif ($ImageState -eq 'IMAGE_STATE_SPECIALIZE_RESEAL_TO_OOBE') {$WindowsPhase = 'Specialize'} - elseif ($ImageState -eq 'IMAGE_STATE_SPECIALIZE_RESEAL_TO_AUDIT') {$WindowsPhase = 'AuditMode'} - else {$WindowsPhase = 'Windows'} - } - - if ($WindowsPhase -eq 'WinPE'){ - #Insipiration & Some code from: https://github.com/JosephM101/Force-Windows-11-Install/blob/main/Win11-TPM-RegBypass.ps1 - - # Mount and edit the setup environment's registry - $REG_System = "C:\Windows\System32\config\system" - $VirtualRegistryPath_SYSTEM = "HKLM\WinPE_SYSTEM" #Load Command - $VirtualRegistryPath_Setup = "HKLM:\WinPE_SYSTEM\Setup" #PowerShell Path - - # $VirtualRegistryPath_LabConfig = $VirtualRegistryPath_Setup + "\LabConfig" - reg unload $VirtualRegistryPath_SYSTEM | Out-Null # Just in case... - Start-Sleep 1 - reg load $VirtualRegistryPath_SYSTEM $REG_System | Out-Null - - - - New-Item -Path $VirtualRegistryPath_Setup -Name "LabConfig" -Force | out-null - New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassTPMCheck" -Value 1 -PropertyType DWORD -Force | out-null - New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassSecureBootCheck" -Value 1 -PropertyType DWORD -Force | out-null - New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassRAMCheck" -Value 1 -PropertyType DWORD -Force | out-null - New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassStorageCheck" -Value 1 -PropertyType DWORD -Force | out-null - New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassCPUCheck" -Value 1 -PropertyType DWORD -Force | out-null - - New-Item -Path $VirtualRegistryPath_Setup -Name "MoSetup" -ErrorAction SilentlyContinue | out-null - New-ItemProperty -Path "$VirtualRegistryPath_Setup\MoSetup" -Name "AllowUpgradesWithUnsupportedTPMOrCPU" -Value 1 -PropertyType DWORD -Force | out-null - - - Start-Sleep 1 - reg unload $VirtualRegistryPath_SYSTEM - } - else { - if (!(Test-Path -Path HKLM:\SYSTEM\Setup\LabConfig)){ - New-Item -Path HKLM:\SYSTEM\Setup -Name "LabConfig" | out-null - } - New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassTPMCheck" -Value 1 -PropertyType DWORD -Force | out-null - New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassSecureBootCheck" -Value 1 -PropertyType DWORD -Force | out-null - New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassRAMCheck" -Value 1 -PropertyType DWORD -Force | out-null - New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassStorageCheck" -Value 1 -PropertyType DWORD -Force | out-null - New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassCPUCheck" -Value 1 -PropertyType DWORD -Force | out-null - if (!(Test-Path -Path HKLM:\SYSTEM\Setup\MoSetup)){ - New-Item -Path HKLM:\SYSTEM\Setup -Name "MoSetup" | out-null - } - New-ItemProperty -Path "HKLM:\SYSTEM\Setup\MoSetup" -Name "AllowUpgradesWithUnsupportedTPMOrCPU" -Value 1 -PropertyType DWORD -Force | out-null - } -} diff --git a/Public/OSDCloudIPU/Invoke-IPUPreInstallNotification.ps1 b/Public/OSDCloudIPU/Invoke-IPUPreInstallNotification.ps1 new file mode 100644 index 000000000..93ae3cb14 --- /dev/null +++ b/Public/OSDCloudIPU/Invoke-IPUPreInstallNotification.ps1 @@ -0,0 +1,223 @@ +function Invoke-IPUPreInstallNotifications { +<# +.SYNOPSIS + --. +.DESCRIPTION + Triggers Notifications when Upgrade Starts +.INPUTS + None. +.OUTPUTS + None. +.NOTES + Created by @gwblok +.LINK + https://garytown.com +.LINK + https://www.recastsoftware.com +.COMPONENT + -- +.FUNCTIONALITY + -- +#> + +## Set script requirements +#Requires -Version 3.0 + +##*============================================= +##* VARIABLE DECLARATION +##*============================================= +#region VariableDeclaration + +## Get script path and name +[string]$ScriptPath = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +[string]$ScriptName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Definition) + + +Start-Transcript +Write-Output -------------------------------------- +Write-Output $ScriptPath $ScriptName +Get-Date + +#Registry Path that will get Tagged +$registryPath = "HKLM:\SOFTWARE\WaaS" +$TimeStamp = Get-Date -f s +$keynameStart = "CA_PreInstallNotification_Start" +$keynameFinish = "CA_PreInstallNotification_Finish" +New-ItemProperty -Path $registryPath -Name $keynameStart -Value $TimeStamp -Force + + +#Logfile generated by this script +$WaaSFolder = "$($env:ProgramData)\WaaS" +$logfile = "$WaaSFolder\CustomActions.log" + +$whoami = whoami + +# Load some required namespaces +$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] +$null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] +Add-Type -Path "$PSScriptRoot\Microsoft.Toolkit.Uwp.Notifications.dll" +if (!($Global:ModuleBase = (Get-Module -Name OSD).ModuleBase)){Import-Module -Name OSD} +if ($Global:ModuleBase = (Get-Module -Name OSD).ModuleBase){ + $Global:NotificationsPath = "$Global:ModuleBase\Projects\assembly\Microsoft.Toolkit.Uwp.Notifications.dll" + +Write-Host "Adding Type Notifications: $Global:NotificationsPath" +Add-Type -Path $Global:NotificationsPath +} + + +#endregion +##*============================================= +##* END VARIABLE DECLARATION +##*============================================= + +##*============================================= +##* FUNCTION LISTINGS +##*============================================= +#region FunctionListings + +#CMTraceLog Function formats logging in CMTrace style + function CMTraceLog { + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + $Message, + + [Parameter(Mandatory=$false)] + $ErrorMessage, + + [Parameter(Mandatory=$false)] + $Component = "Notification", + + [Parameter(Mandatory=$false)] + [int]$Type, + + [Parameter(Mandatory=$true)] + $LogFile + ) + <# + Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) + #> + $Time = Get-Date -Format "HH:mm:ss.ffffff" + $Date = Get-Date -Format "MM-dd-yyyy" + + if ($ErrorMessage -ne $null) {$Type = 3} + if ($Component -eq $null) {$Component = " "} + if ($Type -eq $null) {$Type = 1} + + $LogMessage = "" + $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile + } + + + + +##*============================================= +##* END FUNCTION LISTINGS +##*============================================= + +##*============================================= +##* SCRIPT BODY +##*============================================= +#region ScriptBody + + +CMTraceLog -Message "--------------------------" -Type 1 -LogFile $LogFile +CMTraceLog -Message "Starting $ScriptName" -Type 1 -LogFile $LogFile +CMTraceLog -Message "Running as: $whoami" -Type 1 -LogFile $LogFile + +$SetupProgressPath = "HKLM:System\Setup\mosetup\volatile" +CMTraceLog -Message "Waiting For SetupProgress Value to be populated..." -Type 1 -LogFile $LogFile +$Minutes = 1 +DO + { + $SetupProgress = Get-ItemPropertyValue -Path $SetupProgressPath -Name "SetupProgress" -ErrorAction SilentlyContinue + if (!($SetupProgress)) + { + $Minutes += 1 + Start-Sleep -Seconds 60 + } + if ($Minutes -eq 20) + { + CMTraceLog -Message "Waited $Minutes Minutes, exiting script with Exit 20, I'm tired of waiting" -Type 3 -LogFile $LogFile + $TimeStamp = Get-Date -f s + New-ItemProperty -Path $registryPath -Name $keynameFinish -Value $TimeStamp -Force + exit 20 + } + } +Until ($SetupProgress) + +$ToastTag = "PowerShell" +$ToastGroup = "PowerShell" + +$ToastContentBuilder = [Microsoft.Toolkit.Uwp.Notifications.ToastContentBuilder]::new() +$ProgressBar = [Microsoft.Toolkit.Uwp.Notifications.AdaptiveProgressBar]@{ + Title = [Microsoft.Toolkit.Uwp.Notifications.BindableString]::new("progressTitle") + Value = [Microsoft.Toolkit.Uwp.Notifications.BindableProgressBarValue]::new("progressValue") + ValueStringOverride = [Microsoft.Toolkit.Uwp.Notifications.BindableString]::new("progressValueString") + Status = [Microsoft.Toolkit.Uwp.Notifications.BindableString]::new("progressStatus") +} + + +$ToastContent = $ToastContentBuilder.AddText("Upgrading Windows 10..."). + AddVisualChild($ProgressBar). + AddText("Please do not reboot until you're notified..."). + GetToastContent() +$Toast = [Windows.UI.Notifications.ToastNotification]::new($ToastContent.GetXml()) +$Toast.Tag = $ToastTag +$Toast.Group = $ToastGroup + +$dict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]' +$dict.Add("progressValue","$SetupProgressNumber") +$dict.Add("progressValueString","$SetupProgress% Complete") +$dict.Add("progressStatus","Installing...") +$dict.Add("progressTitle","Processing Feature Update of Windows to 20H2") + +$Toast.Data = [Windows.UI.Notifications.NotificationData]::new($dict, 0) + +CMTraceLog -Message "Triggering Toast" -Type 1 -LogFile $LogFile +$Notification = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Windows.SystemToast.SecurityAndMaintenance") +$Notification.Show($Toast) +### Run to here first to display + +Start-Sleep -Seconds 2 + +do + { + Start-Sleep -Seconds 5 + $SetupProgress = Get-ItemPropertyValue -Path $SetupProgressPath -Name "SetupProgress" -ErrorAction SilentlyContinue + $SetupProgressNumber = $SetupProgress / 100 + ### Update the Toast! (Run this separately to update) + $dict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]' + $dict.Add("progressValue","$SetupProgressNumber") + $dict.Add("progressValueString","$SetupProgress% Complete") + $dict.Add("progressStatus","Installing...") + $dict.Add("progressTitle","Processing Feature Update of Windows to 20H2") + $NotificationData = [Windows.UI.Notifications.NotificationData]::new($dict, 0) + [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Windows.SystemToast.SecurityAndMaintenance").Update($NotificationData, $ToastTag, $ToastGroup) + + + } +Until ($SetupProgress -eq "100") + +if ($SetupProgress -eq "100") + { + ### Update the Toast! (Run this separately to update) + $dict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]' + $dict.Add("progressValue","$SetupProgressNumber") + $dict.Add("progressValueString","$SetupProgress% Complete") + $dict.Add("progressStatus","Waiting for Restart") + $dict.Add("progressTitle","First Phase of Feature Update of Windows to 20H2 Complete") + $NotificationData = [Windows.UI.Notifications.NotificationData]::new($dict, 0) + [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Windows.SystemToast.SecurityAndMaintenance").Update($NotificationData, $ToastTag, $ToastGroup) + } + +$TimeStamp = Get-Date -f s +New-ItemProperty -Path $registryPath -Name $keynameFinish -Value $TimeStamp -Force +CMTraceLog -Message "Finished $ScriptName" -Type 1 -LogFile $LogFile +exit $exitcode +#endregion +##*============================================= +##* END SCRIPT BODY +##*============================================= + +} diff --git a/Public/OSDCloudIPU/Invoke-IPUPreInstallNotificationLauncher.ps1 b/Public/OSDCloudIPU/Invoke-IPUPreInstallNotificationLauncher.ps1 new file mode 100644 index 000000000..6a1fc880e --- /dev/null +++ b/Public/OSDCloudIPU/Invoke-IPUPreInstallNotificationLauncher.ps1 @@ -0,0 +1,430 @@ +Function Invoke-IPUPreInstallNotificationLauncher { + +<# +.SYNOPSIS + --. +.DESCRIPTION +Create a Process as Logged-On-User from PowerShell +Then Launch the PreInstall Notificaiton Script as the logged on user. + +.INPUTS + None. +.OUTPUTS + None. +.NOTES + Created by @gwblok +.LINK + https://garytown.com +.LINK + https://www.recastsoftware.com +.COMPONENT + -- +.FUNCTIONALITY + -- +#> + +## Set script requirements +#Requires -Version 3.0 + +##*============================================= +##* VARIABLE DECLARATION +##*============================================= +#region VariableDeclaration + +## Get script path and name +[string]$ScriptPath = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition) +[string]$ScriptName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Definition) + + +Start-Transcript +Write-Output -------------------------------------- +Write-Output $ScriptPath $ScriptName +Get-Date + +#Registry Path that will get Tagged +$registryPath = "HKLM:\SOFTWARE\WaaS" +$TimeStamp = Get-Date -f s +$keynameStart = "CA_PreInstallNotificationLauncher" +New-ItemProperty -Path $registryPath -Name $keynameStart -Value $TimeStamp -Force + + +#Logfile generated by this script +$WaaSFolder = "$($env:ProgramData)\WaaS" +$logfile = "$WaaSFolder\CustomActions.log" + +$whoami = whoami + + +# Load some required namespaces +$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] +$null = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] +$Null = [System.Security.AccessControl.FileSystemAccessRule] +Add-Type -Path "$PSScriptRoot\Microsoft.Toolkit.Uwp.Notifications.dll" + +#endregion +##*============================================= +##* END VARIABLE DECLARATION +##*============================================= + +##*============================================= +##* FUNCTION LISTINGS +##*============================================= +#region FunctionListings + +#CMTraceLog Function formats logging in CMTrace style + function CMTraceLog { + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$false)] + $Message, + + [Parameter(Mandatory=$false)] + $ErrorMessage, + + [Parameter(Mandatory=$false)] + $Component = "NotificationLauncher", + + [Parameter(Mandatory=$false)] + [int]$Type, + + [Parameter(Mandatory=$true)] + $LogFile + ) + <# + Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) + #> + $Time = Get-Date -Format "HH:mm:ss.ffffff" + $Date = Get-Date -Format "MM-dd-yyyy" + + if ($ErrorMessage -ne $null) {$Type = 3} + if ($Component -eq $null) {$Component = " "} + if ($Type -eq $null) {$Type = 1} + + $LogMessage = "" + $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile + } + +#Create a Process as Logged-On-User from PowerShell +#https://rzander.azurewebsites.net/create-a-process-as-loggedon-user/ +#https://github.com/murrayju/CreateProcessAsUser + +$Source = @" + +using System; +using System.Runtime.InteropServices; + +namespace murrayju.ProcessExtensions +{ + public static class ProcessExtensions + { + #region Win32 Constants + + private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; + private const int CREATE_NO_WINDOW = 0x08000000; + + private const int CREATE_NEW_CONSOLE = 0x00000010; + + private const uint INVALID_SESSION_ID = 0xFFFFFFFF; + private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; + + #endregion + + #region DllImports + + [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + private static extern bool CreateProcessAsUser( + IntPtr hToken, + String lpApplicationName, + String lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + bool bInheritHandle, + uint dwCreationFlags, + IntPtr lpEnvironment, + String lpCurrentDirectory, + ref STARTUPINFO lpStartupInfo, + out PROCESS_INFORMATION lpProcessInformation); + + [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] + private static extern bool DuplicateTokenEx( + IntPtr ExistingTokenHandle, + uint dwDesiredAccess, + IntPtr lpThreadAttributes, + int TokenType, + int ImpersonationLevel, + ref IntPtr DuplicateTokenHandle); + + [DllImport("userenv.dll", SetLastError = true)] + private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); + + [DllImport("userenv.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hSnapshot); + + [DllImport("kernel32.dll")] + private static extern uint WTSGetActiveConsoleSessionId(); + + [DllImport("Wtsapi32.dll")] + private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken); + + [DllImport("wtsapi32.dll", SetLastError = true)] + private static extern int WTSEnumerateSessions( + IntPtr hServer, + int Reserved, + int Version, + ref IntPtr ppSessionInfo, + ref int pCount); + + #endregion + + #region Win32 Structs + + private enum SW + { + SW_HIDE = 0, + SW_SHOWNORMAL = 1, + SW_NORMAL = 1, + SW_SHOWMINIMIZED = 2, + SW_SHOWMAXIMIZED = 3, + SW_MAXIMIZE = 3, + SW_SHOWNOACTIVATE = 4, + SW_SHOW = 5, + SW_MINIMIZE = 6, + SW_SHOWMINNOACTIVE = 7, + SW_SHOWNA = 8, + SW_RESTORE = 9, + SW_SHOWDEFAULT = 10, + SW_MAX = 10 + } + + private enum WTS_CONNECTSTATE_CLASS + { + WTSActive, + WTSConnected, + WTSConnectQuery, + WTSShadow, + WTSDisconnected, + WTSIdle, + WTSListen, + WTSReset, + WTSDown, + WTSInit + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public uint dwProcessId; + public uint dwThreadId; + } + + private enum SECURITY_IMPERSONATION_LEVEL + { + SecurityAnonymous = 0, + SecurityIdentification = 1, + SecurityImpersonation = 2, + SecurityDelegation = 3, + } + + [StructLayout(LayoutKind.Sequential)] + private struct STARTUPINFO + { + public int cb; + public String lpReserved; + public String lpDesktop; + public String lpTitle; + public uint dwX; + public uint dwY; + public uint dwXSize; + public uint dwYSize; + public uint dwXCountChars; + public uint dwYCountChars; + public uint dwFillAttribute; + public uint dwFlags; + public short wShowWindow; + public short cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + private enum TOKEN_TYPE + { + TokenPrimary = 1, + TokenImpersonation = 2 + } + + [StructLayout(LayoutKind.Sequential)] + private struct WTS_SESSION_INFO + { + public readonly UInt32 SessionID; + + [MarshalAs(UnmanagedType.LPStr)] + public readonly String pWinStationName; + + public readonly WTS_CONNECTSTATE_CLASS State; + } + + #endregion + + // Gets the user token from the currently active session + private static bool GetSessionUserToken(ref IntPtr phUserToken) + { + var bResult = false; + var hImpersonationToken = IntPtr.Zero; + var activeSessionId = INVALID_SESSION_ID; + var pSessionInfo = IntPtr.Zero; + var sessionCount = 0; + + // Get a handle to the user access token for the current active session. + if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0) + { + var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); + var current = pSessionInfo; + + for (var i = 0; i < sessionCount; i++) + { + var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO)); + current += arrayElementSize; + + if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) + { + activeSessionId = si.SessionID; + } + } + } + + // If enumerating did not work, fall back to the old method + if (activeSessionId == INVALID_SESSION_ID) + { + activeSessionId = WTSGetActiveConsoleSessionId(); + } + + if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0) + { + // Convert the impersonation token to a primary token + bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero, + (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, + ref phUserToken); + + CloseHandle(hImpersonationToken); + } + + return bResult; + } + + public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true) + { + var hUserToken = IntPtr.Zero; + var startInfo = new STARTUPINFO(); + var procInfo = new PROCESS_INFORMATION(); + var pEnv = IntPtr.Zero; + int iResultOfCreateProcessAsUser; + + startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); + + try + { + if (!GetSessionUserToken(ref hUserToken)) + { + throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed."); + } + + uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); + startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); + startInfo.lpDesktop = "winsta0\\default"; + + if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false)) + { + throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed."); + } + + if (!CreateProcessAsUser(hUserToken, + appPath, // Application Name + cmdLine, // Command Line + IntPtr.Zero, + IntPtr.Zero, + false, + dwCreationFlags, + pEnv, + workDir, // Working directory + ref startInfo, + out procInfo)) + { + iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); + throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser); + } + + iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); + } + finally + { + CloseHandle(hUserToken); + if (pEnv != IntPtr.Zero) + { + DestroyEnvironmentBlock(pEnv); + } + CloseHandle(procInfo.hThread); + CloseHandle(procInfo.hProcess); + } + + return true; + } + + } +} + +"@ +Add-Type -ReferencedAssemblies 'System', 'System.Runtime.InteropServices' -TypeDefinition $Source -Language CSharp + + + + + + + +##*============================================= +##* END FUNCTION LISTINGS +##*============================================= + +##*============================================= +##* SCRIPT BODY +##*============================================= +#region ScriptBody + + +CMTraceLog -Message "--------------------------" -Type 1 -LogFile $LogFile +CMTraceLog -Message "Starting $ScriptName" -Type 1 -LogFile $LogFile +CMTraceLog -Message "Running as: $whoami" -Type 1 -LogFile $LogFile + + +#$EXE = "cmd.exe" +#$ARG = '/c start /MIN powershell.exe -ExecutionPolicy ByPass -File C:\ProgramData\WaaS\PreInstall\PreInstallNotification.ps1' +#[murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser($EXE, $ARG) + +$EXE = "c:\windows\system32\cmd.exe" +$ARG = '/c start /MIN c:\windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass -windowstyle hidden -File C:\ProgramData\WaaS\PreInstall\PreInstallNotification.ps1' +[murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser($EXE, $ARG) + +#$EXE = "powershell.exe" +#$ARG = ' -ExecutionPolicy ByPass -File C:\ProgramData\WaaS\PreInstall\PreInstallNotification.ps1' +#[murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser($EXE, $ARG) + + +#[murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser("cmd.exe /c start /MIN powershell.exe -ExecutionPolicy ByPass -File C:\ProgramData\WaaS\PreInstall\PreInstallNotification.ps1") + + + +CMTraceLog -Message "Finished $ScriptName" -Type 1 -LogFile $LogFile +exit $exitcode +#endregion +##*============================================= +##* END SCRIPT BODY +##*============================================= +} diff --git a/Public/OSDCloudIPU/Invoke-OSDCloudIPU.ps1 b/Public/OSDCloudIPU/Invoke-OSDCloudIPU.ps1 new file mode 100644 index 000000000..bf2fb3f65 --- /dev/null +++ b/Public/OSDCloudIPU/Invoke-OSDCloudIPU.ps1 @@ -0,0 +1,581 @@ +function Invoke-OSDCloudIPU { + <# + Log Files for IPU: https://learn.microsoft.com/en-us/windows/deployment/upgrade/log-files + Setup Command Line: https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11 + #> + + [CmdletBinding(DefaultParameterSetName = 'Default')] + param ( + + [Parameter(ParameterSetName = 'Default')] + [ValidateSet( + 'Windows 11 24H2 x64', + 'Windows 11 24H2 ARM64', + 'Windows 11 23H2 x64', + 'Windows 11 23H2 ARM64', + 'Windows 11 22H2 x64', + 'Windows 11 21H2 x64', + 'Windows 10 22H2 x64', + 'Windows 10 22H2 ARM64')] + [System.String] + $OSName = 'Windows 11 24H2 x64', + + [switch] + $Silent, + + [switch] + $SkipDriverPack, + + [switch] + $NoReboot, + + [switch] + $DownloadOnly, + + [switch] + $DiagnosticPrompt, + + [switch] + $SkipFinalize, + + [switch] + $Finalize, + + [switch] + $DynamicUpdate + ) + #region Admin Elevation + $whoiam = [system.security.principal.windowsidentity]::getcurrent().name + $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") + if ($isElevated) { + Write-Host -ForegroundColor Green "[+] Running as $whoiam and IS Admin Elevated" + } + else { + Write-Warning "[-] Running as $whoiam and is NOT Admin Elevated" + Break + } + + #============================================================================ + #region Functions + #============================================================================ + Function Get-TPMVer { + $Manufacturer = (Get-WmiObject -Class:Win32_ComputerSystem).Manufacturer + if ($Manufacturer -match "HP") + { + if ($((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion) -match "1.2") + { + $versionInfo = (Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).ManufacturerVersionInfo + $verMaj = [Convert]::ToInt32($versionInfo[0..1] -join '', 16) + $verMin = [Convert]::ToInt32($versionInfo[2..3] -join '', 16) + $verBuild = [Convert]::ToInt32($versionInfo[4..6] -join '', 16) + $verRevision = 0 + [version]$ver = "$verMaj`.$verMin`.$verBuild`.$verRevision" + Write-Output "TPM Version: $ver | Spec: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion)" + } + else {Write-Output "TPM Version: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).ManufacturerVersion) | Spec: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion)"} + } + + else + { + if ($((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion) -match "1.2") + { + Write-Output "TPM Version: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).ManufacturerVersion) | Spec: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion)" + } + else {Write-Output "TPM Version: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).ManufacturerVersion) | Spec: $((Get-CimInstance -Namespace "ROOT\cimv2\Security\MicrosoftTpm" -ClassName Win32_TPM).SpecVersion)"} + } + } + + #endregion Functions + + #============================================================================ + #region Device Info + #============================================================================ + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Starting Invoke-OSDCloudIPU" + Write-Host -ForegroundColor Gray "Looking of Details about this device...." + + + $BIOSInfo = Get-WmiObject -Class 'Win32_Bios' + + # Get the current BIOS release date and format it to datetime + $CurrentBIOSDate = [System.Management.ManagementDateTimeConverter]::ToDatetime($BIOSInfo.ReleaseDate).ToUniversalTime() + + $Manufacturer = (Get-WmiObject -Class:Win32_ComputerSystem).Manufacturer + $ManufacturerBaseBoard = (Get-CimInstance -Namespace root/cimv2 -ClassName Win32_BaseBoard).Manufacturer + $ComputerModel = (Get-WmiObject -Class:Win32_ComputerSystem).Model + if ($ManufacturerBaseBoard -eq "Intel Corporation") + { + $ComputerModel = (Get-CimInstance -Namespace root/cimv2 -ClassName Win32_BaseBoard).Product + } + $HPProdCode = (Get-CimInstance -Namespace root/cimv2 -ClassName Win32_BaseBoard).Product + $Serial = (Get-WmiObject -class:win32_bios).SerialNumber + $cpuDetails = @(Get-WmiObject -Class Win32_Processor)[0] + + Write-Output "Computer Name: $env:computername" + $CurrentOSInfo = Get-Item -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' + $WindowsRelease = $CurrentOSInfo.GetValue('ReleaseId') + if ($WindowsRelease -eq "2009"){$WindowsRelease = $CurrentOSInfo.GetValue('DisplayVersion')} + $Build = $($CurrentOSInfo.GetValue('CurrentBuild')) + $BuildUBR_CurrentOS = $Build +"."+$($CurrentOSInfo.GetValue('UBR')) + if ($Build -le 19045){$WinVer = "10"} + else {$WinVer = "11"} + Write-Output "Windows $WinVer $WindowsRelease | $BuildUBR_CurrentOS" + Write-Output "Architecture ('env:PROCESSOR_ARCHITECTURE'): $env:PROCESSOR_ARCHITECTURE " + Write-Output "Architecture (Get-NativeMatchineImage): $((Get-NativeMatchineImage).NativeMachine)" + Write-Output "Computer Model: $ComputerModel" + Write-Output "Serial: $Serial" + if ($Manufacturer -like "HP" -or $Manufacturer -like "Hewlett"){Write-Output "Computer Product Code: $HPProdCode"} + Write-Output $cpuDetails.Name + Write-Output "Current BIOS Level: $($BIOSInfo.SMBIOSBIOSVersion) From Date: $CurrentBIOSDate" + Get-TPMVer + $TimeUTC = [System.DateTime]::UtcNow + $TimeCLT = get-date + Write-Output "Current Client Time: $TimeCLT" + Write-Output "Current Client UTC: $TimeUTC" + Write-Output "Time Zone: $(Get-TimeZone)" + $Locale = Get-WinSystemLocale + if ($Locale -ne "en-US"){Write-Output "WinSystemLocale: $locale"} + $FreeSpace = (Get-CimInstance win32_LogicalDisk -Filter "DeviceID='C:'").FreeSpace/1GB -as [int] + $DiskSize = (Get-CimInstance win32_LogicalDisk -Filter "DeviceID='C:'").Size/1GB -as [int] + Write-Output "C:\ Drive Size: $DiskSize, Free Space: $FreeSpace" + + if ($Build -le 19045){ + $Win11 = Get-Win11Readiness + if ($Win11.Return -eq "CAPABLE"){ + Write-Host -ForegroundColor Green "Device is Windows 11 CAPABLE" + } + else { + Write-Host -ForegroundColor Yellow "Device is !NOT! Windows 11 CAPABLE" + if ($Build -eq 19045){ + write-host -ForegroundColor Yellow "This Device is already at the latest supported Version of Windows for this Hardware" + } + elseif ($Build -lt 19045){ + write-host -ForegroundColor Green "But.. You can upgrade it to Windows 10 22H2" + } + } + } + + #$OSVersion = "Windows $($OSName.split(" ")[1])" + #$OSReleaseID = $OSName.split(" ")[2] + #$Product = (Get-MyComputerProduct) + + $DriverPack = Get-OSDCloudDriverPack # -Product $Product -OSVersion $OSVersion -OSReleaseID $OSReleaseID + if ($DriverPack){ + Write-host -ForegroundColor Gray "Recommended Driverpack for upgrade: $($DriverPack.Name)" + if ($SkipDriverPack){ + write-host -ForegroundColor Yellow "Skipping Download and Integration [-SkipDriverPack]" + } + } + + #endregion Device Info + + #============================================================================ + #region Current Activation + #============================================================================ + + if (!($OSEdition)){ + $OSEdition = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name "EditionID" + } + if (!($OSLanguage)){ + $OSLanguage = (Get-WinSystemLocale).Name + } + if (!($OSActivation)){ + $OSActivation = (Get-CimInstance SoftwareLicensingProduct -Filter "Name like 'Windows%'" | Where-Object { $_.PartialProductKey }).ProductKeyChannel + } + if ($OSActivation -match "OEM"){ + $OSActivation = "Retail" + } + $OSArch = $env:PROCESSOR_ARCHITECTURE + if ($OSArch -eq "AMD64"){$OSArch = 'x64'} + #endregion Current Activation + + if ($OSArch -eq "ARM64"){ + #================================================= + # OSEditionId and OSActivation ARM64 + #================================================= + if (($OSEdition -eq 'Home') -or ($OSEdition -eq 'Core')) { + $OSEditionId = 'Core' + $OSActivation = 'Retail' + $OSImageIndex = 4 + } + if ($OSEdition -eq 'Home Single Language') { + $OSEditionId = 'CoreSingleLanguage' + $OSActivation = 'Retail' + $OSImageIndex = 5 + } + if (($OSEdition -eq 'Pro') -or ($OSEdition -eq 'Professional')) { + $OSEditionId = 'Professional' + if ($OSActivation -eq 'Retail') {$OSImageIndex = 6} + if ($OSActivation -eq 'Volume') {$OSImageIndex = 8} + } + } + else { + #================================================= + # OSEditionId and OSActivation x64 (AMD64) + #================================================= + if (($OSEdition -eq 'Home') -or ($OSEdition -eq 'Core')) { + $OSEditionId = 'Core' + $OSActivation = 'Retail' + $OSImageIndex = 4 + } + if (($OSEdition -eq 'Home N') -or ($OSEdition -eq 'CoreN')) { + $OSEditionId = 'CoreN' + $OSActivation = 'Retail' + $OSImageIndex = 5 + } + if ($OSEdition -eq 'Home Single Language') { + $OSEditionId = 'CoreSingleLanguage' + $OSActivation = 'Retail' + $OSImageIndex = 6 + } + if ($OSEdition -eq 'Enterprise') { + $OSEditionId = 'Enterprise' + $OSActivation = 'Volume' + $OSImageIndex = 6 + } + if (($OSEdition -eq 'Enterprise N') -or ($OSEdition -eq 'EnterpriseN')) { + $OSEditionId = 'EnterpriseN' + $OSActivation = 'Volume' + $OSImageIndex = 7 + } + if ($OSEdition -eq 'Education') { + $OSEditionId = 'Education' + if ($OSActivation -eq 'Retail') {$OSImageIndex = 7} + if ($OSActivation -eq 'Volume') {$OSImageIndex = 4} + } + if (($OSEdition -eq 'Education N') -or ($OSEdition -eq 'EducationN')) { + $OSEditionId = 'EducationN' + if ($OSActivation -eq 'Retail') {$OSImageIndex = 8} + if ($OSActivation -eq 'Volume') {$OSImageIndex = 5} + } + if (($OSEdition -eq 'Pro') -or ($OSEdition -eq 'Professional')) { + $OSEditionId = 'Professional' + if ($OSActivation -eq 'Retail') {$OSImageIndex = 9} + if ($OSActivation -eq 'Volume') {$OSImageIndex = 8} + } + if (($OSEdition -eq 'Pro N') -or ($OSEdition -eq 'ProfessionalN')) { + $OSEditionId = 'ProfessionalN' + if ($OSActivation -eq 'Retail') {$OSImageIndex = 10} + if ($OSActivation -eq 'Volume') {$OSImageIndex = 9} + } + } + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor DarkCyan "These are set automatically based on your current OS" + Write-Host -ForegroundColor Cyan "OSEditionId: " -NoNewline + Write-Host -ForegroundColor Green $OSEditionId + Write-Host -ForegroundColor Cyan "OSImageIndex: " -NoNewline + Write-Host -ForegroundColor Green $OSImageIndex + Write-Host -ForegroundColor Cyan "OSLanguage: " -NoNewline + Write-Host -ForegroundColor Green $OSLanguage + Write-Host -ForegroundColor Cyan "OSActivation: " -NoNewline + Write-Host -ForegroundColor Green $OSActivation + Write-Host -ForegroundColor Cyan "OSArch: " -NoNewline + Write-Host -ForegroundColor Green $OSArch + + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Starting Feature Update lookup and Download" + + #============================================================================ + #region Detect & Download ESD File + #============================================================================ + + $ScratchLocation = 'c:\OSDCloud\IPU' + $OSMediaLocation = 'c:\OSDCloud\OS' + $MediaLocation = "$ScratchLocation\Media" + if (!(Test-Path -Path $OSMediaLocation)){New-Item -Path $OSMediaLocation -ItemType Directory -Force | Out-Null} + if (!(Test-Path -Path $ScratchLocation)){New-Item -Path $ScratchLocation -ItemType Directory -Force | Out-Null} + if (Test-Path -Path $MediaLocation){Remove-Item -Path $MediaLocation -Force -Recurse} + New-Item -Path $MediaLocation -ItemType Directory -Force | Out-Null + + $ESD = Get-FeatureUpdate -OSName $OSName -OSActivation $OSActivation -OSLanguage $OSLanguage -OSArchitecture $OSArch + if (!($ESD)){ + Write-Host -ForegroundColor Red "Unable to Determine proper ESD Upgrade File" + throw "Unable to Determine proper ESD Upgrade File" + } + Write-Host -ForegroundColor Cyan "Name: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Name + Write-Host -ForegroundColor Cyan "Architecture: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Architecture + Write-Host -ForegroundColor Cyan "Activation: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Activation + Write-Host -ForegroundColor Cyan "Build: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Build + Write-Host -ForegroundColor Cyan "FileName: " -NoNewline + Write-Host -ForegroundColor Green $ESD.FileName + Write-Host -ForegroundColor Cyan "Url: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Url + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Getting Content for Upgrade Media" + + #Build Media Paths + $SubFolderName = "$($ESD.Version) $($ESD.ReleaseId)" + $ImageFolderPath = "$OSMediaLocation\$SubFolderName" + if (!(Test-Path -Path $ImageFolderPath)){New-Item -Path $ImageFolderPath -ItemType Directory -Force | Out-Null} + $ImagePath = "$ImageFolderPath\$($ESD.FileName)" + $ImageDownloadRequired = $true + + #Check Flash Drive for Media + $OSDCloudUSB = Get-Volume.usb | Where-Object {($_.FileSystemLabel -match 'OSDCloud') -or ($_.FileSystemLabel -match 'BHIMAGE')} | Select-Object -First 1 + if ($OSDCloudUSB){ + $USBImagePath = "$($OSDCloudUSB.DriveLetter):\OSDCloud\OS\$SubFolderName\$($ESD.FileName)" + if ((Test-Path -path $USBImagePath) -and (!(Test-Path -path $ImagePath))){ + Write-Host -ForegroundColor Green "Found media on OSDCloudUSB - Copying Local" + Copy-Item -Path $USBImagePath -Destination $ImagePath + } + } + + #Test for Media + if (Test-path -path $ImagePath){ + Write-Host -ForegroundColor Gray "Found previously downloaded media, getting SHA1 Hash" + $SHA1Hash = Get-FileHash $ImagePath -Algorithm SHA1 + if ($SHA1Hash.Hash -eq $esd.SHA1){ + Write-Host -ForegroundColor Gray "SHA1 Match on $ImagePath, skipping Download" + $ImageDownloadRequired = $false + } + else { + Write-Host -ForegroundColor Gray "SHA1 Match Failed on $ImagePath, removing content" + } + + } + if ($ImageDownloadRequired -eq $true){ + #Save-WebFile -SourceUrl $ESD.Url -DestinationDirectory $ScratchLocation -DestinationName $ESD.FileName + Write-Host -ForegroundColor Gray "Starting Download to $ImagePath, this takes awhile" + + <# This was taking way too long for some files + #Get ESD Size + $req = [System.Net.HttpWebRequest]::Create("$($ESD.Url)") + $res = $req.GetResponse() + (Invoke-WebRequest $ESD.Url -Method Head).Headers.'Content-Length' + $ESDSizeMB = $([Math]::Round($res.ContentLength /1000000)) + Write-Host "Total Size: $ESDSizeMB MB" + #> + + #Clear Out any Previous Attempts + $ExistingBitsJob = Get-BitsTransfer -Name "$($ESD.FileName)" -AllUsers -ErrorAction SilentlyContinue + If ($ExistingBitsJob) { + Remove-BitsTransfer -BitsJob $ExistingBitsJob + } + + if ((Get-Service -name BITS).Status -ne "Running"){ + Write-Host -ForegroundColor Yellow "BITS Service is not Running, which is required to download ESD File, attempting to Start" + $StartBITS = Start-Service -Name BITS -PassThru + Start-Sleep -Seconds 2 + if ($StartBITS.Status -ne "Running"){ + + } + } + #Start Download using BITS + Write-Host -ForegroundColor DarkGray "Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName $($ESD.FileName) -Description 'Windows Media Download' -RetryInterval 60" + $BitsJob = Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName "$($ESD.FileName)" -Description "Windows Media Download" -RetryInterval 60 + If ($BitsJob.JobState -eq "Error"){ + write-Host "BITS transfer failed: $($BitsJob.ErrorDescription)" + } + + } + + #endregion Detect & Download ESD File + + #============================================================================ + #region Extract of ESD file to create Setup Content + #============================================================================ + + + #Grab ESD File and create bootable ISO + if ((!(Test-Path -Path $ImagePath)) -or (!(Test-Path -Path $MediaLocation))){ + if (!(Test-Path -Path $ImagePath)){ + Write-Host -ForegroundColor Red "Missing $ImagePath, double check download process" + throw "Failed to find $ImagePath, double check download process" + } + if (!(Test-Path -Path $MediaLocation)){ + Write-Host -ForegroundColor Red "Missing $MediaLocation, double check folder exist" + throw "Faield to find $MediaLocation, double check folder exist" + } + } + if ((Test-Path -Path $ImagePath) -and (Test-Path -Path $MediaLocation)){ + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Starting Extract of ESD file to create Setup Content" + $ApplyPath = $MediaLocation + Write-Host -ForegroundColor Gray "Expanding $ImagePath Index 1 to $ApplyPath" + $Expand = Expand-WindowsImage -ImagePath $ImagePath -Index 1 -ApplyPath $ApplyPath + ##Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 2 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType max -CheckIntegrity + ##Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 3 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType max -CheckIntegrity -Setbootable + Write-Host -ForegroundColor Gray "Expanding $ImagePath Index $OSImageIndex to $ApplyPath\Sources\install.wim" + $Expand = Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex $OSImageIndex -DestinationImagePath "$ApplyPath\Sources\install.wim" -CheckIntegrity + ##Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 5 -DestinationImagePath "$ApplyPath\Sources\install.wim" -CompressionType max -CheckIntegrity + $null = $Expand + } + + #endregion Extract of ESD file to create Setup Content + + if (!(Test-Path -Path "$MediaLocation\Setup.exe")){ + Write-Host -ForegroundColor Red "Setup.exe not found, something went wrong" + throw + } + if (!(Test-Path -Path "$MediaLocation\sources\install.wim")){ + Write-Host -ForegroundColor Red "install.wim not found, something went wrong" + throw + } + + + if (($DriverPack) -and (!($SkipDriverPack))){ + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Getting Driver Pack for IPU Integration" + $DriverPackDownloadRequired = $true + if (!(Test-Path -Path "C:\Drivers")){New-Item -Path "C:\Drivers" -ItemType Directory -Force | Out-Null} + $DriverPackPath = "C:\Drivers\$($DriverPack.FileName)" + if (Test-path -path $DriverPackPath){ + Write-Host -ForegroundColor Gray "Found previously downloaded DriverPack File, getting MD5 Hash" + $MD5Hash = Get-FileHash $DriverPackPath -Algorithm MD5 + if ($MD5Hash.Hash -eq $DriverPack.HashMD5){ + Write-Host -ForegroundColor Gray "MD5 Match on $DriverPackPath, skipping Download" + $DriverPackDownloadRequired = $false + } + else { + Write-Host -ForegroundColor Gray "MD5 Match Failed on $DriverPackPath, removing content" + } + } + + IF ($DriverPackDownloadRequired -eq $true){ + Write-Host -ForegroundColor Gray "Starting Download to $DriverPackPath, this takes awhile" + <# + #Get DrivePack Size + $req = [System.Net.HttpWebRequest]::Create("$($DriverPack.Url)") + $res = $req.GetResponse() + (Invoke-WebRequest $ESD.Url -Method Head).Headers.'Content-Length' + $SizeMB = $([Math]::Round($res.ContentLength /1000000)) + Write-Host "Total Size: $SizeMB MB" + #> + + #Clear Out any Previous Attempts + $ExistingBitsJob = Get-BitsTransfer -Name "$($DriverPack.FileName)" -AllUsers -ErrorAction SilentlyContinue + If ($ExistingBitsJob) { + Remove-BitsTransfer -BitsJob $ExistingBitsJob + } + + #Start Download using BITS + $BitsJob = Start-BitsTransfer -Source $DriverPack.Url -Destination $DriverPackPath -DisplayName "$($DriverPack.FileName)" -Description "Driver Pack Download" -RetryInterval 60 + If ($BitsJob.JobState -eq "Error"){ + write-Host "BITS tranfer failed: $($BitsJob.ErrorDescription)" + } + } + #Expand Driver Pack + if (Test-path -path $DriverPackPath){ + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Expanding DriverPack for Upgrade Media" + Expand-StagedDriverPack + $DriverPackFile = Get-ChildItem -Path $DriverPackPath -Filter $DriverPack.FileName + + if ($Manufacturer -like "LENOVO"){ + $DriverPackExpandPath = "$($DriverPackFile.Directory)\SCCM\$($DriverPackFile.BaseName)" + } + else{ + $DriverPackExpandPath = Join-Path $DriverPackFile.Directory $DriverPackFile.BaseName + } + if (Test-Path -Path $DriverPackExpandPath){ + Write-Host -ForegroundColor Green "Confirmed Driver Pack Expanded to $DriverPackExpandPath" + } + else { + Write-Host -ForegroundColor Red "Driver Pack Failed to Expand to $DriverPackExpandPath" + if ($Silent){ + Write-Host -ForegroundColor Red "Continuing without Driver Pack integration" + } + else { + $DriverContinueInput = Read-Host "Do you want to continue without Driver Pack? (Y/N)" + if ($DriverContinueInput -eq 'Y' -or $DriverContinueInput -eq 'y') { + Write-Host -ForegroundColor Red "Continuing without Driver Pack integration" + } elseif ($DriverContinueInput -eq 'N' -or $DriverContinueInput -eq 'n') { + throw "Driver Pack Failed to Expand to $DriverPackExpandPath" + } else { + Write-Output "Invalid input. Please enter Y or N." + } + } + } + } + } + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Triggering Windows Upgrade Setup" + + if ($DownloadOnly){ + Write-Host -ForegroundColor Yellow "Download Complete, exiting script before install based on 'DownloadOnly' switch" + } + else { + #============================================================================ + #region Creating Arguments based on Parameters + #============================================================================ + #Driver Integration - Adds .inf-style drivers to the new Windows 10 installation. + if ($DriverPack){ + if ($DriverPackPath){ + if (Test-path -path $DriverPackPath){ + $driverarg = "/InstallDrivers $DriverPackExpandPath" + } + } + } + else { + $DriverArg = "" + } + + #Run Silently - This will suppress any Windows Setup user experience including the rollback user experience. + if ($Silent){ + $SilentArg = "/quiet" + } + else{ + $SilentArg = "" + } + + #Dynamic Updates - Specifies whether Windows Setup will perform Dynamic Update operations (search, download, and install updates). + if ($DynamicUpdate){ + $DynamicUpdateArg = "/DynamicUpdate Enable" + } + else{ + $DynamicUpdateArg = "/DynamicUpdate Disable" + } + + #Diagnostic Prompt - Specifies that the Command Prompt is available during Windows Setup. + if ($DiagnosticPrompt){ + $DiagnosticPromptArg = "/diagnosticprompt enable" + } + else{ + $DiagnosticPromptArg = "" + } + #Skip Finalize - Instructions setup to start update operations on the down-level OS without initiating a reboot to start the offline phase. + #https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11#skipfinalize + if ($SkipFinalize){ + $SkipFinalizeArg = "/SkipFinalize" + } + else{ + $SkipFinalizeArg = "" + } + #Finalize - Instructions Windows Setup to finish previously started update operations on the down-level OS, followed by an immediate reboot to start the offline phase. + #https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11#finalize + if ($Finalize){ + $FinalizeArg = "/Finalize" + } + else{ + $FinalizeArg = "" + } + + #No Reboot - Instructs Windows Setup not to restart the computer after the down-level phase of Windows Setup completes. + #https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11#noreboot + if ($NoReboot){ + $NoRebootArg = "/noreboot" + } + else{ + $NoRebootArg = "" + } + + $ParamStartProcess = @{ + FilePath = "$MediaLocation\Setup.exe" + ArgumentList = "/Auto Upgrade $DynamicUpdateArg /EULA accept $DriverArg /Priority High $SilentArg $DiagnosticPromptArg $NoRebootArg $SkipFinalizeArg $FinalizeArg" + } + + Write-Host -ForegroundColor Cyan "Setup Path: " -NoNewline + Write-Host -ForegroundColor Green $ParamStartProcess.FilePath + Write-Host -ForegroundColor Cyan "Arguments: " -NoNewline + Write-Host -ForegroundColor Green $ParamStartProcess.ArgumentList + + + #endregion Creating Arguments based on Parameters + + Start-Process @ParamStartProcess + } +} \ No newline at end of file diff --git a/Public/OSDCloudIPU/New-OSDCloudOSWimFile.ps1 b/Public/OSDCloudIPU/New-OSDCloudOSWimFile.ps1 new file mode 100644 index 000000000..5e25b8431 --- /dev/null +++ b/Public/OSDCloudIPU/New-OSDCloudOSWimFile.ps1 @@ -0,0 +1,337 @@ +function New-OSDCloudOSWimFile { + <# + Log Files for IPU: https://learn.microsoft.com/en-us/windows/deployment/upgrade/log-files + Setup Command Line: https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11 + #> + + [CmdletBinding(DefaultParameterSetName = 'Default')] + param ( + + [Parameter(ParameterSetName = 'Default')] + [ValidateSet( + 'Windows 11 25H2 x64', + 'Windows 11 25H2 ARM64', + 'Windows 11 24H2 x64', + 'Windows 11 24H2 ARM64', + 'Windows 11 23H2 x64', + 'Windows 11 23H2 ARM64', + 'Windows 11 22H2 x64', + 'Windows 11 21H2 x64', + 'Windows 10 22H2 x64', + 'Windows 10 22H2 ARM64')] + [System.String] + $OSName = 'Windows 11 25H2 x64', + + #Operating System Edition of the Windows installation + #Alias = Edition + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Legacy')] + [ValidateSet('Home','Home N','Home Single Language','Education','Education N','Enterprise','Enterprise N','Pro','Pro N')] + [Alias('Edition')] + [System.String] + $OSEdition = 'Pro', + + #Operating System Language of the Windows installation + #Alias = Culture, OSCulture + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Legacy')] + [ValidateSet ( + 'ar-sa','bg-bg','cs-cz','da-dk','de-de','el-gr', + 'en-gb','en-us','es-es','es-mx','et-ee','fi-fi', + 'fr-ca','fr-fr','he-il','hr-hr','hu-hu','it-it', + 'ja-jp','ko-kr','lt-lt','lv-lv','nb-no','nl-nl', + 'pl-pl','pt-br','pt-pt','ro-ro','ru-ru','sk-sk', + 'sl-si','sr-latn-rs','sv-se','th-th','tr-tr', + 'uk-ua','zh-cn','zh-tw' + )] + [Alias('Culture','OSCulture')] + [System.String] + $OSLanguage = 'en-us', + + #License of the Windows Operating System + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Legacy')] + [ValidateSet('Retail','Volume')] + [Alias('License','OSLicense','Activation')] + [System.String] + $OSActivation = 'Retail', + + #Create ISO File - Requries Windows ADK (oscdimg.exe) + [Parameter(ParameterSetName = 'Default')] + [Switch] + $CreateISO + + ) + #region Admin Elevation + $whoiam = [system.security.principal.windowsidentity]::getcurrent().name + $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") + if ($isElevated) { + Write-Host -ForegroundColor Green "[+] Running as $whoiam and IS Admin Elevated" + } + else { + Write-Warning "[-] Running as $whoiam and is NOT Admin Elevated" + Break + } + #================================================ + # Get Index & OS Info + #================================================ + <# + if ($OSName -match "ARM64"){ + $OSArch = 'ARM64' + $IndexInfo = Get-OSDCloudOperatingSystemsIndexes -OSArch ARM64 | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} + } + else { + $OSArch = 'x64' + $IndexInfo = Get-OSDCloudOperatingSystemsIndexes | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} + } + #> + + if ($OSName -match "ARM64"){ + $OSArch = 'ARM64' + $OSDCloudOperatingSystem = (Get-OSDCloudOperatingSystems -OSArch ARM64) | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} + $IndexMap = Get-OSDCloudOperatingSystemsIndexMap -OSArch ARM64 | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} + } + else { + $OSArch = 'x64' + $OSDCloudOperatingSystem = Get-OSDCloudOperatingSystems -OSArch x64 | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} + $IndexMap = Get-OSDCloudOperatingSystemsIndexMap -OSArch x64 | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} + } + + $OSEditionID = "$($OSDCloudOperatingSystem.Version) $OSEdition" + $OSImageIndex = $IndexMap.Indexes.$OSEditionID + + if ($OSImageIndex -eq $null){ + Write-Host -ForegroundColor Red "Unable to determine OSImageIndex for Index $OSEdition" + Write-Host -ForegroundColor Yellow "Available Indexes are $($OSDCloudOperatingSystem.IndexNames.replace($(($OSDCloudOperatingSystem).Version),'') -join ', ')" + throw "Unable to determine OSImageIndex for $OSName $OSEdition $OSActivation $OSLanguage" + } + + #$OSBuild = $OSDCloudOperatingSystem.Build + #$OSReleaseID = $OSDCloudOperatingSystem.ReleaseID + #$OSVersion = $OSDCloudOperatingSystem.Version + + #$ImageFileName = $OSDCloudOperatingSystem.FileName + #$ImageFileUrl = $OSDCloudOperatingSystem.Url + + $ImageFileItem = Find-OSDCloudFile -Name $OSDCloudOperatingSystem.FileName -Path '\OSDCloud\OS\' | Sort-Object FullName | Where-Object {$_.Length -gt 3GB} + $ImageFileItem = $ImageFileItem | Where-Object {$_.FullName -notlike "C*"} | Where-Object {$_.FullName -notlike "X*"} | Select-Object -First 1 + + + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor DarkCyan "These are set based on your input parameters" + Write-Host -ForegroundColor Cyan "OSEditionId: " -NoNewline + Write-Host -ForegroundColor Green $OSEdition + Write-Host -ForegroundColor Cyan "OSImageIndex: " -NoNewline + Write-Host -ForegroundColor Green $OSImageIndex + Write-Host -ForegroundColor Cyan "OSLanguage: " -NoNewline + Write-Host -ForegroundColor Green $OSLanguage + Write-Host -ForegroundColor Cyan "OSActivation: " -NoNewline + Write-Host -ForegroundColor Green $OSActivation + Write-Host -ForegroundColor Cyan "OSArch: " -NoNewline + Write-Host -ForegroundColor Green $OSArch + + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Starting Feature Update lookup and Download" + + #============================================================================ + #region Detect & Download ESD File + #============================================================================ + + $ScratchLocation = 'c:\OSDCloud\IPU' + $OSMediaLocation = 'c:\OSDCloud\OS' + $MediaLocation = "$ScratchLocation\Media\$OSName" + if (!(Test-Path -Path $OSMediaLocation)){New-Item -Path $OSMediaLocation -ItemType Directory -Force | Out-Null} + if (!(Test-Path -Path $ScratchLocation)){New-Item -Path $ScratchLocation -ItemType Directory -Force | Out-Null} + if (Test-Path -Path $MediaLocation){Remove-Item -Path $MediaLocation -Force -Recurse} + New-Item -Path $MediaLocation -ItemType Directory -Force | Out-Null + + $ESD = Get-FeatureUpdate -OSName $OSName -OSActivation $OSActivation -OSLanguage $OSLanguage -OSArchitecture $OSArch + if (!($ESD)){ + Write-Host -ForegroundColor Red "Unable to Determine proper ESD Upgrade File" + throw "Unable to Determine proper ESD Upgrade File" + } + Write-Host -ForegroundColor Cyan "Name: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Name + Write-Host -ForegroundColor Cyan "Architecture: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Architecture + Write-Host -ForegroundColor Cyan "Activation: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Activation + Write-Host -ForegroundColor Cyan "Build: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Build + Write-Host -ForegroundColor Cyan "FileName: " -NoNewline + Write-Host -ForegroundColor Green $ESD.FileName + Write-Host -ForegroundColor Cyan "Url: " -NoNewline + Write-Host -ForegroundColor Green $ESD.Url + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Getting Content for Upgrade Media" + + <##> + #Build Media Paths + $SubFolderName = "$($ESD.Version) $($ESD.ReleaseId)" + $ImageFolderPath = "$OSMediaLocation\$SubFolderName" + if (!(Test-Path -Path $ImageFolderPath)){New-Item -Path $ImageFolderPath -ItemType Directory -Force | Out-Null} + $ImagePath = "$ImageFolderPath\$($ESD.FileName)" + $ImageDownloadRequired = $true + + #Check Flash Drive for Media + $OSDCloudUSB = Get-Volume.usb | Where-Object {($_.FileSystemLabel -match 'OSDCloud') -or ($_.FileSystemLabel -match 'BHIMAGE')} | Select-Object -First 1 + if ($OSDCloudUSB){ + $USBImagePath = "$($OSDCloudUSB.DriveLetter):\OSDCloud\OS\$SubFolderName\$($ESD.FileName)" + if ((Test-Path -path $USBImagePath) -and (!(Test-Path -path $ImagePath))){ + Write-Host -ForegroundColor Green "Found media on OSDCloudUSB - Copying Local" + Copy-Item -Path $USBImagePath -Destination $ImagePath + } + } + + #Test for Media + if (Test-path -path $ImagePath){ + Write-Host -ForegroundColor Gray "Found previously downloaded media: $ImagePath" + write-host -ForegroundColor Gray " ... Getting SHA1 Hash for validation" + $SHA1Hash = Get-FileHash $ImagePath -Algorithm SHA1 + if ($SHA1Hash.Hash -eq $esd.SHA1){ + Write-Host -ForegroundColor Gray "SHA1 Match $($SHA1Hash.Hash), skipping Download" + $ImageDownloadRequired = $false + } + else { + Write-Host -ForegroundColor Gray "SHA1 Match Failed on $ImagePath, removing content" + } + + } + if ($ImageDownloadRequired -eq $true){ + #Save-WebFile -SourceUrl $ESD.Url -DestinationDirectory $ScratchLocation -DestinationName $ESD.FileName + Write-Host -ForegroundColor Gray "Starting Download to $ImagePath, this takes awhile" + + <# This was taking way too long for some files + #Get ESD Size + $req = [System.Net.HttpWebRequest]::Create("$($ESD.Url)") + $res = $req.GetResponse() + (Invoke-WebRequest $ESD.Url -Method Head).Headers.'Content-Length' + $ESDSizeMB = $([Math]::Round($res.ContentLength /1000000)) + Write-Host "Total Size: $ESDSizeMB MB" + #> + + #Clear Out any Previous Attempts + $ExistingBitsJob = Get-BitsTransfer -Name "$($ESD.FileName)" -AllUsers -ErrorAction SilentlyContinue + If ($ExistingBitsJob) { + Remove-BitsTransfer -BitsJob $ExistingBitsJob + } + + if ((Get-Service -name BITS).Status -ne "Running"){ + Write-Host -ForegroundColor Yellow "BITS Service is not Running, which is required to download ESD File, attempting to Start" + $StartBITS = Start-Service -Name BITS -PassThru + Start-Sleep -Seconds 2 + if ($StartBITS.Status -ne "Running"){ + + } + } + #Start Download using BITS + Write-Host -ForegroundColor DarkGray "Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName $($ESD.FileName) -Description 'Windows Media Download' -RetryInterval 60" + $BitsJob = Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName "$($ESD.FileName)" -Description "Windows Media Download" -RetryInterval 60 + If ($BitsJob.JobState -eq "Error"){ + write-Host "BITS tranfer failed: $($BitsJob.ErrorDescription)" + } + + } + + #endregion Detect & Download ESD File + + #============================================================================ + #region Extract of ESD file to create Setup Content + #============================================================================ + + #https://www.deploymentresearch.com/how-to-really-create-a-windows-10-build-10041-iso-no-3rd-party-tools-needed/ + #Using info from Johan's process to export properly + #DISM commands are left for reference only. + + #Grab ESD File and create bootable ISO + if ((!(Test-Path -Path $ImagePath)) -or (!(Test-Path -Path $MediaLocation))){ + if (!(Test-Path -Path $ImagePath)){ + Write-Host -ForegroundColor Red "Missing $ImagePath, double check download process" + throw "Failed to find $ImagePath, double check download process" + } + if (!(Test-Path -Path $MediaLocation)){ + Write-Host -ForegroundColor Red "Missing $MediaLocation, double check folder exist" + throw "Failed to find $MediaLocation, double check folder exist" + } + } + if ((Test-Path -Path $ImagePath) -and (Test-Path -Path $MediaLocation)){ + Write-Host -ForegroundColor DarkGray "=========================================================================" + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Starting Extract of ESD file to create Setup Content" + $ApplyPath = $MediaLocation + Write-Host -ForegroundColor Gray "Expanding $ImagePath Index 1 to $ApplyPath" + $Expand = Expand-WindowsImage -ImagePath $ImagePath -Index 1 -ApplyPath $ApplyPath + + # Create empty boot.wim file with compression type set to maximum + $EmptyFolder = "$($env:TEMP)\EmptyFolder" + New-Item -ItemType Directory -Path $EmptyFolder -Force | Out-Null + #dism.exe /Capture-Image /ImageFile:$ISOMediaFolder\sources\boot.wim /CaptureDir:$EmptyFolder /Name:EmptyIndex /Compress:max + New-WindowsImage -ImagePath $ApplyPath\Sources\boot.wim -CapturePath $EmptyFolder -Name EmptyIndex -Description "Empty Index" -CompressionType Fast + + # Export base Windows PE to empty boot.wim file (creating a second index) + #dism.exe /Export-image /SourceImageFile:$ESDFile /SourceIndex:2 /DestinationImageFile:$ISOMediaFolder\sources\boot.wim /Compress:Recovery /Bootable + Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 2 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType Fast -CheckIntegrity -Setbootable + + # Delete the first empty index in boot.wim + #dism.exe /Delete-Image /ImageFile:$ISOMediaFolder\sources\boot.wim /Index:1 + Remove-WindowsImage -ImagePath $ApplyPath\Sources\boot.wim -Index 1 + + # Export Windows PE with Setup to boot.wim file + #dism.exe /Export-image /SourceImageFile:$ESDFile /SourceIndex:3 /DestinationImageFile:$ISOMediaFolder\sources\boot.wim /Compress:Recovery /Bootable + Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 3 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType Fast -CheckIntegrity -Setbootable + + # Create empty install.wim file with MDT/ConfigMgr friendly compression type (maximum) + #dism.exe /Capture-Image /ImageFile:$ISOMediaFolder\sources\install.wim /CaptureDir:C:\EmptyFolder /Name:EmptyIndex /Compress:max + New-WindowsImage -ImagePath $ApplyPath\Sources\install.wim -CapturePath $EmptyFolder -Name EmptyIndex -Description "Empty Index" -CompressionType Fast + + #Export the OS Image to the install.wim file + Write-Host -ForegroundColor Gray "Expanding $ImagePath Index $OSImageIndex to $ApplyPath\Sources\install.wim" + #dism.exe /Export-image /SourceImageFile:$ESDFile /SourceIndex:4 /DestinationImageFile:$ISOMediaFolder\sources\install.wim /Compress:Recovery + ##Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 5 -DestinationImagePath "$ApplyPath\Sources\install.wim" -CompressionType max -CheckIntegrity + $Expand = Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex $OSImageIndex -DestinationImagePath "$ApplyPath\Sources\install.wim" -CheckIntegrity -CompressionType Fast + $null = $Expand + + # Delete the first empty index in install.wim + #dism.exe /Delete-Image /ImageFile:$ISOMediaFolder\sources\install.wim /Index:1 + Remove-WindowsImage -ImagePath $ApplyPath\Sources\install.wim -Index 1 + } + + #endregion Extract of ESD file to create Setup Content + + if (!(Test-Path -Path "$MediaLocation\Setup.exe")){ + Write-Host -ForegroundColor Red "Setup.exe not found, something went wrong" + throw + } + if (!(Test-Path -Path "$MediaLocation\sources\install.wim")){ + Write-Host -ForegroundColor Red "install.wim not found, something went wrong" + throw + } + Write-Host -ForegroundColor DarkGray "=========================================================================" + if ($CreateISO){ + $PathToOscdimg = (Get-WindowsAdkPaths).oscdimgexe + if (!(Test-Path -Path $PathToOscdimg)){ + Write-Host -ForegroundColor Red "oscdimg.exe not found, unable to create ISO File" + throw "oscdimg.exe not found, unable to create ISO File" + } + else { + Write-Host -ForegroundColor Cyan "[$(Get-Date -format G)] Creating ISO File" + $BootData='2#p0,e,b"{0}"#pEF,e,b"{1}"' -f "$ApplyPath\boot\etfsboot.com","$ApplyPath\efi\Microsoft\boot\efisys.bin" + + $ISOFile = "`"$ScratchLocation\$($ESD.Version) $($ESD.ReleaseId) $($ESD.Architecture).iso`"" + $ISOFilePath = "$ScratchLocation\$($ESD.Version) $($ESD.ReleaseId) $($ESD.Architecture).iso" + if (Test-Path -Path $ISOFilePath){Remove-Item -Path $ISOFilePath -Force} + $ISOMedia = "`"$ApplyPath`"" + $Proc = Start-Process -FilePath $PathToOscdimg -ArgumentList @("-bootdata:$BootData",'-u2','-udfver102',"$ISOMedia","$ISOFile") -PassThru -Wait -NoNewWindow + if($Proc.ExitCode -ne 0) + { + Throw "Failed to generate ISO with exitcode: $($Proc.ExitCode)" + } + if (Test-Path -Path $ISOFilePath){ + Write-Host -ForegroundColor Green "ISO File Created: $ISOFile" + } + else { + Write-Host -ForegroundColor Red "Failed to Create ISO File" + } + } + Write-Host -ForegroundColor DarkGray "=========================================================================" + } +} \ No newline at end of file diff --git a/Public/OSDCloudIPU/Set-Win11ReqBypassRegValues.ps1 b/Public/OSDCloudIPU/Set-Win11ReqBypassRegValues.ps1 new file mode 100644 index 000000000..920958974 --- /dev/null +++ b/Public/OSDCloudIPU/Set-Win11ReqBypassRegValues.ps1 @@ -0,0 +1,56 @@ +Function Set-Win11ReqBypassRegValues { + if ($env:SystemDrive -eq 'X:') { + $WindowsPhase = 'WinPE' + } + else { + $ImageState = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State' -ErrorAction Ignore).ImageState + if ($env:UserName -eq 'defaultuser0') {$WindowsPhase = 'OOBE'} + elseif ($ImageState -eq 'IMAGE_STATE_SPECIALIZE_RESEAL_TO_OOBE') {$WindowsPhase = 'Specialize'} + elseif ($ImageState -eq 'IMAGE_STATE_SPECIALIZE_RESEAL_TO_AUDIT') {$WindowsPhase = 'AuditMode'} + else {$WindowsPhase = 'Windows'} + } + + if ($WindowsPhase -eq 'WinPE'){ + #Insipiration & Some code from: https://github.com/JosephM101/Force-Windows-11-Install/blob/main/Win11-TPM-RegBypass.ps1 + + # Mount and edit the setup environment's registry + $REG_System = "C:\Windows\System32\config\system" + $VirtualRegistryPath_SYSTEM = "HKLM\WinPE_SYSTEM" #Load Command + $VirtualRegistryPath_Setup = "HKLM:\WinPE_SYSTEM\Setup" #PowerShell Path + + # $VirtualRegistryPath_LabConfig = $VirtualRegistryPath_Setup + "\LabConfig" + reg unload $VirtualRegistryPath_SYSTEM | Out-Null # Just in case... + Start-Sleep 1 + reg load $VirtualRegistryPath_SYSTEM $REG_System | Out-Null + + + + New-Item -Path $VirtualRegistryPath_Setup -Name "LabConfig" -Force | out-null + New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassTPMCheck" -Value 1 -PropertyType DWORD -Force | out-null + New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassSecureBootCheck" -Value 1 -PropertyType DWORD -Force | out-null + New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassRAMCheck" -Value 1 -PropertyType DWORD -Force | out-null + New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassStorageCheck" -Value 1 -PropertyType DWORD -Force | out-null + New-ItemProperty -Path "$VirtualRegistryPath_Setup\LabConfig" -Name "BypassCPUCheck" -Value 1 -PropertyType DWORD -Force | out-null + + New-Item -Path $VirtualRegistryPath_Setup -Name "MoSetup" -ErrorAction SilentlyContinue | out-null + New-ItemProperty -Path "$VirtualRegistryPath_Setup\MoSetup" -Name "AllowUpgradesWithUnsupportedTPMOrCPU" -Value 1 -PropertyType DWORD -Force | out-null + + + Start-Sleep 1 + reg unload $VirtualRegistryPath_SYSTEM + } + else { + if (!(Test-Path -Path HKLM:\SYSTEM\Setup\LabConfig)){ + New-Item -Path HKLM:\SYSTEM\Setup -Name "LabConfig" | out-null + } + New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassTPMCheck" -Value 1 -PropertyType DWORD -Force | out-null + New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassSecureBootCheck" -Value 1 -PropertyType DWORD -Force | out-null + New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassRAMCheck" -Value 1 -PropertyType DWORD -Force | out-null + New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassStorageCheck" -Value 1 -PropertyType DWORD -Force | out-null + New-ItemProperty -Path "HKLM:\SYSTEM\Setup\LabConfig" -Name "BypassCPUCheck" -Value 1 -PropertyType DWORD -Force | out-null + if (!(Test-Path -Path HKLM:\SYSTEM\Setup\MoSetup)){ + New-Item -Path HKLM:\SYSTEM\Setup -Name "MoSetup" | out-null + } + New-ItemProperty -Path "HKLM:\SYSTEM\Setup\MoSetup" -Name "AllowUpgradesWithUnsupportedTPMOrCPU" -Value 1 -PropertyType DWORD -Force | out-null + } +}