Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

Windows 11 Semi-automatic Creation of Windows ISO

  • Thread Author
This guide is to automate many manual and error-prone steps of "How to create a Windows 10 ISO image for clean, in-place upgrade and repair install" when is used to prepare Custom Windows 11 ISO with pre-installed software, no user accounts.

This guide is only applicable to Windows 11 (both guest and host). Tested on Host Windows 11 Home and guest Windows 11 Home.
This automatizes steps 3.3 - 3.6, 4.6 - 4.13, 5.1 - 5.5 from Create Windows 10 ISO image from Existing Installation written by Kari.

I've made two scripts to avoid unintentional errors when following Kari's manual steps to prepare custom Windows ISO with pre-installed Visual Studio for developer's needs, with some of Windows telemetry and bloatware disabled and removed.

Code:
@echo off

cd /d "c:\Windows\Temp"

:: Download Office 2021 HomeAndStudent; more Office 2021 ISOs here: https://web.archive.org/web/20240520043111/https://gist.github.com/DartPower/e9809d3468eec83af9c42057cb3375bc
echo 'Downloading HomeStudent2021Retail.img'
powershell -command "& cmd.exe /c 'curl.exe 2>&1' 'https://officecdn.microsoft.com/db/492350f6-3a01-4f97-b9c0-c7c6ddf67d60/media/en-us/HomeStudent2021Retail.img' -S -L --output 'HomeStudent2021Retail.img' | Tee-Object -FilePath '../Logs/download_Office2021.log'"

:: Download MobaXterm to transmit files between host OS and VM
echo 'Downloading MobaXterm_Installer_v24.0.zip'
powershell -command "& cmd.exe /c 'curl.exe 2>&1' 'https://web.archive.org/web/20240424095026/https://download.mobatek.net/2402024022512842/MobaXterm_Installer_v24.0.zip' -S -L --output 'MobaXterm_Installer_v24.0.zip' | Tee-Object -FilePath '../Logs/download_MobaXterm.log'"

:: Download 7-zip
echo 'Downloading 7z2301-x64.msi'
powershell -command "& cmd.exe /c 'curl.exe 2>&1' 'https://7-zip.org/a/7z2301-x64.msi' -S -L --output '7z2301-x64.msi' | Tee-Object -FilePath '../Logs/download_7zip.log'"

:: Download cmake
echo 'Downloading cmake-3.29.2-windows-x86_64.msi'
powershell -command "& cmd.exe /c 'curl.exe 2>&1' 'https://github.com/Kitware/CMake/releases/download/v3.29.2/cmake-3.29.2-windows-x86_64.msi' -S -L --output 'cmake-3.29.2-windows-x86_64.msi' | Tee-Object -FilePath '../Logs/download_7zip.log'"

:: Download mingw
echo 'winlibs-x86_64-mcf-seh-gcc-13.2.0-mingw-w64ucrt-11.0.1-r3.zip'
powershell -command "& cmd.exe /c 'curl.exe 2>&1' 'https://github.com/brechtsanders/winlibs_mingw/releases/download/13.2.0mcf-11.0.1-ucrt-r3/winlibs-x86_64-mcf-seh-gcc-13.2.0-mingw-w64ucrt-11.0.1-r3.zip' -S -L --output 'winlibs-x86_64-mcf-seh-gcc-13.2.0-mingw-w64ucrt-11.0.1-r3.zip' --output-dir 'c:\Windows' | Tee-Object -FilePath '../Logs/download_mingw.log'"

:: Install 7-zip by piping the result to force a wait until the process is finished
powershell -command "& msiexec.exe /i 7z2301-x64.msi /quiet /qn /norestart /log 7z2301-x64.log | Out-File -FilePath '..\Logs\ps_7z_install.log' -Encoding Unicode" && move "7z2301-x64.log" "..\Logs\7z2301-x64.log"

:: Unzip MobaXterm
"c:\Program Files\7-Zip\7z.exe" e MobaXterm_Installer_v24.0.zip > tmp.txt & cmd /u /c type tmp.txt > ../Logs/unzip_MobaXterm.log & del tmp.txt

:: Unzip Office2021HomeStudent
"c:\Program Files\7-Zip\7z.exe" x -oOffice2021HomeStudent HomeStudent2021Retail.img > tmp.txt & cmd /u /c type tmp.txt > ../Logs/unzip_Office2021HomeStudent.log & del tmp.txt


:: Create Remove all Offices config
(
echo ^<Configuration^>
echo     ^<Remove All="TRUE"/^>
echo     ^<Display Level="None" CompletionNotice="no" SuppressModal="yes" AcceptEULA="TRUE"/^>
echo     ^<Property Name="AUTOACTIVATE" Value="0"/^>
echo     ^<Property Name="FORCEAPPSHUTDOWN" Value="TRUE"/^>
echo     ^<Property Name="SharedComputerLicensing" Value="0"/^>
echo     ^<Property Name="PinIconsToTaskbar" Value="FALSE"/^>
echo     ^<Logging Level="Standard" Path="C:\Windows\Logs" /^>
echo ^</Configuration^>
)>"C:\Windows\Temp\Office2021HomeStudent\uninstall.xml"

:: Create Office Install config
(
echo ^<Configuration^>
echo   ^<Add OfficeClientEdition="64"^>
echo     ^<Product ID="HomeStudent2021Retail"^>
echo       ^<Language ID="ru-ru" /^>
echo     ^</Product^>
echo   ^</Add^>
echo   ^<Display Level="None" CompletionNotice="no" SuppressModal="yes" AcceptEULA="TRUE" /^>
echo   ^<Logging Level="Standard" Path="C:\Windows\Logs" /^>
echo ^</Configuration^>
)>"C:\Windows\Temp\Office2021HomeStudent\configuration.xml"

:: Remove Office 365
powershell -command "(Get-WMIObject win32_product | Where-Object {$_.name -match 'Office365'}).uninstall()"

:: Uninstall previous Offices and install fresh one
cd Office2021HomeStudent
powershell -command "& '.\setup.exe' /configure uninstall.xml | Out-File -FilePath '..\..\Logs\uninstall_previous_microsoft_office.log' -Encoding Unicode"
taskkill /im Setup32.exe
taskkill /im OfficeC2RClient.exe

powershell -command "& '.\setup.exe' /configure configuration.xml | Out-File -FilePath '..\..\Logs\install_new_microsoft_office.log' -Encoding Unicode"
taskkill /im Setup32.exe
taskkill /im OfficeC2RClient.exe

cd ..

:: Install MobaXterm by piping the result to force a wait until the process is finished
powershell -command "& msiexec.exe /i MobaXterm_installer_24.0.msi /quiet /qn /norestart /log MobaXterm_installer_24.0.log | Out-File -FilePath '..\Logs\install_MobaXterm.log' -Encoding Unicode" && move "MobaXterm_installer_24.0.log" "..\Logs\MobaXterm_installer_24.0.log"

:: Install cmake by piping the result to force a wait until the process is finished
powershell -command "& msiexec.exe /i cmake-3.29.2-windows-x86_64.msi /quiet /qn /norestart /log cmake-3.29.2-windows-x86_64.log | Out-File -FilePath '..\Logs\install_cmake.log' -Encoding Unicode" && move "cmake-3.29.2-windows-x86_64.log" "..\Logs\cmake-3.29.2-windows-x86_64.log"

:: Download Visual Studio bootstrapper
echo 'Downloading vs_enterprise.exe'
powershell -command "& cmd.exe /c 'curl.exe 2>&1' 'https://aka.ms/vs/16/release/vs_enterprise.exe' -S -L --output 'vs_enterprise.exe' | Tee-Object -FilePath '../Logs/download_Visual_studio_bootstrapper.log'"

