2024-12-09

Windows 11 Custom Setup without 8dot3name


Outcome after Windows 11 24H2 install:

fsutil 8dot3name scan /s C:

Total affected registry keys: 40
files & directories scanned: 163804
found: 0

To get this result requires an install.wim without 8dot3 names, and timely use of

fsutil.exe behavior set disable8dot3 1
fsutil.exe 8dot3name set C: 1

Note: Some of the wide, unbroken pre-formatted lines below do not display well in this web page. To see them better, hightlight all, copy, and paste into text editor, e.g., Notepad++.


Preparation and Windows Install Steps:

Step 0: Obtain ISO and install.wim


Obtain Windows Install ISO from trusted source, e.g., Microsoft. Extract sources/install.wim from ISO with unzip program, e.g., 7Zip. From this original install.wim, extract parts if wanted, e.g., only Pro, only Pro N, Home and Pro.

Example to extract just Pro N:
dism /Export-Image /SourceImageFile:"Z:\wims\oem\install.wim" /SourceIndex:7 /DestinationImageFile:"C:\temp\pro_n\install.wim"

Step 1: Prepare wim file:


Note 0: This is the only step where stripping 8dot3name is performed.

Note 1: Removing the 8dot3 names from the wim file is necessary, else the installed files will not be free of 8dot3 names, despite other steps later.

Note 2: This example includes sideloading Windows Updates. Edit Updates_List.txt as wanted, e.g., empty for no updates.

For more info on sideloading Windows Updates, RTFM the DISM docs, and do not follow incomplete instructions from web searches.
https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/dism-operating-system-package-servicing-command-line-options?view=windows-11
https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/add-or-remove-packages-offline-using-dism?view=windows-11
Note 3: Contents of my Updates_List.txt:
C:\temp\wip\Updates\cumulative\windows11.0-kb5048667-x64_d4ad0ca69de9a02bc356757581e0e0d6960c9f93.msu
C:\temp\wip\Updates\cumulative_.net\windows11.0-kb5045934-x64-ndp481_fa9c3adfb0532eb8f4e521f4fb92a179380184c5.msu
Note: Dependency in folder C:\temp\wip\Updates\cumulative\ but not listed in Updates_List.txt:
windows11.0-kb5043080-x64_953449672073f8fb99badb4cc6d5d7849b9c83e8.msu

Updates downloaded from Windows Catalog:
https://catalog.update.microsoft.com/Search.aspx?q=24h2+x64&scol=DateComputed&sdir=desc
The following scripts are modifications of works by https://schneegans.de and https://github.com/memstechtips.

Wim-Add-Updates-and-Strip-8dot3names.ps1:
# Note: Requires elevated privileges to run.
# Note: Before first use, maybe need to call PsExec.exe/PsExec64.exe manually to accept eula.
#
# https://answers.microsoft.com/en-us/windows/forum/all/remove-multiple-indexes-with-dism/b57d2169-47a2-41f6-b667-af2c8ef44b06
#
# https://www.xda-developers.com/integrate-updates-windows-11-iso/
# https://woshub.com/add-updates-into-windows-image/
# https://woshub.com/integrate-drivers-to-windows-install-media/
#
# https://learn.microsoft.com/en-us/windows/deployment/customize-boot-image?tabs=powershell#step-12-export-boot-image-to-reduce-size
#
# https://schneegans.de/windows/no-8.3/
# https://learn.microsoft.com/de-de/archive/blogs/josebda/windows-server-2012-file-server-tip-disable-8-3-naming-and-strip-those-short-names-too
#

