├── LTSConvert.bat ├── README.md └── LTSConvert.ps1 /LTSConvert.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd /d "%~dp0" 3 | powershell.exe -noexit -ExecutionPolicy Bypass -WindowStyle hidden -file .\LTSConvert.ps1 4 | exit 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LTSConvert 2 | A Powershell script to convert any 64-bit installation of Windows 10 to IoT Enterprise LTSC 2021 (EOL = January 13, 2032) without data loss - for those who want to avoid switching to Windows 11 and keep receiving updates on 10 3 | 4 | The script has been tested and works on Windows 10 Home, Pro, and Education - but it should work on any 64-bit edition of Windows 10 because of the way it functions 5 | 6 | ![image](https://github.com/user-attachments/assets/24975610-81f6-47cc-a34d-e62ca219d462) 7 | 8 | **_Instructions_** 9 | - Run LTSConvert.bat **as an administrator** to run the script (or run LTSConvert.ps1 manually from an admin Powershell prompt) 10 | - Click on 1: Write LTSC Registry Keys to apply the necessary registry edits to make this possible 11 | - Click on 2: Convert Windows to LTSC 2021 - select a Windows 10 Enterprise LTSC 2021 64-bit .ISO file, or press Cancel to choose an Archive.org mirror to download one from - either way, the .ISO will be mounted and setup.exe will be run automatically 12 | - If the installer asks you to download updates during the install, **do not do this. it will likely cause installation to fail** 13 | - After the Windows installer restarts your PC, run the LTSConvert script again and choose 3: Activate Windows - Windows will no longer be activated after the conversion 14 | - Choose Option 1 (HWID Activation) when the activation script runs - if it fails for some reason then choose option 4 (KMS38 Activation) 15 | - After Windows activates, run Windows Update to get the necessary LTSC updates 16 | 17 | **_Info_** 18 | - The Windows installer may hang a few times around 70-100% for a while - be patient and wait, it will continue 19 | 20 | 21 | 22 | **_Common Questions_** 23 | 24 | **Will I lose any data by doing this/does this remove anything I already have installed?** 25 | - No! That's exactly the purpose of this, so you can switch without losing any data or having to do a fresh installation of Windows. 26 | 27 | **Does IoT Enterprise LTSC 2021 support all of my programs/games/etc?** 28 | - Yes! I've personally been using IoT Enterprise LTSC since 2019, and I can confirm that everything works just as you'd expect with Windows 10 Home/Pro/etc. 29 | 30 | **IoT Enterprise LTSC 2021 is only on version 21H2 but my Windows 10 installation is on 22H2, will this still work without losing data?** 31 | - Yes! I've been successfully able to use this on Home, Pro, and Education versions of 22H2 - they convert back to 21H2 just fine. 32 | 33 | **Can I use a real key rather than [MAS](https://github.com/massgravel/Microsoft-Activation-Scripts "MAS") to activate Windows 10 IoT Enterprise LTSC 2021?** 34 | - Yes! The only caveat being that they're generally pretty pricey ($150+) but this converts you to a full, real installation of Windows 10 IoT Enterprise LTSC 2021. Just skip running step 3: Activate Windows if you'd like to do this. 35 | 36 | 37 | **Having an issue converting from a non-english Home version of Windows 10?** 38 | - See the solution [here](https://github.com/Bladez1992/LTSConvert/issues/2#issuecomment-2860795132 "here") - thanks [eilegz](https://github.com/eilegz "eilegz")! -------------------------------------------------------------------------------- /LTSConvert.ps1: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------------------------------------------------------------------------------- 2 | cd "$PSScriptRoot" 3 | 4 | #---------------------------------------------------------------------------------------------------------------------------------------------- 5 | Add-Type -AssemblyName System.Windows.Forms 6 | Add-Type -AssemblyName System.Drawing 7 | 8 | #---------------------------------------------------------------------------------------------------------------------------------------------- 9 | $form = New-Object System.Windows.Forms.Form 10 | $form.Icon = "$PSScriptRoot\Dependencies\LTSC.ico" 11 | $form.BackgroundImage = [System.Drawing.Image]::FromFile("$PSScriptRoot\Dependencies\LTSC.png") 12 | $form.BackgroundImageLayout = "Stretch" 13 | $form.BackColor = "Black" 14 | $form.ForeColor = "Cyan" 15 | $form.FormBorderStyle = "None" 16 | $form.Text = "LTSConvert by Bladez1992" 17 | $form.Size = New-Object System.Drawing.Size(450, 320) 18 | $form.StartPosition = "CenterScreen" 19 | 20 | #---------------------------------------------------------------------------------------------------------------------------------------------- 21 | $btn1LTSCKeys = New-Object System.Windows.Forms.Button 22 | $btn1LTSCKeys.Text = "1: Write LTSC Registry Keys" 23 | $btn1LTSCKeys.BackColor = "Black" 24 | $btn1LTSCKeys.ForeColor = "Cyan" 25 | $btn1LTSCKeys.Size = New-Object System.Drawing.Size(205, 20) 26 | $btn1LTSCKeys.Location = New-Object System.Drawing.Point(125, 25) 27 | $btn1LTSCKeys.Add_Click({ 28 | $registryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" 29 | 30 | $values = @{ 31 | "CompositionEditionID" = "EnterpriseS" 32 | "EditionID" = "EnterpriseS" 33 | "ProductName" = "Windows 10 Enterprise LTSC 2021" 34 | } 35 | 36 | foreach ($key in $values.Keys) { 37 | try { 38 | if ($values[$key] -is [byte[]]) { 39 | Set-ItemProperty -Path $registryPath -Name $key -Value $values[$key] -Type Binary -Force 40 | } 41 | elseif ($values[$key] -is [int]) { 42 | Set-ItemProperty -Path $registryPath -Name $key -Value $values[$key] -Type DWord -Force 43 | } 44 | else { 45 | Set-ItemProperty -Path $registryPath -Name $key -Value $values[$key] -Type String -Force 46 | } 47 | } 48 | catch { 49 | [System.Windows.Forms.MessageBox]::Show("Failed to set registry key: $key", "Error", "OK", "Error") 50 | } 51 | } 52 | [System.Windows.Forms.MessageBox]::Show("Registry values have been updated successfully, now click on Convert Windows to LTSC 2021.", "Success", "OK", "Information") 53 | }) 54 | 55 | #---------------------------------------------------------------------------------------------------------------------------------------------- 56 | $btn2ConvertWindows = New-Object System.Windows.Forms.Button 57 | $btn2ConvertWindows.Text = "2: Convert Windows to LTSC 2021" 58 | $btn2ConvertWindows.BackColor = "Black" 59 | $btn2ConvertWindows.ForeColor = "Cyan" 60 | $btn2ConvertWindows.Size = New-Object System.Drawing.Size(205, 20) 61 | $btn2ConvertWindows.Location = New-Object System.Drawing.Point(125, 50) 62 | 63 | $btn2ConvertWindows.Add_Click({ 64 | [System.Windows.Forms.MessageBox]::Show("After the Windows installer is finished and your PC restarts, run this script again and click on Activate Windows - the conversion to LTSC 2021 will deactivate Windows", "Instructions", "OK", "Information") 65 | $SelectedISO = $null 66 | $isDownload = $false # false if ISO is manually selected, true if downloading 67 | 68 | $mirrors = @{ 69 | "Archive.org Mirror 1" = "https://archive.org/download/Windows10EnterpriseLTSC202164Bit/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso" 70 | "Archive.org Mirror 2" = "https://archive.org/download/win10ltsc2021enx64/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso" 71 | "Archive.org Mirror 3" = "https://archive.org/download/Windows_10_Enterprise_LTSC_Versions/Windows%2010%20Enterprise%20LTSC%202021/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso" 72 | "Archive.org Mirror 4" = "https://archive.org/download/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96_202112/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso" 73 | } 74 | 75 | $fileDialog = New-Object System.Windows.Forms.OpenFileDialog 76 | $fileDialog.Filter = "ISO Files (*.iso)|*.iso" 77 | $fileDialog.Title = "Select a Windows 10 Enterprise LTSC 2021 64-bit ISO File or Press Cancel to Download One" 78 | if ($fileDialog.ShowDialog() -eq "OK") { 79 | $SelectedISO = $fileDialog.FileName 80 | } 81 | else { 82 | $folderDialog = New-Object System.Windows.Forms.FolderBrowserDialog 83 | $folderDialog.Description = "Select a location to save the downloaded ISO file" 84 | 85 | if ($folderDialog.ShowDialog() -eq "OK") { 86 | $mirrorDropdown = New-Object System.Windows.Forms.ComboBox 87 | $mirrorDropdown.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList 88 | $mirrorDropdown.Items.Add("Archive.org Mirror 1") 89 | $mirrorDropdown.Items.Add("Archive.org Mirror 2") 90 | $mirrorDropdown.Items.Add("Archive.org Mirror 3") 91 | $mirrorDropdown.Items.Add("Archive.org Mirror 4") 92 | $mirrorDropdown.SelectedIndex = 0 93 | $mirrorDropdown.Location = New-Object System.Drawing.Point(10, 40) 94 | $mirrorDropdown.Size = New-Object System.Drawing.Size(300, 20) 95 | 96 | $mirrorForm = New-Object System.Windows.Forms.Form 97 | $mirrorForm.Text = "Select an ISO Download Mirror" 98 | $mirrorForm.Size = New-Object System.Drawing.Size(350, 180) 99 | $mirrorForm.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen 100 | 101 | $label = New-Object System.Windows.Forms.Label 102 | $label.Text = "Select ISO Download Mirror" 103 | $label.Location = New-Object System.Drawing.Point(10, 10) 104 | $label.Size = New-Object System.Drawing.Size(320, 20) 105 | $mirrorForm.Controls.Add($label) 106 | $mirrorForm.Controls.Add($mirrorDropdown) 107 | 108 | $buttonOK = New-Object System.Windows.Forms.Button 109 | $buttonOK.Text = "OK" 110 | $buttonOK.Location = New-Object System.Drawing.Point(120, 80) 111 | $buttonOK.Add_Click({ $mirrorForm.Close() }) 112 | $mirrorForm.Controls.Add($buttonOK) 113 | 114 | $mirrorForm.ShowDialog() 115 | 116 | $SelectedMirror = $mirrorDropdown.SelectedItem 117 | if (-not $SelectedMirror) { 118 | [System.Windows.Forms.MessageBox]::Show("Invalid selection.", "Error", "OK", "Error") 119 | return 120 | } 121 | 122 | $remoteFilename = [System.IO.Path]::GetFileName($mirrors[$SelectedMirror]) 123 | $SelectedISO = Join-Path -Path $folderDialog.SelectedPath -ChildPath $remoteFilename 124 | 125 | $btn2ConvertWindows.Text = "Downloading ISO..." 126 | $btn2ConvertWindows.Enabled = $false 127 | $isDownload = $true 128 | 129 | $progressForm = New-Object System.Windows.Forms.Form 130 | $progressForm.Text = "Downloading ISO (4.56gb)" 131 | $progressForm.Size = New-Object System.Drawing.Size(325, 150) 132 | $progressForm.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen 133 | 134 | $progressLabel = New-Object System.Windows.Forms.Label 135 | $progressLabel.Size = New-Object System.Drawing.Size(250, 20) 136 | $progressLabel.Location = New-Object System.Drawing.Point(25, 40) 137 | $progressLabel.Text = "Downloading" 138 | $progressLabel.TextAlign = 'MiddleCenter' 139 | $progressForm.Controls.Add($progressLabel) 140 | 141 | $progressForm.Show() 142 | [System.Windows.Forms.Application]::DoEvents() 143 | 144 | $dotTimer = New-Object System.Windows.Forms.Timer 145 | $dotTimer.Interval = 500 146 | $dotTimer.Add_Tick({ 147 | $current = $progressLabel.Text 148 | if ($current -match "^Downloading(\.*)$") { 149 | $dots = $Matches[1] 150 | $progressLabel.Text = "Downloading" + ("." * (($dots.Length + 1) % 4)) 151 | } 152 | }) 153 | $dotTimer.Start() 154 | 155 | $wc = New-Object System.Net.WebClient 156 | # Start the download asynchronously... 157 | $wc.DownloadFileAsync($mirrors[$SelectedMirror], $SelectedISO) 158 | # Continuously poll until the download finishes 159 | while ($wc.IsBusy) { 160 | Start-Sleep -Milliseconds 200 161 | [System.Windows.Forms.Application]::DoEvents() 162 | } 163 | $dotTimer.Stop() 164 | $dotTimer.Dispose() 165 | $progressForm.Close() 166 | $progressForm.Dispose() 167 | 168 | try { 169 | $btn2ConvertWindows.Text = "Mounting ISO..." 170 | $mountResult = Mount-DiskImage -ImagePath $SelectedISO -PassThru 171 | # Wait up to 10 seconds for the drive letter to appear 172 | $attempt = 0 173 | do { 174 | Start-Sleep -Seconds 1 175 | $driveLetter = ($mountResult | Get-Volume).DriveLetter 176 | $attempt++ 177 | } until ($driveLetter -or $attempt -ge 10) 178 | if ($driveLetter) { 179 | $drivePath = "${driveLetter}:" 180 | $setupPath = Join-Path -Path $drivePath -ChildPath "setup.exe" 181 | if (Test-Path $setupPath) { 182 | $btn2ConvertWindows.Text = "Launching Setup..." 183 | Start-Process -FilePath $setupPath 184 | } 185 | else { 186 | [System.Windows.Forms.MessageBox]::Show("setup.exe not found in the root of the mounted ISO!", "Error", "OK", "Error") 187 | } 188 | } 189 | else { 190 | [System.Windows.Forms.MessageBox]::Show("Failed to mount the ISO or retrieve the drive letter.", "Error", "OK", "Error") 191 | } 192 | } 193 | catch { 194 | [System.Windows.Forms.MessageBox]::Show("An error occurred while mounting the ISO: $_", "Error", "OK", "Error") 195 | } 196 | finally { 197 | $btn2ConvertWindows.Text = "2: Convert Windows to LTSC 2021" 198 | $btn2ConvertWindows.Enabled = $true 199 | } 200 | return # Exit so the manual mount code below doesn't run 201 | } 202 | } 203 | 204 | if (-not $SelectedISO) { 205 | [System.Windows.Forms.MessageBox]::Show("No ISO selected or downloaded!", "Error", "OK", "Error") 206 | return 207 | } 208 | 209 | # --- If the user selected an ISO manually, mount it now --- 210 | if (-not $isDownload) { 211 | try { 212 | $btn2ConvertWindows.Text = "Mounting ISO..." 213 | $btn2ConvertWindows.Enabled = $false 214 | 215 | $mountResult = Mount-DiskImage -ImagePath $SelectedISO -PassThru 216 | $driveLetter = ($mountResult | Get-Volume).DriveLetter 217 | 218 | if ($driveLetter) { 219 | $drivePath = "${driveLetter}:" 220 | $setupPath = Join-Path -Path $drivePath -ChildPath "setup.exe" 221 | if (Test-Path $setupPath) { 222 | $btn2ConvertWindows.Text = "Launching Setup..." 223 | Start-Process -FilePath $setupPath 224 | } 225 | else { 226 | [System.Windows.Forms.MessageBox]::Show("setup.exe not found in the root of the mounted ISO!", "Error", "OK", "Error") 227 | } 228 | } 229 | else { 230 | [System.Windows.Forms.MessageBox]::Show("Failed to mount the ISO or retrieve the drive letter.", "Error", "OK", "Error") 231 | } 232 | } 233 | catch { 234 | [System.Windows.Forms.MessageBox]::Show("An error occurred while mounting the ISO: $_", "Error", "OK", "Error") 235 | } 236 | finally { 237 | $btn2ConvertWindows.Text = "2: Convert Windows to LTSC 2021" 238 | $btn2ConvertWindows.Enabled = $true 239 | } 240 | } 241 | }) 242 | 243 | #---------------------------------------------------------------------------------------------------------------------------------------------- 244 | $btn3ActivateWindows = New-Object System.Windows.Forms.Button 245 | $btn3ActivateWindows.Text = "3: Activate Windows" 246 | $btn3ActivateWindows.BackColor = "Black" 247 | $btn3ActivateWindows.ForeColor = "Cyan" 248 | $btn3ActivateWindows.Size = New-Object System.Drawing.Size(205, 20) 249 | $btn3ActivateWindows.Location = New-Object System.Drawing.Point(125, 75) 250 | $btn3ActivateWindows.Add_Click({ 251 | [System.Windows.Forms.MessageBox]::Show("Choose Option 1 (HWID Activation) when the activation script runs - if it fails for some reason then choose Option 4 (KMS38 Activation)", "Activation Instructions", "OK", "Information") 252 | 253 | $btn3ActivateWindows.Text = "3: Activating..." 254 | $btn3ActivateWindows.Enabled = $false 255 | 256 | try { 257 | irm https://get.activated.win | iex 258 | } 259 | catch { 260 | [System.Windows.Forms.MessageBox]::Show("An error occurred while executing the command: $_", "Error", "OK", "Error") 261 | } 262 | finally { 263 | $btn3ActivateWindows.Text = "3: Activate Windows" 264 | $btn3ActivateWindows.Enabled = $true 265 | } 266 | }) 267 | 268 | #---------------------------------------------------------------------------------------------------------------------------------------------- 269 | $btnOpenLTSConvertDir = New-Object System.Windows.Forms.Button 270 | $btnOpenLTSConvertDir.Text = "LTSConvert Directory" 271 | $btnOpenLTSConvertDir.BackColor = "Black" 272 | $btnOpenLTSConvertDir.ForeColor = "Cyan" 273 | $btnOpenLTSConvertDir.Size = New-Object System.Drawing.Size(125, 20) 274 | $btnOpenLTSConvertDir.Location = New-Object System.Drawing.Point(5, 295) 275 | $btnOpenLTSConvertDir.Add_Click({ 276 | Invoke-Item "$PSScriptRoot" 277 | }) 278 | 279 | #---------------------------------------------------------------------------------------------------------------------------------------------- 280 | $btnMinimize = New-Object System.Windows.Forms.Button 281 | $btnMinimize.Text = "-" 282 | $btnMinimize.BackColor = "Black" 283 | $btnMinimize.ForeColor = "Cyan" 284 | $btnMinimize.Font = New-Object System.Drawing.Font("Microsoft Sans Serif", 20, [System.Drawing.FontStyle]::Bold) 285 | $btnMinimize.Size = New-Object System.Drawing.Size(20, 20) 286 | $btnMinimize.Location = New-Object System.Drawing.Point(405, 5) 287 | $btnMinimize.Add_Click({ 288 | $form.WindowState = [System.Windows.Forms.FormWindowState]::Minimized 289 | }) 290 | 291 | $btnExit = New-Object System.Windows.Forms.Button 292 | $btnExit.Text = "X" 293 | $btnExit.BackColor = "Black" 294 | $btnExit.ForeColor = "Cyan" 295 | $btnExit.Font = New-Object System.Drawing.Font("Microsoft Sans Serif", 9, [System.Drawing.FontStyle]::Bold) 296 | $btnExit.Size = New-Object System.Drawing.Size(20, 20) 297 | $btnExit.Location = New-Object System.Drawing.Point(425, 5) 298 | $btnExit.Add_Click({ 299 | taskkill /IM "cmd.exe" /F 300 | taskkill /IM "powershell.exe" /F 301 | $form.Close() 302 | }) 303 | 304 | #---------------------------------------------------------------------------------------------------------------------------------------------- 305 | $form.Controls.Add($btn1LTSCKeys) 306 | $form.Controls.Add($btn2ConvertWindows) 307 | $form.Controls.Add($btn3ActivateWindows) 308 | $form.Controls.Add($btnOpenLTSConvertDir) 309 | $form.Controls.Add($btnMinimize) 310 | $form.Controls.Add($btnExit) 311 | $form.Controls.Add($btn3ActivateWindows) 312 | 313 | #---------------------------------------------------------------------------------------------------------------------------------------------- 314 | $form.ShowDialog() 315 | --------------------------------------------------------------------------------