:: Make list of Visual Studio components and workloads to be installed
(
echo ^{
echo   ^"version^": ^"1.0^"^,
echo   ^"components^": ^[
echo     "Microsoft.VisualStudio.Component.CoreEditor",
echo     "Microsoft.VisualStudio.Workload.CoreEditor",
echo     "Microsoft.VisualStudio.Workload.Data",
echo     "Microsoft.VisualStudio.Workload.ManagedDesktop",
echo     "Microsoft.NetCore.Component.DevelopmentTools",
echo     "Microsoft.VisualStudio.Component.Debugger.JustInTime",
echo     "Microsoft.VisualStudio.Component.DiagnosticTools",
echo     "Microsoft.VisualStudio.Component.EntityFramework",
echo     "Microsoft.VisualStudio.Component.FSharp",
echo     "Microsoft.VisualStudio.Component.IntelliCode",
echo     "Microsoft.VisualStudio.Component.IntelliTrace.FrontEnd",
echo     "Microsoft.VisualStudio.Component.JavaScript.TypeScript",
echo     "Microsoft.VisualStudio.Component.LiveUnitTesting",
echo     "Microsoft.VisualStudio.Component.NuGet",
echo     "Microsoft.VisualStudio.Component.TypeScript.4.3",
echo     "Microsoft.VisualStudio.Component.ClassDesigner",
echo     "Microsoft.VisualStudio.Component.CodeClone",
echo     "Microsoft.VisualStudio.Component.CodeMap",
echo     "Microsoft.VisualStudio.Component.DependencyValidation.Enterprise",
echo     "Microsoft.VisualStudio.Component.DockerTools",
echo     "Microsoft.VisualStudio.Component.FSharp.Desktop",
echo     "Microsoft.VisualStudio.Component.GraphDocument",
echo     "Microsoft.VisualStudio.Component.JavaScript.Diagnostics",
echo     "Microsoft.VisualStudio.Component.MSODBC.SQL",
echo     "Microsoft.VisualStudio.Component.MSSQL.CMDLnUtils",
echo     "Microsoft.VisualStudio.Component.PortableLibrary",
echo     "Microsoft.VisualStudio.Component.SQL.ADAL",
echo     "Microsoft.VisualStudio.Component.SQL.DataSources",
echo     "Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime",
echo     "Microsoft.VisualStudio.Component.SQL.SSDT",
echo     "Microsoft.VisualStudio.ComponentGroup.ArchitectureTools.Managed",
echo     "Microsoft.VisualStudio.Component.PortableLibrary",
echo     "Microsoft.VisualStudio.Component.Web",
echo     "Microsoft.Net.ComponentGroup.4.8.DeveloperTools",
echo     "Microsoft.Net.ComponentGroup.4.6.2.DeveloperTools",
echo     "Microsoft.VisualStudio.Workload.NativeDesktop",
echo     "Microsoft.VisualStudio.Component.VC.CMake.Project",
echo     "Microsoft.VisualStudio.Component.VC.DiagnosticTools",
echo     "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake",
echo     "Microsoft.VisualStudio.Workload.Node",
echo     "Microsoft.VisualStudio.Workload.Python",
echo     "Microsoft.Component.PythonTools.Web",
echo     "Component.CPython39.x64",
echo     "Microsoft.ComponentGroup.PythonTools.NativeDevelopment",
echo     "Microsoft.VisualStudio.Component.DockerTools",
echo     "Microsoft.Net.Component.4.8.SDK",
echo     "Microsoft.Net.Component.4.6.2.TargetingPack",
echo     "Microsoft.Net.ComponentGroup.DevelopmentPrerequisites",
echo     "Microsoft.VisualStudio.Component.ManagedDesktop.Core",
echo     "Microsoft.VisualStudio.Component.SQL.CLR",
echo     "Microsoft.VisualStudio.Component.TextTemplating",
echo     "Microsoft.VisualStudio.Component.VC.CoreIde",
echo     "Microsoft.VisualStudio.Component.TypeScript.4.3",
echo     "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
echo     "Microsoft.VisualStudio.ComponentGroup.Web",
echo     "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions",
echo     "Microsoft.VisualStudio.Workload.Universal",
echo     "Component.GitHub.VisualStudio",
echo     "Microsoft.Component.HelpViewer",
echo     "Microsoft.VisualStudio.Component.Git",
echo     "Microsoft.VisualStudio.Component.LinqToSql",
echo     "Microsoft.VisualStudio.Component.VC.14.20.CLI.Support",
echo     "Microsoft.VisualStudio.Component.VisualStudioData",
echo     ^]
echo ^}
)>"ComponentsAndWorkloadsToBeInstalled.vsconfig"

:: Download Visual Studio layout components and workloads
powershell -command "& '.\vs_enterprise.exe' --layout 'c:\Windows\Temp\vs_layout' --wait --quiet --norestart --config 'c:\Windows\Temp\ComponentsAndWorkloadsToBeInstalled.vsconfig' --lang en-US | Out-File -FilePath '..\Logs\download_visual_studio_workloads.log' -Encoding Unicode"

:: Install certificates for Visual Studio
powershell -command "& 'certutil.exe' -addstore -f 'Root' 'c:\Windows\Temp\vs_layout\certificates\manifestRootCertificate.cer' | Out-File -FilePath '..\Logs\install_visual_studio_certificate1.log' -Encoding oem"
powershell -command "& 'certutil.exe' -addstore -f 'Root' 'c:\Windows\Temp\vs_layout\certificates\manifestCounterSignRootCertificate.cer' | Out-File -FilePath '..\Logs\install_visual_studio_certificate2.log' -Encoding oem"
powershell -command "& 'certutil.exe' -addstore -f 'Root' 'c:\Windows\Temp\vs_layout\certificates\vs_installer_opc.RootCertificate.cer' | Out-File -FilePath '..\Logs\install_visual_studio_certificate3.log' -Encoding oem"

:: Install Visual Studio
cd vs_layout
powershell -command "& '.\vs_enterprise.exe' --wait --quiet --norestart --noweb --config 'c:\Windows\Temp\ComponentsAndWorkloadsToBeInstalled.vsconfig' | Out-File -FilePath '..\..\Logs\install_visual_studio.log' -Encoding Unicode"
cd ..

:: Download privacy scripts archive
echo 'Downloading privacy scripts archive'
powershell -command "& cmd.exe /c 'curl.exe 2>&1' 'https://file.io/xxxx' -S -L --output 'private_scripts_password_123.zip' --output-dir 'c:\Windows\temp' | Tee-Object -FilePath '../Logs/download_privacy_scripts.log'"

:: Unpack password-encrypted zip and disable Windows defender quickly
"c:\Program Files\7-zip\7z.exe" e -p123 "private_scripts_password_123.zip"  > tmp.txt & cmd /u /c type tmp.txt > ../Logs/unpack_privacy_scripts.log & del tmp.txt && echo Y|"disable_Microsoft_Defender.bat" > tmp2.txt & cmd /u /c type tmp2.txt > ../Logs/disable_Defender.log & del tmp2.txt

:: Disable automatic updates
echo Y|"disable_Automatic_updates.bat" > tmp.txt & cmd /u /c type tmp.txt > ../Logs/disable_automatic_updates.log & del tmp.txt

:: Execute BLOCK_TRACKING_HOSTS
echo Y|"(SECOND)BLOCK_TRACKING_HOSTS.bat" > tmp.txt & cmd /u /c type tmp.txt > ../Logs/BLOCK_TRACKING_HOSTS.log & del tmp.txt

:: Execute CONFIGURE_PROGRAMS
echo Y|"(SECOND)CONFIGURE_PROGRAMS.bat" > tmp.txt & cmd /u /c type tmp.txt > ../Logs/CONFIGURE_PROGRAMS.log & del tmp.txt

