diff --git a/.github/PSScriptAnalyzerSettings.psd1 b/.github/PSScriptAnalyzerSettings.psd1 new file mode 100644 index 0000000..3dc58c5 --- /dev/null +++ b/.github/PSScriptAnalyzerSettings.psd1 @@ -0,0 +1,20 @@ +@{ + # Rules excluded for BypassNRO: + # + # PSAvoidUsingWriteHost -- this is an interactive tool run from the OOBE + # Shift+F10 console, where coloured status output + # is the point. There is no pipeline to pollute. + ExcludeRules = @( + 'PSAvoidUsingWriteHost' + ) + + # The script targets Windows PowerShell 5.1, which is what Shift+F10 + # provides during OOBE. + Rules = @{ + PSUseCompatibleCmdlets = @{ + compatibility = @( + 'desktop-5.1.14393.206-windows' + ) + } + } +} diff --git a/.github/workflows/powershell.yml b/.github/workflows/powershell.yml index f4a3dde..a5b9aaf 100644 --- a/.github/workflows/powershell.yml +++ b/.github/workflows/powershell.yml @@ -35,11 +35,9 @@ jobs: uses: microsoft/psscriptanalyzer-action@6b2948b1944407914a58661c49941824d149734f with: # Check https://github.com/microsoft/action-psscriptanalyzer for more info about the options. - # The below set up runs PSScriptAnalyzer to your entire repository and runs some basic security rules. path: .\ recurse: true - # Include your own basic security rules. Removing this option will run all the rules - # includeRule: '"PSAvoidGlobalAliases", "PSAvoidUsingConvertToSecureStringWithPlainText"' + settings: .github/PSScriptAnalyzerSettings.psd1 output: results.sarif # Upload the SARIF file generated in the previous step diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53c5b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Windows +Thumbs.db +desktop.ini +$RECYCLE.BIN/ + +# Editors +.vscode/ +.idea/ +*.swp + +# Local test output +*.log +*.bak +*.bak-* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..adda56a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Stensel8 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index f4b8d7e..81eb7ed 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # Bypass NRO -## Status of Bypass Methods (December 2025) +## Status of Bypass Methods (August 2026) -Since March 2025, Microsoft's `oobe\bypassnro` command has been removed from Windows 11 (24H2/25H2). Until October 2025, the alternative `start ms-cxh:localonly` (and `start ms-cxh://setaddlocalonly`) worked, but this was blocked starting with Insider Build 26220.6772 (October 6, 2025) and only works on the current 25H2 stable branch. +Since March 2025, Microsoft's `oobe\bypassnro` command has been removed from Windows 11 (24H2/25H2). The alternative `start ms-cxh:localonly` (and `start ms-cxh://setaddlocalonly`) was blocked starting with Insider build 26220.6772 (October 6, 2025). Whether that block reached the retail 25H2 branch (26200.x) has not been re-tested here, so check your own image before relying on it. The **BypassNRO method in this project still works** because it uses Sysprep with a custom unattend.xml. This approach remains functional for now, because unattend.xml is part of Windows' official enterprise deployment tools and cannot easily be blocked by Microsoft without breaking enterprise scenarios. +Rufus, and on Pro/Enterprise "Set up for work or school" > "Sign-in options" > "Domain join instead", also still reach a local account. + **Timeline:** - Removal of `oobe\bypassnro`: March 2025 -- Blocking of `ms-cxh:localonly`: October 6, 2025 (Insider build 26220.6772 and higher) +- Blocking of `ms-cxh:localonly`: October 6, 2025 (Insider builds 26220.6772 / 26120.6772) ## Download and Run (Shift+F10 during OOBE) @@ -20,4 +22,24 @@ iex(irm bypassnro.stensel.nl) ### CMD Wrapper ```powershell powershell -c "iex(irm bypassnro.stensel.nl)" -``` \ No newline at end of file +``` + +`iex` cannot pass parameters. Use a script block for those: +```powershell +& ([scriptblock]::Create((irm bypassnro.stensel.nl))) -Force # skip confirmation +& ([scriptblock]::Create((irm bypassnro.stensel.nl))) -NoReboot # shut down instead +``` + +## Without Sysprep (faster) + +Save [`unattend.xml`](unattend.xml) to the root of the Windows 11 USB as `autounattend.xml`. Setup reads it during installation, so OOBE never asks for an account and there is no second reboot. + +## Accounts + +`unattend.xml` creates `Admin` (Administrators) and `User` (Users), both **without a password**, and signs `Admin` in automatically once. Set a password right after first logon. + +## Notes + +Only the `oobeSystem` pass applies. `Sysprep /oobe` without `/generalize` does not re-run `specialize`, so anything placed there is ignored. For debloating and tweaks use [WinDeploy](https://github.com/Stensel8/WinDeploy) or [WinUtil](https://github.com/ChrisTitusTech/winutil). + +Troubleshooting: Sysprep logs to `C:\Windows\System32\Sysprep\Panther\setuperr.log`. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7e56dcb --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +## Reporting a Vulnerability + +**Do not open a public GitHub issue for security vulnerabilities.** + +Report privately via [GitHub Security Advisories](https://github.com/Stensel8/bypassnro/security/advisories/new). + +Include: +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if any) + +You will receive a response within 7 days. If the report is accepted, a fix will be released as soon as possible and you will be credited in the release notes. + +### Out of scope + +By design, `unattend.xml` creates accounts without a password and auto-logs in once, and the one-liner downloads and runs a remote script. These are documented in the README, not vulnerabilities. diff --git a/bypass.ps1 b/bypass.ps1 index e958ff8..1ec4298 100644 --- a/bypass.ps1 +++ b/bypass.ps1 @@ -1,100 +1,188 @@ -<# -.SYNOPSIS - Downloads an unattend.xml from this repo and runs Sysprep /oobe /unattend: /reboot - - Designed for PowerShell 5.1 (Windows PowerShell). Requires elevation. - - USAGE: - - Ensure you run: Set-ExecutionPolicy Bypass -Scope Process -Force - - Run the script from an elevated PowerShell session. -#> -<# -.SYNOPSIS -Downloads an unattend.xml from this repo and runs Sysprep /oobe /unattend: /reboot - -Designed for PowerShell 5.1 (Windows PowerShell). Requires elevation. - -USAGE: - - Ensure you run: Set-ExecutionPolicy -Scope Process Bypass -Force - - Run the script from an elevated PowerShell session. -#> - -[CmdletBinding()] -param( - [string]$UnattendUrl = 'https://raw.githubusercontent.com/Stensel8/bypassnro/main/unattend.xml', - [string]$Destination = 'C:\Windows\Panther\unattend.xml' -) - -function Assert-Elevation { - if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { - Write-Error 'This script must be run elevated (as Administrator). Exiting.' - exit 1 - } -} - -function Save-Unattend { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)][string]$Url, - [Parameter(Mandatory=$true)][string]$OutPath - ) - - $dir = Split-Path -Path $OutPath -Parent - if (-not (Test-Path -Path $dir)) { - New-Item -Path $dir -ItemType Directory -Force | Out-Null - } - - Write-Host "Downloading unattend file from: $Url" -ForegroundColor Cyan - try { - # Use Invoke-WebRequest; fallback to Start-BitsTransfer if available - Invoke-WebRequest -Uri $Url -UseBasicParsing -OutFile $OutPath -ErrorAction Stop - } catch { - Write-Warning "Invoke-WebRequest failed: $($_.Exception.Message). Trying Start-BitsTransfer..." - try { - Start-BitsTransfer -Source $Url -Destination $OutPath -ErrorAction Stop - } catch { - Write-Error "Failed to download $Url - $($_.Exception.Message)" - exit 2 - } - } - - if (-not (Test-Path -Path $OutPath)) { - Write-Error "Download completed but file not found at $OutPath" - exit 3 - } -} - -function Start-Sysprep { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)][string]$UnattendPath - ) - - $sysprep = Join-Path -Path 'C:\Windows' -ChildPath 'System32\Sysprep\Sysprep.exe' - if (-not (Test-Path -Path $sysprep)) { - Write-Error "Sysprep not found at $sysprep" - exit 4 - } - - $argumentList = @('/oobe', "/unattend:`"$UnattendPath`"", '/reboot') - Write-Host "Running: $sysprep $($argumentList -join ' ')" -ForegroundColor Yellow - $proc = Start-Process -FilePath $sysprep -ArgumentList $argumentList -Wait -PassThru - - if ($proc.ExitCode -ne 0) { - Write-Error "Sysprep exited with code $($proc.ExitCode)" - exit $proc.ExitCode - } -} - -Assert-Elevation - -Write-Host "Using destination: $Destination" -ForegroundColor Green -Save-Unattend -Url $UnattendUrl -OutPath $Destination - -if (Test-Path -Path $Destination) { - Write-Host "Unattend file saved to $Destination" -ForegroundColor Green - Start-Sysprep -UnattendPath $Destination -} else { - Write-Error "Failed to place unattend file at $Destination" -ForegroundColor Red - exit 5 -} +<# +.SYNOPSIS + Sets up Windows 11 with a local account by applying an unattend.xml through Sysprep. + +.DESCRIPTION + Downloads an answer file from this repository, writes it to + C:\Windows\Panther\unattend.xml and runs: + + Sysprep.exe /oobe /unattend: /reboot + + On the next boot, OOBE processes the answer file's oobeSystem pass, which + creates local accounts and skips the Microsoft-account sign-in screens. + + Unlike `oobe\bypassnro` (removed in March 2025) and `ms-cxh:localonly` + (blocked from October 2025), unattend.xml is part of Windows' supported + deployment tooling, so it is not something Microsoft can remove without + breaking enterprise imaging. + +.PARAMETER UnattendUrl + Answer file to download. Defaults to the copy in this repository. + +.PARAMETER Destination + Where to write the answer file. Defaults to C:\Windows\Panther\unattend.xml. + +.PARAMETER Force + Skip the confirmation prompt. + +.PARAMETER NoReboot + Run Sysprep with /shutdown instead of /reboot. + +.EXAMPLE + & ([scriptblock]::Create((irm bypassnro.stensel.nl))) + + Run from an elevated prompt (Shift+F10 during OOBE gives you one). + See the README for the shorter pipe-to-execute one-liner. + +.EXAMPLE + & ([scriptblock]::Create((irm bypassnro.stensel.nl))) -Force + + Same, without the confirmation prompt. The short one-liner form cannot pass + parameters, so use a script block when you need them. + +.NOTES + Requires elevation. Designed for Windows PowerShell 5.1, which is what + Shift+F10 gives you during OOBE. + + THIS REBOOTS THE MACHINE and sends it back through OOBE. Any work in + progress is lost. +#> + +#Requires -Version 5.1 + +[CmdletBinding()] +param( + [string]$UnattendUrl = 'https://raw.githubusercontent.com/Stensel8/bypassnro/main/unattend.xml', + [string]$Destination = 'C:\Windows\Panther\unattend.xml', + [switch]$Force, + [switch]$NoReboot +) + +$ErrorActionPreference = 'Stop' + +# Invoke-WebRequest's progress bar makes downloads dramatically slower in +# Windows PowerShell, and it renders badly in the OOBE console. +$ProgressPreference = 'SilentlyContinue' + +function Assert-Elevation { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal]$identity + if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Error 'This script must be run elevated (as Administrator). Exiting.' + exit 1 + } +} + +function Save-Unattend { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)][string]$Url, + [Parameter(Mandatory = $true)][string]$OutPath + ) + + $dir = Split-Path -Path $OutPath -Parent + if (-not (Test-Path -Path $dir)) { + New-Item -Path $dir -ItemType Directory -Force | Out-Null + } + + # Download to a temporary file first, so a failed or truncated transfer + # cannot leave a broken answer file in C:\Windows\Panther. + $temp = Join-Path ([System.IO.Path]::GetTempPath()) ("unattend-{0}.xml" -f [guid]::NewGuid()) + + Write-Host "Downloading answer file from: $Url" -ForegroundColor Cyan + try { + Invoke-WebRequest -Uri $Url -UseBasicParsing -OutFile $temp -ErrorAction Stop + } catch { + Write-Warning "Invoke-WebRequest failed: $($_.Exception.Message). Trying Start-BitsTransfer..." + try { + Start-BitsTransfer -Source $Url -Destination $temp -ErrorAction Stop + } catch { + Write-Error "Failed to download $Url - $($_.Exception.Message)" + exit 2 + } + } + + if (-not (Test-Path -Path $temp)) { + Write-Error "Download reported success but no file was written to $temp" + exit 3 + } + + # Make sure we got XML and not a captive-portal page or a GitHub error. + try { + $xml = New-Object System.Xml.XmlDocument + $xml.Load($temp) + if ($xml.DocumentElement.LocalName -ne 'unattend') { + throw "root element is <$($xml.DocumentElement.LocalName)>, expected " + } + } catch { + Remove-Item -LiteralPath $temp -Force -ErrorAction SilentlyContinue + Write-Error "Downloaded file is not a valid unattend answer file: $($_.Exception.Message)" + exit 3 + } + + # Keep whatever was there before; Windows may already have an answer file. + if (Test-Path -LiteralPath $OutPath) { + $backup = "$OutPath.bak-{0}" -f (Get-Date -Format 'yyyyMMdd-HHmmss') + Copy-Item -LiteralPath $OutPath -Destination $backup -Force -ErrorAction SilentlyContinue + Write-Host "Existing answer file backed up to $backup" -ForegroundColor DarkGray + } + + Move-Item -LiteralPath $temp -Destination $OutPath -Force +} + +function Start-Sysprep { + [CmdletBinding(SupportsShouldProcess = $true)] + param( + [Parameter(Mandatory = $true)][string]$UnattendPath, + [switch]$Shutdown + ) + + $sysprep = Join-Path -Path $env:SystemRoot -ChildPath 'System32\Sysprep\Sysprep.exe' + if (-not (Test-Path -Path $sysprep)) { + Write-Error "Sysprep not found at $sysprep" + exit 4 + } + + $finish = if ($Shutdown) { '/shutdown' } else { '/reboot' } + $argumentList = @('/oobe', "/unattend:`"$UnattendPath`"", $finish) + + Write-Host "Running: $sysprep $($argumentList -join ' ')" -ForegroundColor Yellow + if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Run Sysprep /oobe $finish")) { + return + } + + $proc = Start-Process -FilePath $sysprep -ArgumentList $argumentList -Wait -PassThru + + # Sysprep restarts the machine itself, so a non-zero code here means it + # refused to run - check C:\Windows\System32\Sysprep\Panther\setuperr.log. + if ($proc.ExitCode -ne 0) { + Write-Error "Sysprep exited with code $($proc.ExitCode). See C:\Windows\System32\Sysprep\Panther\setuperr.log" + exit $proc.ExitCode + } +} + +Assert-Elevation + +# No TLS version is pinned here on purpose. Windows 11 (the only supported +# target) already negotiates TLS 1.2/1.3 through SystemDefault, and hardcoding +# a version stops the OS handing us a better protocol later. Pinning is only +# needed on Windows 7/8.1-era images, which this script does not support. + +if (-not $Force) { + Write-Host "" + Write-Host "This will run Sysprep and $(if ($NoReboot) { 'shut down' } else { 'restart' }) the computer." -ForegroundColor Yellow + Write-Host "Windows will go back through OOBE and create the local accounts" -ForegroundColor Yellow + Write-Host "defined in the answer file. Anything unsaved will be lost." -ForegroundColor Yellow + Write-Host "" + $answer = Read-Host "Continue? [y/N]" + if ($answer -notmatch '^(y|yes)$') { + Write-Host "Cancelled. Nothing was changed." -ForegroundColor Cyan + exit 0 + } +} + +Write-Host "Using destination: $Destination" -ForegroundColor Green +Save-Unattend -Url $UnattendUrl -OutPath $Destination +Write-Host "Answer file saved to $Destination" -ForegroundColor Green + +Start-Sysprep -UnattendPath $Destination -Shutdown:$NoReboot diff --git a/unattend.xml b/unattend.xml index 3e615a7..56977e3 100644 --- a/unattend.xml +++ b/unattend.xml @@ -1,512 +1,100 @@ - - - - - - - - - 00000-00000-00000-00000-00000 - OnError - - true - - false - - - - - - - - 1 - powershell.exe -WindowStyle Normal -NoProfile -Command "$xml = [xml]::new(); $xml.Load('C:\Windows\Panther\unattend.xml'); $sb = [scriptblock]::Create( $xml.unattend.Extensions.ExtractScript ); Invoke-Command -ScriptBlock $sb -ArgumentList $xml;" - - - 2 - powershell.exe -WindowStyle Normal -NoProfile -Command "Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\Specialize.ps1' -Raw | Invoke-Expression;" - - - 3 - reg.exe load "HKU\DefaultUser" "C:\Users\Default\NTUSER.DAT" - - - 4 - powershell.exe -WindowStyle Normal -NoProfile -Command "Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\DefaultUser.ps1' -Raw | Invoke-Expression;" - - - 5 - reg.exe unload "HKU\DefaultUser" - - - - - - - - - - - - Admin - - Administrators - - - true</PlainText> - </Password> - </LocalAccount> - <LocalAccount wcm:action="add"> - <Name>User</Name> - <DisplayName></DisplayName> - <Group>Users</Group> - <Password> - <Value></Value> - <PlainText>true</PlainText> - </Password> - </LocalAccount> - </LocalAccounts> - </UserAccounts> - <AutoLogon> - <Username>Admin</Username> - <Enabled>true</Enabled> - <LogonCount>1</LogonCount> - <Password> - <Value></Value> - <PlainText>true</PlainText> - </Password> - </AutoLogon> - <OOBE> - <ProtectYourPC>3</ProtectYourPC> - <HideEULAPage>true</HideEULAPage> - <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> - <HideOnlineAccountScreens>false</HideOnlineAccountScreens> - </OOBE> - <FirstLogonCommands> - <SynchronousCommand wcm:action="add"> - <Order>1</Order> - <CommandLine>powershell.exe -WindowStyle Normal -NoProfile -Command "Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\FirstLogon.ps1' -Raw | Invoke-Expression;"</CommandLine> - </SynchronousCommand> - </FirstLogonCommands> - </component> - </settings> - <Extensions xmlns="https://schneegans.de/windows/unattend-generator/"> - <ExtractScript> -param( - [xml] $Document -); - -foreach( $file in $Document.unattend.Extensions.File ) { - $path = [System.Environment]::ExpandEnvironmentVariables( $file.GetAttribute( 'path' ) ); - mkdir -Path( $path | Split-Path -Parent ) -ErrorAction 'SilentlyContinue'; - $encoding = switch( [System.IO.Path]::GetExtension( $path ) ) { - { $_ -in '.ps1', '.xml' } { [System.Text.Encoding]::UTF8; } - { $_ -in '.reg', '.vbs', '.js' } { [System.Text.UnicodeEncoding]::new( $false, $true ); } - default { [System.Text.Encoding]::Default; } - }; - $bytes = $encoding.GetPreamble() + $encoding.GetBytes( $file.InnerText.Trim() ); - [System.IO.File]::WriteAllBytes( $path, $bytes ); -} - </ExtractScript> - <File path="C:\Windows\Setup\Scripts\RemovePackages.ps1"> -$selectors = @( - 'Microsoft.Microsoft3DViewer'; - 'Microsoft.BingSearch'; - 'Microsoft.WindowsCamera'; - 'Clipchamp.Clipchamp'; - 'Microsoft.549981C3F5F10'; - 'Microsoft.Windows.DevHome'; - 'MicrosoftCorporationII.MicrosoftFamily'; - 'Microsoft.WindowsFeedbackHub'; - 'Microsoft.Edge.GameAssist'; - 'Microsoft.GetHelp'; - 'Microsoft.Getstarted'; - 'microsoft.windowscommunicationsapps'; - 'Microsoft.WindowsMaps'; - 'Microsoft.MixedReality.Portal'; - 'Microsoft.BingNews'; - 'Microsoft.WindowsNotepad'; - 'Microsoft.MicrosoftOfficeHub'; - 'Microsoft.Office.OneNote'; - 'Microsoft.OutlookForWindows'; - 'Microsoft.MSPaint'; - 'Microsoft.People'; - 'Microsoft.Windows.Photos'; - 'Microsoft.PowerAutomateDesktop'; - 'MicrosoftCorporationII.QuickAssist'; - 'Microsoft.SkypeApp'; - 'Microsoft.MicrosoftSolitaireCollection'; - 'Microsoft.MicrosoftStickyNotes'; - 'MicrosoftTeams'; - 'MSTeams'; - 'Microsoft.Todos'; - 'Microsoft.WindowsSoundRecorder'; - 'Microsoft.Wallet'; - 'Microsoft.BingWeather'; - 'Microsoft.Xbox.TCUI'; - 'Microsoft.XboxApp'; - 'Microsoft.XboxGameOverlay'; - 'Microsoft.XboxGamingOverlay'; - 'Microsoft.XboxIdentityProvider'; - 'Microsoft.XboxSpeechToTextOverlay'; - 'Microsoft.GamingApp'; - 'Microsoft.ZuneMusic'; - 'Microsoft.ZuneVideo'; -); -$getCommand = { - Get-AppxProvisionedPackage -Online; -}; -$filterCommand = { - $_.DisplayName -eq $selector; -}; -$removeCommand = { - [CmdletBinding()] - param( - [Parameter( Mandatory, ValueFromPipeline )] - $InputObject - ); - process { - $InputObject | Remove-AppxProvisionedPackage -AllUsers -Online -ErrorAction 'Continue'; - } -}; -$type = 'Package'; -$logfile = 'C:\Windows\Setup\Scripts\RemovePackages.log'; -&amp; { - $installed = &amp; $getCommand; - foreach( $selector in $selectors ) { - $result = [ordered] @{ - Selector = $selector; - }; - $found = $installed | Where-Object -FilterScript $filterCommand; - if( $found ) { - $result.Output = $found | &amp; $removeCommand; - if( $? ) { - $result.Message = "$type removed."; - } else { - $result.Message = "$type not removed."; - $result.Error = $Error[0]; - } - } else { - $result.Message = "$type not installed."; - } - $result | ConvertTo-Json -Depth 3 -Compress; - } -} *&gt;&amp;1 &gt;&gt; $logfile; - </File> - <File path="C:\Windows\Setup\Scripts\RemoveCapabilities.ps1"> -$selectors = @( - 'Print.Fax.Scan'; - 'Language.Handwriting'; - 'Browser.InternetExplorer'; - 'MathRecognizer'; - 'OneCoreUAP.OneSync'; - 'OpenSSH.Client'; - 'Microsoft.Windows.PowerShell.ISE'; - 'App.Support.QuickAssist'; - 'Language.Speech'; - 'Language.TextToSpeech'; - 'App.StepsRecorder'; - 'Hello.Face.18967'; - 'Hello.Face.Migration.18967'; - 'Hello.Face.20134'; - 'Media.WindowsMediaPlayer'; - 'Microsoft.Windows.WordPad'; -); -$getCommand = { - Get-WindowsCapability -Online | Where-Object -Property 'State' -NotIn -Value @( - 'NotPresent'; - 'Removed'; - ); -}; -$filterCommand = { - ($_.Name -split '~')[0] -eq $selector; -}; -$removeCommand = { - [CmdletBinding()] - param( - [Parameter( Mandatory, ValueFromPipeline )] - $InputObject - ); - process { - $InputObject | Remove-WindowsCapability -Online -ErrorAction 'Continue'; - } -}; -$type = 'Capability'; -$logfile = 'C:\Windows\Setup\Scripts\RemoveCapabilities.log'; -&amp; { - $installed = &amp; $getCommand; - foreach( $selector in $selectors ) { - $result = [ordered] @{ - Selector = $selector; - }; - $found = $installed | Where-Object -FilterScript $filterCommand; - if( $found ) { - $result.Output = $found | &amp; $removeCommand; - if( $? ) { - $result.Message = "$type removed."; - } else { - $result.Message = "$type not removed."; - $result.Error = $Error[0]; - } - } else { - $result.Message = "$type not installed."; - } - $result | ConvertTo-Json -Depth 3 -Compress; - } -} *&gt;&amp;1 &gt;&gt; $logfile; - </File> - <File path="C:\Windows\Setup\Scripts\RemoveFeatures.ps1"> -$selectors = @( - 'MediaPlayback'; - 'MicrosoftWindowsPowerShellV2Root'; - 'Microsoft-RemoteDesktopConnection'; - 'Recall'; -); -$getCommand = { - Get-WindowsOptionalFeature -Online | Where-Object -Property 'State' -NotIn -Value @( - 'Disabled'; - 'DisabledWithPayloadRemoved'; - ); -}; -$filterCommand = { - $_.FeatureName -eq $selector; -}; -$removeCommand = { - [CmdletBinding()] - param( - [Parameter( Mandatory, ValueFromPipeline )] - $InputObject - ); - process { - $InputObject | Disable-WindowsOptionalFeature -Online -Remove -NoRestart -ErrorAction 'Continue'; - } -}; -$type = 'Feature'; -$logfile = 'C:\Windows\Setup\Scripts\RemoveFeatures.log'; -&amp; { - $installed = &amp; $getCommand; - foreach( $selector in $selectors ) { - $result = [ordered] @{ - Selector = $selector; - }; - $found = $installed | Where-Object -FilterScript $filterCommand; - if( $found ) { - $result.Output = $found | &amp; $removeCommand; - if( $? ) { - $result.Message = "$type removed."; - } else { - $result.Message = "$type not removed."; - $result.Error = $Error[0]; - } - } else { - $result.Message = "$type not installed."; - } - $result | ConvertTo-Json -Depth 3 -Compress; - } -} *&gt;&amp;1 &gt;&gt; $logfile; - </File> - <File path="C:\Windows\Setup\Scripts\SetStartPins.ps1"> -$json = '{"pinnedList":[]}'; -if( [System.Environment]::OSVersion.Version.Build -lt 20000 ) { - return; -} -$key = 'Registry::HKLM\SOFTWARE\Microsoft\PolicyManager\current\device\Start'; -New-Item -Path $key -ItemType 'Directory' -ErrorAction 'SilentlyContinue'; -Set-ItemProperty -LiteralPath $key -Name 'ConfigureStartPins' -Value $json -Type 'String'; - </File> - <File path="C:\Windows\Setup\Scripts\Specialize.ps1"> -$scripts = @( - { - Remove-Item -LiteralPath 'Registry::HKLM\Software\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\DevHomeUpdate' -Force -ErrorAction 'SilentlyContinue'; - }; - { - reg.exe add "HKCR\.txt\ShellNew" /v ItemName /t REG_EXPAND_SZ /d "@C:\Windows\system32\notepad.exe,-470" /f; - reg.exe add "HKCR\.txt\ShellNew" /v NullFile /t REG_SZ /f; - reg.exe add "HKCR\txtfilelegacy" /v FriendlyTypeName /t REG_EXPAND_SZ /d "@C:\Windows\system32\notepad.exe,-469" /f; - reg.exe add "HKCR\txtfilelegacy" /ve /t REG_SZ /d "Text Document" /f; - }; - { - Remove-Item -LiteralPath 'C:\Users\Default\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk', 'C:\Windows\System32\OneDriveSetup.exe', 'C:\Windows\SysWOW64\OneDriveSetup.exe' -ErrorAction 'Continue'; - }; - { - Remove-Item -LiteralPath 'Registry::HKLM\Software\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate' -Force -ErrorAction 'SilentlyContinue'; - }; - { - reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications" /v ConfigureChatAutoInstall /t REG_DWORD /d 0 /f; - }; - { - Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\RemovePackages.ps1' -Raw | Invoke-Expression; - }; - { - Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\RemoveCapabilities.ps1' -Raw | Invoke-Expression; - }; - { - Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\RemoveFeatures.ps1' -Raw | Invoke-Expression; - }; - { - net.exe accounts /maxpwage:UNLIMITED; - }; - { - reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f - }; - { - reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Dsh" /v AllowNewsAndInterests /t REG_DWORD /d 0 /f; - }; - { - Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\SetStartPins.ps1' -Raw | Invoke-Expression; - }; -); - -&amp; { - [float] $complete = 0; - [float] $increment = 100 / $scripts.Count; - foreach( $script in $scripts ) { - Write-Progress -Activity 'Running scripts to customize your Windows installation. Do not close this window.' -PercentComplete $complete; - '*** Will now execute command &#xAB;{0}&#xBB;.' -f $( - $str = $script.ToString().Trim() -replace '\s+', ' '; - $max = 100; - if( $str.Length -le $max ) { - $str; - } else { - $str.Substring( 0, $max - 1 ) + '&#x2026;'; - } - ); - $start = [datetime]::Now; - &amp; $script; - '*** Finished executing command after {0:0} ms.' -f [datetime]::Now.Subtract( $start ).TotalMilliseconds; - "`r`n" * 3; - $complete += $increment; - } -} *&gt;&amp;1 &gt;&gt; "C:\Windows\Setup\Scripts\Specialize.log"; - </File> - <File path="C:\Windows\Setup\Scripts\UserOnce.ps1"> -$scripts = @( - { - Get-AppxPackage -Name 'Microsoft.Windows.Ai.Copilot.Provider' | Remove-AppxPackage; - }; - { - Set-ItemProperty -LiteralPath 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Search' -Name 'SearchboxTaskbarMode' -Type 'DWord' -Value 0; - }; - { - Get-Process -Name 'explorer' -ErrorAction 'SilentlyContinue' | Where-Object -FilterScript { - $_.SessionId -eq ( Get-Process -Id $PID ).SessionId; - } | Stop-Process -Force; - }; -); - -&amp; { - [float] $complete = 0; - [float] $increment = 100 / $scripts.Count; - foreach( $script in $scripts ) { - Write-Progress -Activity 'Running scripts to configure this user account. Do not close this window.' -PercentComplete $complete; - '*** Will now execute command &#xAB;{0}&#xBB;.' -f $( - $str = $script.ToString().Trim() -replace '\s+', ' '; - $max = 100; - if( $str.Length -le $max ) { - $str; - } else { - $str.Substring( 0, $max - 1 ) + '&#x2026;'; - } - ); - $start = [datetime]::Now; - &amp; $script; - '*** Finished executing command after {0:0} ms.' -f [datetime]::Now.Subtract( $start ).TotalMilliseconds; - "`r`n" * 3; - $complete += $increment; - } -} *&gt;&amp;1 &gt;&gt; "$env:TEMP\UserOnce.log"; - </File> - <File path="C:\Windows\Setup\Scripts\DefaultUser.ps1"> -$scripts = @( - { - reg.exe add "HKU\DefaultUser\Software\Policies\Microsoft\Windows\WindowsCopilot" /v TurnOffWindowsCopilot /t REG_DWORD /d 1 /f; - }; - { - reg.exe add "HKU\DefaultUser\Software\Microsoft\Internet Explorer\LowRegistry\Audio\PolicyConfig\PropertyStore" /f; - }; - { - reg.exe add "HKU\DefaultUser\Software\Microsoft\Notepad" /v ShowStoreBanner /t REG_DWORD /d 0 /f; - }; - { - Remove-ItemProperty -LiteralPath 'Registry::HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\Run' -Name 'OneDriveSetup' -Force -ErrorAction 'Continue'; - }; - { - reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\GameDVR" /v AppCaptureEnabled /t REG_DWORD /d 0 /f; - }; - { - reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v ShowTaskViewButton /t REG_DWORD /d 0 /f; - }; - { - reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v TaskbarAl /t REG_DWORD /d 0 /f; - }; - { - reg.exe add "HKU\DefaultUser\Software\Policies\Microsoft\Windows\Explorer" /v DisableSearchBoxSuggestions /t REG_DWORD /d 1 /f; - }; - { - reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\RunOnce" /v "UnattendedSetup" /t REG_SZ /d "powershell.exe -WindowStyle Normal -NoProfile -Command \""Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\UserOnce.ps1' -Raw | Invoke-Expression;\""" /f; - }; -); - -&amp; { - [float] $complete = 0; - [float] $increment = 100 / $scripts.Count; - foreach( $script in $scripts ) { - Write-Progress -Activity 'Running scripts to modify the default user&#x2019;&#x2019;s registry hive. Do not close this window.' -PercentComplete $complete; - '*** Will now execute command &#xAB;{0}&#xBB;.' -f $( - $str = $script.ToString().Trim() -replace '\s+', ' '; - $max = 100; - if( $str.Length -le $max ) { - $str; - } else { - $str.Substring( 0, $max - 1 ) + '&#x2026;'; - } - ); - $start = [datetime]::Now; - &amp; $script; - '*** Finished executing command after {0:0} ms.' -f [datetime]::Now.Subtract( $start ).TotalMilliseconds; - "`r`n" * 3; - $complete += $increment; - } -} *&gt;&amp;1 &gt;&gt; "C:\Windows\Setup\Scripts\DefaultUser.log"; - </File> - <File path="C:\Windows\Setup\Scripts\FirstLogon.ps1"> -$scripts = @( - { - Set-ItemProperty -LiteralPath 'Registry::HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name 'AutoLogonCount' -Type 'DWord' -Force -Value 0; - }; - { - cmd.exe /c "rmdir C:\Windows.old"; - }; - { - Remove-Item -LiteralPath @( - 'C:\Windows\Panther\unattend.xml'; - 'C:\Windows\Panther\unattend-original.xml'; - 'C:\Windows\Setup\Scripts\Wifi.xml'; - ) -Force -ErrorAction 'SilentlyContinue' -Verbose; - }; -); - -&amp; { - [float] $complete = 0; - [float] $increment = 100 / $scripts.Count; - foreach( $script in $scripts ) { - Write-Progress -Activity 'Running scripts to finalize your Windows installation. Do not close this window.' -PercentComplete $complete; - '*** Will now execute command &#xAB;{0}&#xBB;.' -f $( - $str = $script.ToString().Trim() -replace '\s+', ' '; - $max = 100; - if( $str.Length -le $max ) { - $str; - } else { - $str.Substring( 0, $max - 1 ) + '&#x2026;'; - } - ); - $start = [datetime]::Now; - &amp; $script; - '*** Finished executing command after {0:0} ms.' -f [datetime]::Now.Subtract( $start ).TotalMilliseconds; - "`r`n" * 3; - $complete += $increment; - } -} *&gt;&amp;1 &gt;&gt; "C:\Windows\Setup\Scripts\FirstLogon.log"; - </File> - </Extensions> -</unattend> +<?xml version="1.0" encoding="utf-8"?> +<!-- + BypassNRO answer file - https://github.com/Stensel8/bypassnro + + Creates local accounts and skips the Microsoft-account sign-in screens + during OOBE. Works two ways: + + 1. Sysprep (bypass.ps1) + Sysprep.exe /oobe /unattend:C:\Windows\Panther\unattend.xml /reboot + Only the oobeSystem pass is processed. The specialize pass is NOT + re-run without /generalize, which is why there is nothing in it. + + 2. Installation media (recommended) + Rename to autounattend.xml and put it on the root of the Windows 11 + USB. Setup picks it up automatically and both windowsPE and + oobeSystem are processed. This never reaches the account screen at + all, so there is nothing to bypass. + + WARNING: both accounts below are created WITHOUT A PASSWORD, and Admin is + logged on automatically once. Set a password immediately after first logon. +--> +<unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"> + <settings pass="offlineServicing"></settings> + <settings pass="windowsPE"> + <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <UserData> + <ProductKey> + <!-- All zeroes: skip the product key page and use the firmware/digital licence. --> + <Key>00000-00000-00000-00000-00000</Key> + <WillShowUI>OnError</WillShowUI> + </ProductKey> + <AcceptEula>true</AcceptEula> + </UserData> + <UseConfigurationSet>false</UseConfigurationSet> + </component> + </settings> + <settings pass="generalize"></settings> + <settings pass="specialize"></settings> + <settings pass="auditSystem"></settings> + <settings pass="auditUser"></settings> + <settings pass="oobeSystem"> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <!-- + Defining LocalAccounts here is what actually performs the bypass: + OOBE skips the account screens entirely because the accounts already + exist, so it never asks for a Microsoft account. + --> + <UserAccounts> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Name>Admin</Name> + <DisplayName></DisplayName> + <Group>Administrators</Group> + <Password> + <Value></Value> + <PlainText>true</PlainText> + </Password> + </LocalAccount> + <LocalAccount wcm:action="add"> + <Name>User</Name> + <DisplayName></DisplayName> + <Group>Users</Group> + <Password> + <Value></Value> + <PlainText>true</PlainText> + </Password> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <AutoLogon> + <Username>Admin</Username> + <Enabled>true</Enabled> + <LogonCount>1</LogonCount> + <Password> + <Value></Value> + <PlainText>true</PlainText> + </Password> + </AutoLogon> + <OOBE> + <ProtectYourPC>3</ProtectYourPC> + <HideEULAPage>true</HideEULAPage> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + </OOBE> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <Description>Disable autologon after the first sign-in</Description> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoLogonCount /t REG_DWORD /d 0 /f</CommandLine> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <Description>Remove the answer file, which contains the plaintext passwords</Description> + <CommandLine>cmd.exe /c del /q /f "C:\Windows\Panther\unattend.xml" "C:\Windows\Panther\unattend-original.xml"</CommandLine> + </SynchronousCommand> + </FirstLogonCommands> + </component> + </settings> +</unattend>