function Wim-Add-Updates-and-Strip-8dot3names {
    [CmdletBinding()]
    param(
        [Parameter( Mandatory )]
        [string]
        $WimFile,

        [Parameter( Mandatory )]
        [string]
        $UpdatesList,

        [Parameter( Mandatory )]
        [string]
        $UpdatesDir,

        [Parameter( Mandatory )]
        [string]
        $TempMountDirectory,

        [ValidateSet( 'Errors', 'Warnings', 'WarningsInfo' )]
        [string]
        $LogLevel = 'Errors',

        [Parameter( Mandatory )]
        [string]
        $PsExecPath,

        [string]
        $Dism = "$env:windir\system32\Dism.exe",

        [string]
        $fsutil = "$env:windir\system32\fsutil.exe",
        
        [Parameter( Mandatory )]
        [string]
        $TempWimDirectory
    );

    $ErrorActionPreference = "Stop";

    $RequiredInput = @($WimFile, $PsExecPath, $Dism, $fsutil, $UpdatesList);
    $RequiredInput.ForEach( {
        if( -not [System.IO.File]::Exists( $_ ) ) {
            throw "'$_' not found.";
        }
    } );

    $params = @{
        LogLevel = $LogLevel;
    };

    Write-Output $(Get-Date -Format u);
    dir -Path $WimFile;
    & $PsExecPath -accepteula -nobanner -s $Dism /Get-WimInfo /WimFile:$WimFile;
    & $PsExecPath -accepteula -nobanner -s $Dism /Get-WimInfo /WimFile:$WimFile /Index:1;
    if( $LASTEXITCODE ) {
        throw "exited with error code $LASTEXITCODE.";
    };
    $TempDirs = @($TempMountDirectory, $TempWimDirectory);
    $TempDirs.ForEach( {
        if (Test-Path -Path "$_") {
            throw "$_ should not already exist.";
        };
        New-Item -Path "$_" -Type Directory;
    } );
    Get-WindowsImage -ImagePath $WimFile @params | ForEach-Object -Process {
        Write-Output $(Get-Date -Format u);
        Write-Output ("Processing edition '{0}'. Mounting to '{1}'." -f $_.ImageName, $TempMountDirectory);
        Mount-WindowsImage -Path $TempMountDirectory -ImagePath $WimFile -Name $_.ImageName @params;
        Write-Output $(Get-Date -Format u);
        Write-Output "Adding updates...";
        & $PsExecPath -accepteula -nobanner -s $Dism /Image:$TempMountDirectory /Get-Packages /Format:Table;
        $UpdateFiles = Get-Content -Path $UpdatesList;
        $UpdateFiles.ForEach( {
            Write-Output $(Get-Date -Format u);
            $UpdateFile = "$UpdatesDir\$_";
            Write-Output ("Processing Update: '{0}'" -f $UpdateFile);
            & $PsExecPath -accepteula -nobanner -s $Dism /Image:$TempMountDirectory /Add-Package /PackagePath:$UpdateFile;
            if( $LASTEXITCODE ) {
                throw "exited with error code $LASTEXITCODE.";
            };
            Write-Output $(Get-Date -Format u);
            & $PsExecPath -accepteula -nobanner -s $Dism /Image:$TempMountDirectory /Get-Packages /Format:Table;
        } );
        Write-Output ("Stripping 8dot3names in '{0}'..." -f $TempMountDirectory);
        & $PsExecPath -accepteula -nobanner -s $fsutil 8dot3name strip /f /s "$TempMountDirectory";
        if( $LASTEXITCODE ) {
            throw "exited with error code $LASTEXITCODE.";
        };
        & $PsExecPath -accepteula -nobanner -s $fsutil 8dot3name scan /s "$TempMountDirectory";
        Write-Output $(Get-Date -Format u);
        Dismount-WindowsImage -Path $TempMountDirectory -Save @params;
        Write-Output $(Get-Date -Format u);
    };
    Remove-Item -LiteralPath "$TempMountDirectory" -Force;
    & $PsExecPath -accepteula -nobanner -s $Dism /Get-WimInfo /WimFile:$WimFile;
    & $PsExecPath -accepteula -nobanner -s $Dism /Get-WimInfo /WimFile:$WimFile /Index:1;
    if( $LASTEXITCODE ) {
        throw "exited with error code $LASTEXITCODE.";
    };
    
    Write-Output $(Get-Date -Format u);
    $WimFileExported = "$TempWimDirectory\$(Split-Path $WimFile -Leaf)";
    Write-Output ("Compressing '{0}' to '{1}'..." -f $WimFile, $WimFileExported);
    Export-WindowsImage -SourceImagePath $WimFile -DestinationImagePath $WimFileExported -CompressionType max -Verbose;
    Remove-Item -LiteralPath "$WimFile" -Force;
    Move-Item -LiteralPath "$WimFileExported" -Destination "$WimFile" -Force;
    Remove-Item -LiteralPath "$TempWimDirectory" -Force;

    Write-Output $(Get-Date -Format u);

    $WimDirectory = Split-Path -Path "$WimFile";
    $WimExtension = [System.IO.Path]::GetExtension($WimFile)
    Set-Location "$WimDirectory";
    $FinalWimFiles = Get-ChildItem -Path "$WimDirectory" -Name "*$WimExtension" -File;
    $FinalWimFiles.ForEach( {
        Write-Output ("Hashing '{0}'..." -f $_);
        dir -Path $_;
        $WimFileHash = Get-FileHash -Algorithm SHA256 -Path $_ | % {$_.Hash + "  " + (Resolve-Path -Path $_.Path -Relative)};
        $WimFileHashStream = [System.IO.StreamWriter]::new("$WimDirectory\$_.SHA256.txt");
        $WimFileHashStream.WriteLine($WimFileHash);
        $WimFileHashStream.Close();
    } );
    
    Write-Output $(Get-Date -Format u);
    Write-Output "FINI";
};