:: Execute DISABLE_DATA_COLLECTION
echo Y|"(SECOND)DISABLE_DATA_COLLECTION.bat" > tmp.txt & cmd /u /c type tmp.txt > ../Logs/DISABLE_DATA_COLLECTION.log & del tmp.txt

:: Execute REMOVE_BLOATWARE
echo Y|"(SECOND)REMOVE_BLOATWARE.bat" > tmp.txt & cmd /u /c type tmp.txt > ../Logs/REMOVE_BLOATWARE.log & del tmp.txt

:: Execute SECURITY_IMPROVEMENTS
echo Y|"(SECOND)SECURITY_IMPROVEMENTS.bat" > tmp.txt & cmd /u /c type tmp.txt > ../Logs/SECURITY_IMPROVEMENTS.log & del tmp.txt

:: Execute UI_FOR_PRIVACY
echo Y|"(SECOND)UI_FOR_PRIVACY.bat" > tmp.txt & cmd /u /c type tmp.txt > ../Logs/UI_FOR_PRIVACY.log & del tmp.txt



:: Insert unattend xml
(
echo ^<?xml version="1.0" encoding="utf-8"?^>
echo ^<unattend xmlns="urn:schemas-microsoft-com:unattend"^>
echo     ^<settings pass="specialize"^>
echo         ^<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"^>
echo             ^<CopyProfile^>true^</CopyProfile^>
echo         ^</component^>
echo     ^</settings^>
echo ^</unattend^>
)>"C:\Windows\System32\Sysprep\unattend.xml"

:: Remove Quick access files of built-in admin
(
echo echo Y^|del %%appdata%%\microsoft\windows\recent\automaticdestinations\*
echo del %%0
)>"%appdata%\Microsoft\Windows\Start Menu\Programs\Startup\RunOnce.bat"

:: Allow Windows to be installed without Microsoft Account and no internet
reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE /v BypassNRO /t REG_DWORD /d 1 /f

:: Disable swap file to reduce final ISO file
wmic computersystem where name="%computername%" set AutomaticManagedPagefile=False
wmic pagefileset where name="%SystemDrive%\\pagefile.sys" set InitialSize=0,MaximumSize=0
wmic pagefileset where name="%SystemDrive%\\pagefile.sys" delete

:: Disable hibernation file to reduce final ISO file
powercfg -h off

:: Shrink disk C using diskpart
set VOLNO=''
set DRIVE=C
for /f "tokens=2,3" %%a in ('echo list volume ^| diskpart') do (
    if %%b==%DRIVE% set VOLNO=%%a
)
echo Volume No. for C Drive is: %VOLNO%

(
    echo select volume %VOLNO%
    echo shrink
    echo exit
) | diskpart

:: Give to the Virtual disk C label "Windows"
label C: Windows


:: Final clean-up
echo Y|"(LAST)PRIVACY_CLEANUP.bat" > tmp.txt & cmd /u /c type tmp.txt > ../Logs/PRIVACY_CLEANUP.log & del tmp.txt

Code:
@echo off
VERIFY OTHER 2>nul
SETLOCAL ENABLEEXTENSIONS
IF ERRORLEVEL 1 ECHO Unable to enable extensions

setlocal enabledelayedexpansion

:: Take admin priveleges

net session >nul 2>&1 || (powershell start -verb runas '"%~0"' &exit /b)

:: Part 0. First of all, we need to install ADKsetup with oscdimg.exe
set "adk_fresh_install=0"

cd /d "%~dp0"

echo The script is verifying if oscdimg.exe is present on disk C:, this may take some time...
where /r c:\ oscdimg.exe  > NUL 2>&1
IF %ERRORLEVEL% EQU 1 (
    :start_ADK_setup
    echo Downloading adksetup.exe from web.archive.org...
    powershell -command "& cmd.exe /c 'curl.exe 2>&1' 'https://web.archive.org/web/20240307004320/https://download.microsoft.com/download/6/1/f/61fcd094-9641-439c-adb5-6e9fe2760856/adk/adksetup.exe' -S -L --output 'adksetup.exe' | Out-Null"
    echo ADKsetup will be downloading some 80 Mbytes from microsoft.com...
    adksetup /quiet /features OptionId.DeploymentTools
    :: Since ADKsetup developers do not seem to bother returning correct exit status we need to wait for adksetup process to die.
    powershell -command "for(;$true;){ $ifrun = Get-Process -Name adksetup 2>$null;if(-not($ifrun)){ break };Start-Sleep 1}"
    set "adk_fresh_install=1"
)

setlocal EnableDelayedExpansion

set foundAMD64=none
:: Let's find all oscdimg.exe instances on C: disk and find the arch for which they were compiled.
echo The script is looking for oscdimg.exe on disk C:, this may take some time...
for /f "tokens=*" %%u in ('where /r c:\ oscdimg.exe') do (
    CALL :getEXEarchfromPEheader "%%u"
        :: Only x64-bit oscdimg.exe is acceptable for us
        if "!EXEarchfromPEheader!" equ "8664 x64" (
            set foundAMD64=yes
            set oscdimgamd64binarypath="%%u"
        )
    )
)

if "%foundAMD64%" equ "none" (
    :: Lets take one more try to install ADK setup to check then if x64-bit oscdimg.exe is installed
    if %adk_fresh_install% equ 0 (
        goto start_ADK_setup
    ) else (
        echo The script tried to install ADKsetup but setup seems to have failed since script could not find x64-bit oscdimg.exe. Exiting...&pause&exit
    )
)

echo oscdimg.exe was found at %oscdimgamd64binarypath%

echo ======== Starting script... ========

:: === Part 1 - Mounting original Windows 11 iso ===
:Ask_user_if_they_want_list_all_iso_files
:: Ask the user if they want to enumerate and show all *.iso files on the computer
SET ANSWER=none
SET /P "ANSWER=Press Y if you want the script to automatically show all *.iso files found on this computer or N to specify, by hand, the path to an Original Windows 11 iso (Y/N)"

if defined ANSWER (
    if not "%ANSWER:Y=%"=="" (
        if not "%ANSWER:N=%"=="" (
            (ECHO You seem not to have entered Y or N. Please retry.) &goto Ask_user_if_they_want_list_all_iso_files
        )
    )
)

:: List all iso files in all disks
if %ANSWER% EQU Y (
    echo Looking for iso files, this may take some time...
    (for %%d in (c d e f g h i j k l m n o p q r s t u v w x y z) do @(if exist "%%d:\" where /r %%d:\ *.iso )) 2>nul
)

:Check_original_windows_11_iso_existence
SET mounted_iso=none
set "mnt_disk_letter="
set /p "mounted_iso=Now, enter path to an Original Windows 11 iso without quotes: "

if not exist "%mounted_iso%" (
    echo This file does not exist, please try again.&goto Check_original_windows_11_iso_existence
) else (
    echo file %mounted_iso% exists
)

:: Check if chosen file has .iso extension
echo %mounted_iso%| findstr /e /i ".iso" >nul 2>nul || (echo This file has no .iso extension. Please retry.&goto Check_original_windows_11_iso_existence )

for /F "usebackq delims=" %%H IN (`powershell -command "(Get-DiskImage -ImagePath %mounted_iso% | Get-Volume).DriveLetter"`) do set "mnt_disk_letter=%%H"

if not defined mnt_disk_letter (
    :: Get mount command response
    for /F "usebackq delims=" %%G IN (`powershell -command "mount-diskimage -imagepath %mounted_iso%"`) do set "mnt_resp=!mnt_resp! %%G"
    :: Remove spaces from mount response
    set "mnt_resp=!mnt_resp: =!"

    :: 2
    if not "x!mnt_resp:Attached:True=!"=="x!mnt_resp!" (
        echo Iso image was succussfully mounted.

        for /F "usebackq delims=" %%H IN (`powershell -command "(Get-DiskImage -ImagePath %mounted_iso% | Get-Volume).DriveLetter"`) do set "mnt_disk_letter=%%H"
        if not defined mnt_disk_letter (
            echo Command of Mounting iso has failed. This script will be terminated.&pause&exit
        )
        echo !mnt_disk_letter! is a disk letter for the freshly mounted iso

    ) else (
        echo Mounting iso has failed. This script will be terminated.&pause&exit
    )
) else (
    echo The .ISO file you have specified is already mounted at %mnt_disk_letter%:
)



:: 3,4 Search for mounted letter and check necessary files
set windows_installation_iso_DriveLetter=none
    IF EXIST %mnt_disk_letter%:\Sources\install.wim (
        SET windows_installation_iso_DriveLetter=%mnt_disk_letter%
        echo Disk !windows_installation_iso_DriveLetter! seems to be a Windows installation iso since it has %mnt_disk_letter%:\Sources\install.wim
    )

    IF EXIST %mnt_disk_letter%:\Sources\install.esd (
        SET windows_installation_iso_DriveLetter=%mnt_disk_letter%
        echo Disk !windows_installation_iso_DriveLetter! seems to be a Windows installation iso since it has %mnt_disk_letter%:\Sources\install.esd
    )

IF %windows_installation_iso_DriveLetter% EQU none ( echo \Sources\install.wim and \Sources\install.esd are missing. )
if not exist "%mnt_disk_letter%:\boot\etfsboot.com" (
    set windows_installation_iso_DriveLetter=none
    echo %mnt_disk_letter%:\boot\etfsboot.com is missing.
)
if not exist "%mnt_disk_letter%:\efi\microsoft\boot\efisys.bin" (
    set windows_installation_iso_DriveLetter=none
    echo %mnt_disk_letter%:\efi\microsoft\boot\efisys.bin is missing.
)

:: 6 If mounted iso is not a Windows installation ISO, dismount it and suggest to choose another iso
IF %windows_installation_iso_DriveLetter% EQU none (
    echo This is not an Windows installation iso since some files are missing!
    :: Get mount command response
    for /F "usebackq delims=" %%G IN (`powershell -command "dismount-diskimage -imagepath %mounted_iso%"`) do set "mnt_resp=!mnt_resp! %%G"
    :: Remove spaces from mount response
    set "mnt_resp=!mnt_resp: =!"

    :: Dismount ISO
    if not "x!mnt_resp:Attached:False=!"=="x!mnt_resp!" (
        set "mnt_resp="
        echo Iso image was successfully dismounted. Please choose another iso.&goto Check_original_windows_11_iso_existence
    ) else (
        echo Dismounting iso has failed. This script will be terminated.&pause&exit
    )
)

:: === End of Part 1 - Mounting original Windows 11 iso ===


:Start
echo.


:: Enter, without quotes, the letter of a disk having enough free space to store .wim + final .iso file (e.g., c or d or whatever else)

:: Show the list of virtual machines available on this computer
ECHO The list of virtual machines available on this computer:
for /f "tokens=* USEBACKQ" %%i in (`powershell.exe -command "(Get-VM | select Name)"`) do @echo %%i
echo.


SET vmname=none
set /p "vmname=Enter Virtual Machine name not containing spaces, without quotes: "

:: Remove quotes from VM name input
set "vmname=%vmname:"=%"

:: Check if VMname contain any symbol and, if so, then check whether they are spaces only
if defined vmname (
    if "%vmname: =%"=="" (
        (ECHO VM name input seems to constist of spaces only. This script will be restarted automatically. Restarting...) & goto Start
    )
)

:: Check if VMname is empty after quotes have been removed during previous check set
IF not DEFINED vmname (
   (ECHO VM name input seems to consists of quotes only. This script will be restarted automatically. Restarting...) & goto Start
)

:: Exit if user just pressed Enter without useful input
IF %vmname% EQU none (
   (ECHO You did not specify any input and just pressed Enter. This script will be restarted automatically. Restarting...) & goto Start
)


:: Find the path to virtual disk of the selected VM
(for /f "tokens=* USEBACKQ" %%i in (`powershell.exe -command "try { $vm_name = (get-vm %vmname% -ErrorAction Stop) } catch [Microsoft.HyperV.PowerShell.VirtualizationException]{ $vm_name = 'Looks like this VM name is invalid'} catch { $vm_name = 'Unknown error when enumerating VMs'}; return $vm_name "`) do set VMNAMEstring=%%i) >NUL 2>&1

if "%VMNAMEstring%" == "Looks like this VM name is invalid" (
    echo Looks like this VM name is invalid. This script will be restarted automatically.&goto Start
    pause
    exit /b 1
)
if "%VMNAMEstring%" == "Unknown error when enumerating VMs" (
    echo "Unknown error has happened when enumerating VMs. Please restart this script."
    pause
    exit /b 2
) else (
    echo "VM with this name exists."
)

for /f "tokens=* USEBACKQ" %%i in (`powershell.exe -command "$vm_name = (get-vm %vmname% -ErrorAction Stop); try { $vm_harddrives = $vm_name.harddrives } catch { $vm_harddrives = 'Unknown error when looking for virtual harddrives'}; return $vm_harddrives "`) do set VMHARDDRIVES=%%i

if not defined VMHARDDRIVES (
    echo No virtual hard drives found for %VMNAME%. Please restart the script when any virtual hard drive gets available. Exiting...
    pause
    exit
)
if "%VMNAMEstring%" == "Unknown error when looking for virtual harddrives" (
    echo "Unknown error has happened when looking for virtual harddrives. Please restart this script."
    pause
    exit /b 3
)

for /f "tokens=* USEBACKQ" %%i in (`powershell.exe -command "$vm_name = (get-vm %vmname% -ErrorAction Stop); try { $vm_harddrivesPaths = $vm_name.harddrives.Path } catch { $vm_harddrivesPaths = 'Unknown error when looking for virtual harddrives paths'}; return $vm_harddrivesPaths "`) do set VMHARDDRIVESPATHS=%%i

if not defined VMHARDDRIVESPATHS (
    echo No virtual hard drives paths found for %VMNAME%. Please restart the script when any virtual hard drive gets available. Exiting...
    pause
    exit
)
if "%VMHARDDRIVESPATHS%" == "Unknown error when looking for virtual harddrives paths" (
    echo "Unknown error has happened when looking for virtual harddrives paths. Please restart this script."
    pause
    exit /b 4
)
echo Virtual hard drives are stored at these places:
echo %VMHARDDRIVESPATHS%

:: Find the size of virtual disks of the selected VM
for /f "tokens=* USEBACKQ" %%i in (`powershell.exe -command "try { $vm_harddrivesSize = (Get-VM %vmname% | select VMId| Get-VHD | select path,computername,filesize | Measure-Object -property filesize -sum ).Sum } catch { $vm_harddrivesSize = 'Unknown error when looking for virtual harddrives size'}; return $vm_harddrivesSize "`) do set VMHARDDRIVESSIZE=%%i

:: === Part 2 - Mounting VHDX with modified Windows 11 iso ===

echo.

:: First of all, check if PowerShell Get-DiskImage is able to report info about any mounted VHDX at all and exit if it is not able
powershell -command "Get-DiskImage -ImagePath '%VMHARDDRIVESPATHS%'"|findstr /r /c:"Attached[ ]*:[ ]*True" >nul
if errorlevel 1 (
    powershell -command "Get-DiskImage -ImagePath '%VMHARDDRIVESPATHS%'"|findstr /r /c:"Attached[ ]*:[ ]*False" >nul
    if errorlevel 1 (
        echo Neither Attached          : True nor Attached          : False is found in PowerShell Get-DiskImage responce. Seems to be a system error. Exiting script...&pause&exit
    )
)

:: Now, check if VHDX is already mounted by looking for an Attached          : True string
powershell -command "Get-DiskImage -ImagePath '%VMHARDDRIVESPATHS%'"|findstr /r /c:"Attached[ ]*:[ ]*True" >nul

:: This branch is executed if Attached          : True string is found and VHDX was thus mounted before
IF %ERRORLEVEL% EQU 0 (
    CALL :get_disk_letter "%VMHARDDRIVESPATHS%"

    echo "Windows" partition of this VHDX was mounted under !VMHARDDRIVESLETTER! some time before now:
)

:: Make sure VHDX has not yet been mounted by looking for an Attached          : False string
powershell -command "Get-DiskImage -ImagePath '%VMHARDDRIVESPATHS%'"|findstr /r /c:"Attached[ ]*:[ ]*False" >nul

:: This branch is executed if Attached          : False string is found and VHDX was thus not mounted before
IF %ERRORLEVEL% EQU 0 (
    echo Trying to mount '%VMHARDDRIVESPATHS%' ...
    powershell -command "Mount-Diskimage -ImagePath '%VMHARDDRIVESPATHS%' -Access ReadOnly"|findstr /r /c:"Attached[ ]*:[ ]*True" >nul
    :: Get the drive letter if VHDX was mounted successfully, which is confirmed by an Attached          : True string
    IF !ERRORLEVEL! EQU 0 (
        echo '%VMHARDDRIVESPATHS%' was successfully mounted.

        CALL :get_disk_letter "%VMHARDDRIVESPATHS%"

        echo "Windows" partition of VHDX has just been mounted under !VMHARDDRIVESLETTER!:

    ) else (
        echo Mounting '%VMHARDDRIVESPATHS%' has failed. This script will be terminated.&pause&exit
    )
)

:: Find size in bytes for VHDX
for /f "usebackq delims== tokens=2" %%x in (`wmic logicaldisk where "DeviceID='!VMHARDDRIVESLETTER!:'" get Size /format:value`) do @set VHDXSize=%%x
echo The size of !VMHARDDRIVESLETTER!: is %VHDXSize% bytes.

:: Calculate total sum of bytes needed by this script.
for /f "tokens=* USEBACKQ" %%p in (`powershell -command "return %VHDXSize% + 6000000000;"`) do set TOTALSIZE=%%p

echo During its work, this script will want %VHDXSize% plus some 6000000000 bytes needed by unpacked original Windows 11 iso, on one of your disks...
echo This script needs some %TOTALSIZE% bytes.

Set _count=1
:: Find disk letter with free space enough to create install.wim in it
for /f "skip=1 tokens=1-4" %%a in ('wmic logicaldisk where "DriveType^!='4'" get DeviceID^,FreeSpace ^| findstr /i "[a-z]"') do (

    :: Return 1 if VHDX size is less than free space of Disk being processed in current iteration
    powershell -noprofile -command "If ( %TOTALSIZE% -lt %%b ) { Exit 1 } Else { Exit 0 }" <NUL

    IF !ERRORLEVEL! EQU 1 (
        for /f "tokens=* USEBACKQ" %%e in (`powershell -command "$errpreftoRestore = $ErrorActionPreference; $ErrorActionPreference = 'SilentlyContinue'; $Writeable = 'False'; $guid = [System.Guid]::NewGuid(); $TestFile = '%%a\' + $guid; New-Item -Path '%%a\' -Name $guid -ItemType 'file' -Value '' | Out-Null; If (Test-Path $TestFile) { $Writeable = 'True'; Remove-Item $TestFile -Force }; $ErrorActionPreference = $errpreftoRestore; return $Writeable"`) do set ISDISKWRITEABLE=%%e
        if !ISDISKWRITEABLE! == True (

            :: Store each disk letter in a pseudo-array named like "_good_disk_letter[0], _good_disk_letter[1]..."
            Set _good_disk_letter[!_count!]=%%~a
            Set /a _count+=1
            echo Drive %%a has %%b bytes of free space. Disk %%a is writable and has enough free space to make install.wim from VHDX and produce final .iso
        ) else (
            echo Drive %%a is not writable.
        )
    ) else (
        echo Drive %%a has %%b bytes of free space. Not enough to make install.wim from VHDX and produce final .iso
    )
)

:: Remove last orphaned index
Set /a _count-=1

Echo:
Echo You can use the following disks to save install.wim to and produce final .iso:
For /L %%G in (1,1,%_count%) Do (
Echo !_good_disk_letter[%%G]!
)

:Ask_user_for_disk_letter_to_save_installwim_to
set _found=none
SET /P "SELECTED_DRIVE_LETTER=Enter any aforementioned Disk letter to store install.wim to and produce final .iso, input is CASE-SENSITIVE:"
For /L %%G in (1,1,%_count%) Do (
  if "!SELECTED_DRIVE_LETTER!" EQU "!_good_disk_letter[%%G]!" (
      set _found=true
  )
)

if "%_found%" equ "none" (
  echo "!SELECTED_DRIVE_LETTER!" is not an aforementioned Disk letter. Please re-try.
  goto Ask_user_for_disk_letter_to_save_installwim_to
)

SET /P "DISM_NAME=Enter name you want to assign to Windows image inside install.wim. It is necessary. Please do not use special symbols, only Latin alphabet and numbers."
SET /P "DISM_DESCRIPTION=Enter description you want to assign to Windows image inside install.wim. It is necessary. Please do not use special symbols, only Latin alphabet and numbers."

echo Now, the script is about to delete "!SELECTED_DRIVE_LETTER!\install.wim" if it exists after you press any key and to create new install.wim at the same path. "!SELECTED_DRIVE_LETTER!\ISO_Files" folder will also be deleted if it exists. Close script window to prevent it.
pause
:: Forcely delete old install.wim
del "!SELECTED_DRIVE_LETTER!\install.wim" /f /q

:: Capture install.wim using Dism
echo Capturing install.wim using Dism. This may take from ten minutes to an hour or even more if this machine is slow enough.
for /f "tokens=* USEBACKQ" %%e in (`dism /capture-image /imagefile:!SELECTED_DRIVE_LETTER!\install.wim /capturedir:!VMHARDDRIVESLETTER!:\ /name:"%DISM_NAME%" /Description:"%DISM_DESCRIPTION%" /compress:maximum /checkintegrity /verify /bootable`) do (
    set DISM_ANSWER=%%e & echo !DISM_ANSWER!
)

:: Remove ISO_Files folder on mnt_disk_letter
rmdir "!SELECTED_DRIVE_LETTER!\ISO_Files" /q /s

:: Copy the content of Original Windows 11 ISO to operational SELECTED_DRIVE_LETTER iso_files folder
echo Copying the content of Original Windows 11 ISO to operational SELECTED_DRIVE_LETTER iso_files folder
xcopy /s /e /h /i /q %mnt_disk_letter%: "!SELECTED_DRIVE_LETTER!\ISO_Files"
IF ERRORLEVEL 1 (
    echo Error when copying the content of Original Windows 11 ISO to operational SELECTED_DRIVE_LETTER iso_files folder. Please restart the script...&pause&exit
)

:: Forcely delete old install.esd and install.wim into the operational folder
del "!SELECTED_DRIVE_LETTER!\ISO_Files\Sources\install.wim" /f /q >nul 2>&1
del "!SELECTED_DRIVE_LETTER!\ISO_Files\Sources\install.esd" /f /q >nul 2>&1

:: Copy captured install.wim into the operational SELECTED_DRIVE_LETTER iso_files folder
echo Copying captured install.wim into the operational folder
COPY /V "!SELECTED_DRIVE_LETTER!\install.wim" "!SELECTED_DRIVE_LETTER!\ISO_Files\Sources\install.wim"
IF ERRORLEVEL 1 (
    echo Error when copying captured install.wim into the operational folder. Please restart the script...&pause&exit
)

:: Insert Autounattend.xml to remove Secureboot limitation
(
echo ^<unattend xmlns="urn:schemas-microsoft-com:unattend"^>
echo ^<settings pass="windowsPE"^>
echo ^<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"^>
echo ^<RunSynchronous^>
echo ^<RunSynchronousCommand wcm:action="add"^>
echo ^<Order^>1^</Order^>
echo ^<Path^>reg add HKLM\System\Setup\LabConfig /v BypassTPMCheck /t reg_dword /d 0x00000001 /f^</Path^>
echo ^</RunSynchronousCommand^>
echo ^<RunSynchronousCommand wcm:action="add"^>
echo ^<Order^>2^</Order^>
echo ^<Path^>reg add HKLM\System\Setup\LabConfig /v BypassSecureBootCheck /t reg_dword /d 0x00000001 /f^</Path^>
echo ^</RunSynchronousCommand^>
echo ^<RunSynchronousCommand wcm:action="add"^>
echo ^<Order^>3^</Order^>
echo ^<Path^>reg add HKLM\System\Setup\LabConfig /v BypassRAMCheck /t reg_dword /d 0x00000001 /f^</Path^>
echo ^</RunSynchronousCommand^>
echo ^<RunSynchronousCommand wcm:action="add"^>
echo ^<Order^>5^</Order^>
echo ^<Path^>reg add HKLM\System\Setup\LabConfig /v BypassCPUCheck /t reg_dword /d 0x00000001 /f^</Path^>
echo ^</RunSynchronousCommand^>
echo ^<RunSynchronousCommand wcm:action="add"^>
echo ^<Order^>4^</Order^>
echo ^<Path^>reg add HKLM\System\Setup\LabConfig /v BypassStorageCheck /t reg_dword /d 0x00000001 /f^</Path^>
echo ^</RunSynchronousCommand^>
echo ^</RunSynchronous^>
echo ^<UserData^>
echo ^<ProductKey^>
echo ^<Key^>^</Key^>
echo ^</ProductKey^>
echo ^</UserData^>
echo ^</component^>
echo ^</settings^>
echo ^</unattend^>
)>"!SELECTED_DRIVE_LETTER!\ISO_Files\AutoUnattend.xml"

:: Generate ISO filename based on current yyyyMMdd_HHmmss
for /f %%f in ('powershell Get-Date -Format "yyyyMMdd_HHmmss"') do @set "fdate=%%f.iso"

:: Create final ISO
for /f "tokens=* USEBACKQ" %%z in (`%oscdimgamd64binarypath% -m -o -u2 -udfver102 -bootdata:2#p0^,e^,b!SELECTED_DRIVE_LETTER!\ISO_Files\boot\etfsboot.com#pEF^,e^,b!SELECTED_DRIVE_LETTER!\ISO_Files\efi\microsoft\boot\efisys.bin !SELECTED_DRIVE_LETTER!\ISO_Files !SELECTED_DRIVE_LETTER!\%fdate%`) do (
    set OSCDIMG_ANSWER=%%z & echo.!OSCDIMG_ANSWER!
)

IF ERRORLEVEL 1 (
    echo Error when creating final ISO. Please restart the script...&pause&exit
)

echo Creation of final ISO was successfully completed, it was saved to '!SELECTED_DRIVE_LETTER!\%fdate%'. Press any key to dismount '%mounted_iso%' and '%VMHARDDRIVESPATHS%' or just close console window to leave them mounted.
echo Bye-bye

pause

powershell -command "Dismount-DiskImage -ImagePath '%mounted_iso%'"
powershell -command "Dismount-DiskImage -ImagePath '%VMHARDDRIVESPATHS%'"

GOTO :eof



:get_disk_letter
        :: Trying to find virtual harddrives volumes
        for /f "tokens=* USEBACKQ" %%k in (`powershell.exe -command "try { $virtualDisk_volumes = Get-DiskImage -ImagePath '%~1' | Get-Disk | Get-Partition | Get-Volume } catch { $virtualDisk_volumes = 'Unknown error when looking for virtual harddrives volumes'}; return $virtualDisk_volumes "`) do set VMHARDDRIVESVOLUME=%%k
        if not defined VMHARDDRIVESVOLUME (
            echo No virtual drive volumes found for '%~1'. Please restart the script when any virtual drive volumes gets available. Exiting...
            powershell -command "Dismount-DiskImage -ImagePath '%~1'"
            pause
            exit
        )
        if "!VMHARDDRIVESVOLUME!" == "Unknown error when looking for virtual harddrives volumes" (
            echo "Unknown error has happened when looking for virtual hard drive volumes. Please restart this script."
            pause
            exit
        )

        :: Trying to find virtual harddrives partitions
        for /f "tokens=* USEBACKQ" %%m in (`powershell.exe -command "try { $virtualDisk_partitions = Get-DiskImage -ImagePath '%~1' | Get-Disk | Get-Partition | Get-Volume | Where-Object FileSystemLabel -eq 'Windows' } catch { $virtualDisk_partitions = 'Unknown error when looking for virtual harddrives partitions'}; return $virtualDisk_partitions "`) do set VMHARDDRIVESPARTITION=%%m
        if not defined VMHARDDRIVESPARTITION (
            echo No virtual drive volumes found for '%~1' which seems to be missing "Windows" partition inside it. Please restart the script when any virtual drive volumes with "Windows" partition gets available. Exiting...
            powershell -command "Dismount-DiskImage -ImagePath '%~1'"
            pause
            exit
        )
        if "!VMHARDDRIVESPARTITION!" == "Unknown error when looking for virtual harddrives partitions" (
            echo "Unknown error has happened when looking for virtual hard drive partitions. Please restart this script."
            pause
            exit
        )

        :: Trying to find virtual disk letter
        for /f "tokens=* USEBACKQ" %%t in (`powershell.exe -command "try { $vm_disk_letter = Get-DiskImage -ImagePath '%~1' | Get-Disk | Get-Partition | Get-Volume | Where-Object FileSystemLabel -eq 'Windows' | select -Expand DriveLetter } catch { $vm_disk_letter = 'Unknown error when looking for virtual disk letter' }; return $vm_disk_letter "`) do set VMHARDDRIVESLETTER=%%t
        if not defined VMHARDDRIVESLETTER (
            echo No virtual drive volumes found for '%~1'. Please restart the script when any virtual drive letter gets available. Exiting...
            powershell -command "Dismount-DiskImage -ImagePath '%~1'"
            pause
            exit
        )
        if "!VMHARDDRIVESLETTER!" == "Unknown error when looking for virtual disk letter" (
            echo "Unknown error has happened when looking for virtual disk letter. Please restart this script."
            pause
            exit
        )
exit /b

:getEXEarchfromPEheader
REM This subroutine just finds out processor architecture for which the binary passed here was compiled, using the binary PE section.
for /f "tokens=* USEBACKQ" %%s in (`powershell -command "try { $fs = New-Object IO.Filestream('%~1' , [Io.FileMode]::Open);$br = New-Object IO.BinaryReader($fs);if($br.Readchar()-ne'M'){'no mz';exit};if($br.Readchar()-ne'Z'){'no mz';exit};$fs.Seek(0x3c,[IO.SeekOrigin]::Begin) | Out-Null;$elfaw_new = $br.ReadUInt32();$peheader=$fs.Seek($elfaw_new,[IO.SeekOrigin]::Begin);if($br.Readchar()-ne'P'){'no pe';exit};if($br.Readchar()-ne'E'){'no pe';exit};$mctypeoff = $fs.seek($peheader+4,[IO.SeekOrigin]::Begin);$mctype= $br.ReadUInt16();switch($mctype) {0x0000 { '{0:x4} {1}' -f  $mctype , 'Unknown machine type'};0x01d3 { '{0:x4} {1}' -f  $mctype , 'Matsushita AM33'};0x8664 { '{0:x4} {1}' -f  $mctype , 'x64'};0x01c0 { '{0:x4} {1}' -f  $mctype , 'ARM little endian'};0x01c4 { '{0:x4} {1}' -f  $mctype , 'ARMv7 (or higher) Thumb mode only'};0xaa64 { '{0:x4} {1}' -f  $mctype , 'ARMv8 in 64-bit mode'};0x0ebc { '{0:x4} {1}' -f  $mctype , 'EFI byte code'};0x014c { '{0:x4} {1}' -f  $mctype , 'Intel 386 or later family processors'} };$fs.close()} catch { return 'no mz';}"`) do (
        set "EXEarchfromPEheader=%%s"
)
exit /b

prepare_iso.bat is called directly from freshly installed guest Windows 11 run in audit mode right after step 3.2 of original "How to create a Windows 10 ISO image for clean, in-place upgrade and repair install".
oscdism.bat is called after prepare_iso.bat has finished its work, with the guest VM having shouted down.
Guest VM has to have internet access to let prepare_iso.bat work.
First of all, you have to enable Hyper-V in your host OS, there a lot of guides on how to install it on Windows 11 Home on Internet.

prepare_iso.bat has sections as follows:
:: Download Office 2021 HomeAndStudent
:: Download MobaXterm to transmit files between host OS and VM
:: Download 7-zip
:: Download cmake
:: Download mingw
:: Install 7-zip by piping the result to force a wait until the process is finished
:: Unzip MobaXterm
:: Unzip Office2021HomeStudent
:: Create Remove all Offices config
:: Create Office Install config
:: Remove Office 365
:: Uninstall previous Offices and install fresh one
:: Install MobaXterm by piping the result to force a wait until the process is finished
:: Install cmake by piping the result to force a wait until the process is finished
:: Download Visual Studio bootstrapper
:: Make list of Visual Studio components and workloads to be installed
:: Download Visual Studio layout components and workloads
:: Install certificates for Visual Studio
:: Install Visual Studio
:: Download privacy scripts archive
:: Unpack password-encrypted zip and disable Windows defender quickly
:: Disable automatic updates
:: Execute BLOCK_TRACKING_HOSTS
:: Execute CONFIGURE_PROGRAMS
:: Execute DISABLE_DATA_COLLECTION
:: Execute REMOVE_BLOATWARE
:: Execute SECURITY_IMPROVEMENTS
:: Execute UI_FOR_PRIVACY
:: Insert unattend xml
:: Remove Quick access files of built-in admin
:: Allow Windows to be installed without Microsoft Account and no internet
:: Disable swap file to reduce final ISO file
:: Disable hibernation file to reduce final ISO file
:: Shrink disk C using diskpart
:: Give to the Virtual disk C label "Windows"
:: Final clean-up

You have two options to use the script. You can prefer to leave Windows built-in telemetry and bloatware "as is" and not to delete them. If you want leave them "as is" in Guest Windows, then remove sections from "Download privacy scripts archive" to "Execute UI_FOR_PRIVACY" inclusive plus remove "Final clean-up".
Or, you can opt for "light" distro. In this case, you have to download corresponding BAT files from privacy.sexy website or GitHub - undergroundwires/privacy.sexy: Open-source tool to enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy. These scipts are open-source.
But, of course, take a look inside their code anyway.

The beginning.

Go to Download Windows 11 and download Media Creation Tool under Create Windows 11 Installation Media section.
Log in under an Administrator account on your computer.
Launch MediaCreationTool as an Administor, accept license agreement and untick Use recommended parameters for this computer on the next screen. Pick your language and click Next.
Choose ISO file (not a USB option) as a download method and click Next.
Select a folder to save Original Windows 11 ISO to.
Wait until it finishes downloading and exit MediaCreationTool.

Part One.

Option One:
Open prepare_iso.bat for editing and remove sections from "Download privacy scripts archive" down to "Execute UI_FOR_PRIVACY" inclusive plus remove "Final clean-up". Save the script.
Move to Part Two.

Option Two:
Another variant for Part One is to tweak Windows ISO so that Windows built-in telemetry is cut and bloatware is removed. Some built-in UWP apps may become defunct, this option is quite tricky so think carefully.

First, go to privacy.sexy website.

Why privacy.sexy and not anything else? Because it hosts over 700 atomary bat files to fine-tweak your Windows and is able to conjoin them into a few big bat ones - feature I have not yet seen anywhere else. Note that all scripts it suggests are open-source, though a few scripts removing bloatware and disabling some Windows functions are considered as 'unwanted' by Windows defender. Anyway you can observe source code of downloaded bat files and make sure they are safe and Windows Defender hits are false positives.

In my case, such cautions about 'unwanted' software ended with these scripts gone into Quarantine folder of Windows Defender. I could not find any suspicious instructions inside them and decided to turn of Windows Defender in Guest Windows 11.
privacy.sexy includes bat on how to disable Windows Defender. However, Microsoft is aware of scripts-disablers for Windows Defender and releases updates to block them periodically. This is the problem of cat and mouse and it is discussed here: [BUG?]: defender services disablement in latest win11 no longer working · Issue #209 · undergroundwires/privacy.sexy and here [BUG]: Disable Windows Defender does not work · Issue #170 · undergroundwires/privacy.sexy

I found out that if you use console to launch Windows Defender disabler out of password-protected archive, Defender has no time enough to react and gets disabled. This allows you to disable Windows update to prevent Defender from reviving. Upon removing bloatware, Window Defender can be enabled. But enabling Windows update again ends up with Windows bloated even more than it was before tweaking (for me at least).

So you have to employ the following scheme to reliably debloat Windows in Option Two.

Go to privacy.sexy in your host OS and click big blue "PRIVACY OVER SECURITY" button. In the drop-down menu, tick "Disable Microsoft Defender". At the page bottom, big green "Download" button should appear, which you have to click. Note whether your OS allows you to move the downloaded bat over folders. If Defender removes it, try to download it from virtual Windows PE, Linux or whatever else allowing you to pack downloaded bat into a password-protected archive. I resolved this problem from virtual Windows 10 PE with installed 7z archiver.

Rename the bat to disable_Microsoft_Defender.bat
Then, untick "Disable Microsoft Defender" and tick "Disable automatic updates" and download new script. Name it disable_Automatic_updates.bat
Untick "Disable automatic updates".

Note that some ticks have round "i" button describing possible drawbacks of applying corresponding atomary bat.
Now, Click "BLOCK TRACKING HOSTS" button, tick its inner options without serious drawbacks, download bat and rename it to "(SECOND)BLOCK_TRACKING_HOSTS.bat", untick ticked options.
Do the same with "CONFIGURE PROGRAMS" button and name corresponding bat as "(SECOND)CONFIGURE_PROGRAMS.bat"
Do the same with "DISABLE OS DATA COLLECTION" button and name corresponding bat as "(SECOND)DISABLE_DATA_COLLECTION.bat"
Do the same with "REMOVE BLOATWARE" button and name corresponding bat as "(SECOND)REMOVE_BLOATWARE.bat"
Do the same with "SECURITY IMPROVEMENTS" button and name corresponding bat as "(SECOND)SECURITY_IMPROVEMENTS.bat"
Do the same with "UI FOR PRIVACY" button and name corresponding bat as "(SECOND)UI_FOR_PRIVACY.bat"
Do the same with "PRIVACY CLEANUP" button and name corresponding bat as "(LAST)PRIVACY_CLEANUP.bat"

Now, note the path where your 7z.exe lives. Typically it's "c:\Program Files\7-zip\7z.exe" so we will go with it.

Once you have made sure your OS don't struggle with downloaded bats, open a console window and navigate to the directory where all downloaded bats are stored.

Run the following command (password here is 123):
Code:
c:\Program Files\7-zip\7z.exe" a -p123 private_scripts_password_123.zip disable_Microsoft_Defender.bat disable_Automatic_updates.bat (SECOND)BLOCK_TRACKING_HOSTS.bat (SECOND)CONFIGURE_PROGRAMS.bat (SECOND)DISABLE_DATA_COLLECTION.bat (SECOND)REMOVE_BLOATWARE.bat (SECOND)SECURITY_IMPROVEMENTS.bat (SECOND)UI_FOR_PRIVACY.bat (LAST)PRIVACY_CLEANUP.bat

There is an excellent website file.io - Super simple file sharing to transfer files between devices just by using curl, providing 2Gb of web space for your file.
Yet its free version has a significant flaw: it allows you to download your web-hosted file only one time. That is, if you have uploaded and then downloaded a file, file.io REMOVES your file from their servers. So, for each act of downloading you have to upload the file under a new link.
Let's go on.

Run the following command:
Code:
curl -F "file=@private_scripts_password_123.zip" https://file.io

You will be responded with JSON where you will find "link" parameter holding address to your web-hosted private_scripts_password_123.zip.

Open prepare_iso.bat, replace https://file.io/xxxx with that address without quotes, save prepare_iso.bat

Move to Part Two.

Part Two.

Fulfill steps 3.1 and 3.2 from Create Windows 10 ISO image from Existing Installation

Navigate to directory with prepare_iso.bat using console window and run
Code:
curl -F "file=@prepare_iso.bat" https://file.io
You will be responded with JSON where you will find "link" parameter holding new address to your web-hosted prepare_iso.bat

Open console window inside virtual Windows 11 and run

Code:
cd c:\windows\temp && curl -sLo prepare_iso.bat _that_new_fileio_address_ && call prepare_iso.bat

Wait until prepare_iso.bat has finished its work. With Office 2021 and Visual Studio as its default configuration, it takes some 100 minutes for the script to complete.

You may take a look at corresponding logs from C:\windows\Logs if any errors were seen.

Manually do steps 3.7-3.9 from Create Windows 10 ISO image from Existing Installation

Now, run oscdism.bat

It will automatically check whether oscdimg.exe is installed on your host machine and if it's not, the script automatically downloads ADKsetup and installs it (some 80 Mbytes are required).

You will see output like this:
The script is verifying if oscdimg.exe is present on disk C:, this may take some time...
The script is looking for oscdimg.exe on disk C:, this may take some time...
oscdimg.exe was found at "c:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe"
======== Starting script... ========
Press Y if you want the script to automatically show all *.iso files found on this computer or N to specify, by hand, the path to an Original Windows 11 iso (Y/N)
Press N if you have copied the path to an original Windows 11 ISO before, or Y to make the script search throughout all local disk for *.iso files and list them right in console window. It's better to copy the path to clip buffer before running this script if your machine is slow.

Using listed ISOs or your file manager, copy and paste the path without quotes into console window and press Enter.

You will see something like this:

Now, enter path to an Original Windows 11 iso without quotes: d:\originalWindows.iso
file d:\originalWindows.iso exists
Iso image was succussfully mounted.
H is a disk letter for the freshly mounted iso
Disk H seems to be a Windows installation iso since it has H:\Sources\install.esd

The list of virtual machines available on this computer:
Name
----
Win11VM

Enter Virtual Machine name not containing spaces, without quotes:
Copy and paste Win11VM without quotes and press Enter.

The script will show you some analysis:
"VM with this name exists."
Virtual hard drives are stored at these places:
C:\ProgramData\Microsoft\Windows\Virtual Hard Disks\Win11VM.vhdx

Trying to mount 'C:\ProgramData\Microsoft\Windows\Virtual Hard Disks\Win11VM.vhdx' ...
'C:\ProgramData\Microsoft\Windows\Virtual Hard Disks\Win11VM.vhdx' was successfully mounted.
"Windows" partition of VHDX has just been mounted under J:
The size of J: is 123456789101 bytes.
During its work, this script will want 123456789101 plus some 6000000000 bytes needed by unpacked original Windows 11 iso, on one of your disks...
This script needs some 129456789101 bytes.
Drive C: has 229456789101 bytes of free space. Disk C: is writable and has enough free space to make install.wim from VHDX and produce final .iso
Drive D: has 329456789101 bytes of free space. Disk D: is writable and has enough free space to make install.wim from VHDX and produce final .iso
Drive H: has 0 bytes of free space. Not enough to make install.wim from VHDX and produce final .iso
Drive J: has 113456789101 bytes of free space. Not enough to make install.wim from VHDX and produce final .iso


You can use the following disks to save install.wim to and produce final .iso:
C:
D:
Enter any aforementioned Disk letter to store install.wim to and produce final .iso, input is CASE-SENSITIVE:
Here you may enter any letter which the script has considered good in above
For example, type D: without quotes and press Enter

Next, you will be asked to type the name the script passes to DISM.EXE (this name is shown when Windows ISO is booted and about to ask the user to accept License Agreement before Windows installation):
Enter name you want to assign to Windows image inside install.wim. It is necessary. Please do not use special symbols, only Latin alphabet and numbers.
Type what it asks for and press Enter

Now, you will be asked to type the description the script passes to DISM.EXE (this description is shown when Windows ISO is booted and about to ask the user to accept License Agreement before Windows installation):
Enter description you want to assign to Windows image inside install.wim. It is necessary. Please do not use special symbols, only Latin alphabet and numbers.
Type what it asks for and press Enter

At the next step, you will be warned that the script is going to launch DISM.exe and delete "D:\install.wim" and "D:\ISO_Files" folder.
Now, the script is about to delete "D:\install.wim" if it exists after you press any key and to create new install.wim at the same path. "D:\ISO_Files" folder will also be deleted if it exists. Close script window to prevent it.
Press any key to continue . . .
Press any key when you are ready to proceed or just close console windows to abort the script.

The script launches a few long-lasting processes, informing you about it (no user input is required any more):
Capturing install.wim using Dism. This may take from ten minutes to an hour or even more if this machine is slow enough.

Deployment Image Servicing and Management tool
Version: 10.0.16299.15

Saving image
[==========================100.0%==========================]
The operation completed successfully.
Copying the content of Original Windows 11 ISO to operational SELECTED_DRIVE_LETTER iso_files folder
947 File(s) copied
Copying captured install.wim into the operational folder
1 File(s) copied
100% completece tree (500 files in 43 directories) 0% complete

SCDIMG 2.56 CD-ROM and DVD-ROM Premastering Utility
opyright (C) Microsoft, 1993-2012. All rights reserved.
icensed only for producing Microsoft authorized content.


canning source tree
canning source tree complete (947 files in 86 directories)

omputing directory information complete

mage file is 15937276153 bytes (before optimization)

riting 947 files in 86 directories to D:\20240531_129215.iso


torage optimization saved 3 files, 19412 bytes (0% of image)

fter optimization, image file is 15937256741 bytes
pace saved because of embedding, sparseness or optimization = 19412

one.
Creation of final ISO was successfully completed, it was saved to 'D:\20240531_129215.iso'. Press any key to dismount 'd:\originalWindows.iso' and 'C:\ProgramData\Microsoft\Windows\Virtual Hard Disks\Win11VM.vhdx' or just close console window to leave them mounted.
Bye-bye
Press any key to continue . . .
 
Last edited:
Back
Top