$WimFile="$(Get-Location)\prepare_wim\pro_n\install.wim"
$UpdatesList="$(Get-Location)\prepare_wim\Updates_List.txt"
$UpdatesDir="$(Get-Location)\prepare_wim\Updates\"
$TempMountDirectory="Z:\mnt"
$TempWimDirectory="Z:\temp\TempWim"
$PsExecPath="C:\tools\SysinternalsSuite--2024-10-14\PsExec64.exe"

Wim-Add-Updates-and-Strip-8dot3names `
-WimFile $WimFile `
-UpdatesList $UpdatesList `
-UpdatesDir $UpdatesDir `
-TempMountDirectory $TempMountDirectory `
-TempWimDirectory $TempWimDirectory `
-PsExecPath $PsExecPath `
| Tee-Object -FilePath "$(Get-Location)\prepare_wim\Wim-Add-Updates-and-Strip-8dot3names--pro_n.log" -Append

Step 2: Create ISO:

Note: The "sourceroot" contains all contents from original OEM ISO, with sources/install.wim replaced with the one created in previous step.

bootOrder.txt contents:
boot\bcd
boot\boot.sdi
boot\bootfix.bin
boot\bootsect.exe
boot\etfsboot.com
boot\memtest.exe
boot\en-us\bootsect.exe.mui
boot\fonts\chs_boot.ttf
boot\fonts\cht_boot.ttf
boot\fonts\jpn_boot.ttf
boot\fonts\kor_boot.ttf
boot\fonts\wgl4_boot.ttf
sources\boot.wim
Create-ISO.ps1:
# Note: Requires elevated privileges to run.
#
# https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/oscdimg-command-line-options?view=windows-11
# https://github.com/memstechtips/WIMUtil/blob/main/src/WIMUtil.ps1

function Create-ISO {
    [CmdletBinding()]
    param(
        [Parameter( Mandatory )]
        [string]
        $PsExecPath,
        
        [Parameter( Mandatory )]
        [string]
        $oscdimg,
        
        [Parameter( Mandatory )]
        [string]
        $isoVolumeLabel,
        
        [Parameter( Mandatory )]
        [string]
        $BootOrderFile,
        
        [Parameter( Mandatory )]
        [string]
        $etfsboot,
        
        [Parameter( Mandatory )]
        [string]
        $efisys,
        
        [Parameter( Mandatory )]
        [string]
        $sourceroot,
        
        [Parameter( Mandatory )]
        [string]
        $targetfile
    );
    
    $ErrorActionPreference = "Stop";

    $RequiredInput = @($PsExecPath, $oscdimg, $BootOrderFile, $etfsboot, $efisys);
    $RequiredInput.ForEach( {
        if( -not [System.IO.File]::Exists( "$_" ) ) {
            throw "'$_' not found.";
        }
    } );
    
    Write-Output $(Get-Date -Format u);

    $targetroot = Split-Path -Path "$targetfile";
    if (-not(Test-Path $targetroot -PathType Container)) {
        New-Item -path $targetroot -ItemType Directory;
    };
    # Set-Location "$targetroot";
    
    & $PsExecPath -accepteula -nobanner -s "$oscdimg" -m -o -g -h -u2 -udfver102 -l"$isoVolumeLabel" -yo"$BootOrderFile" -bootdata:2#p0,e,b"$etfsboot"#pEF,e,b"$efisys" "$sourceroot" "$targetfile";
    
    dir -Path "$targetfile";
    $isoFileHash = Get-FileHash -Algorithm SHA256 -Path "$targetfile" | % {$_.Hash + "  " + (Resolve-Path -Path $_.Path -Relative)};
    $isoFileHashStream = [System.IO.StreamWriter]::new("$targetfile.SHA256.txt");
    $isoFileHashStream.WriteLine($isoFileHash);
    $isoFileHashStream.Close();
    
    Write-Output $(Get-Date -Format u);
    Write-Output "FINI";
};

$sourceroot = "$(Get-Location)\create_iso\oem_iso";
$targetfile = "Z:\temp\created_iso\Win11PN_24H2_20241214_en-us_x64.iso";
$PsExecPath = "C:\tools\SysinternalsSuite--2024-10-14\PsExec64.exe";
$oscdimg = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe";
$isoVolumeLabel = "W11PN_2605";
$BootOrderFile = "$(Get-Location)\create_iso\bootOrder.txt";
$etfsboot = "$sourceroot\boot\etfsboot.com";
$efisys = "$sourceroot\efi\microsoft\boot\efisys.bin";

Create-ISO `
-sourceroot $sourceroot `
-targetfile $targetfile `
-PsExecPath $PsExecPath `
-oscdimg $oscdimg `
-isoVolumeLabel $isoVolumeLabel `
-BootOrderFile $BootOrderFile `
-etfsboot $etfsboot `
-efisys $efisys `
| Tee-Object -FilePath "$(Get-Location)\create_iso\Create-ISO--pro_n.log" -Append
Note: Keep the length of the isoVolumeLabel to just 11 characters, if preferred.


Step 3: Prepare notautounattend.xml


Generate schneegans.de notautounattend.xml with at least these two commands in the section, "Scripts to run in the system context, before user accounts are created":

fsutil.exe 8dot3name set C: 1
fsutil.exe behavior set disable8dot3 1

The first command might not be necessary, but it does not hurt, the second command is necessary.

Note: USB device with notautounattend.xml attached and automatically mounted as C:. After reboot, Windows Setup will re-label volumes.

Step 4: Windows Install:


Note: This does not work in full unattend mode with autounattend.xml. The "Previous Version of Setup" is required.

At Screen: Select setup option: Select "Previous Version of Setup".

At Screen: Windows Setup > Language, Local, Input...

Either of these work, depending on other needs:
X:\sources\setup.exe /Unattend:C:\notautounattend.xml
Or:
X:\sources\setup.exe /NoReboot /Unattend:C:\notautounattend.xml

If using /NoReboot:

Back at Screen: Windows Setup > Language, Local, Input...

X:\Windows\System32\wpeutil.exe reboot

After setup reboot finishes, first boot and first logon scripts run.

Confirmed 8dot3 disabled:

fsutil 8dot3name query C:
fsutil 8dot3name scan /s C:

Total affected registry keys: 40
files & directories scanned: 163804
found: 0