function Test-CompatibleImage() { <# .SYNOPSIS Checks the version of a Windows image and determines whether or not it is compatible with a specific feature depending on a desired version .PARAMETER Name imgVersion - The version of the Windows image desiredVersion - The version to compare the image version with #> param ( [Parameter(Mandatory, position=0)] [string]$imgVersion, [Parameter(Mandatory, position=1)] [Version]$desiredVersion ) try { $version = [Version]$imgVersion return $version -ge $desiredVersion } catch { return $False } } function Remove-Features([switch] $dumpFeatures = $false, [switch] $keepDefender = $false) { <# .SYNOPSIS Removes certain features from ISO image .PARAMETER Name dumpFeatures - Dumps all features found in the ISO into a file called allfeaturesdump.txt. This file can be examined and used to decide what to remove. keepDefender - Should Defender be removed from the ISO? .EXAMPLE Remove-Features -keepDefender:$false #> try { $featlist = (Get-WindowsOptionalFeature -Path $scratchDir).FeatureName if ($dumpFeatures) { $featlist > allfeaturesdump.txt } $featlist = $featlist | Where-Object { $_ -NotLike "*Printing*" -AND $_ -NotLike "*TelnetClient*" -AND $_ -NotLike "*PowerShell*" -AND $_ -NotLike "*NetFx*" -AND $_ -NotLike "*Media*" -AND $_ -NotLike "*NFS*" } if ($keepDefender) { $featlist = $featlist | Where-Object { $_ -NotLike "*Defender*" }} foreach($feature in $featlist) { $status = "Removing feature $feature" Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$featlist.Count*100) Write-Debug "Removing feature $feature" Disable-WindowsOptionalFeature -Path "$scratchDir" -FeatureName $feature -Remove -ErrorAction SilentlyContinue -NoRestart } Write-Progress -Activity "Removing features" -Status "Ready" -Completed Write-Host "You can re-enable the disabled features at any time, using either Windows Update or the SxS folder in \Sources." } catch { Write-Host "Unable to get information about the features. MicroWin processing will continue, but features will not be processed" } } function Remove-Packages { try { $pkglist = (Get-WindowsPackage -Path "$scratchDir").PackageName $pkglist = $pkglist | Where-Object { $_ -NotLike "*ApplicationModel*" -AND $_ -NotLike "*indows-Client-LanguagePack*" -AND $_ -NotLike "*LanguageFeatures-Basic*" -AND $_ -NotLike "*Package_for_ServicingStack*" -AND $_ -NotLike "*.NET*" -AND $_ -NotLike "*Store*" -AND $_ -NotLike "*VCLibs*" -AND $_ -NotLike "*AAD.BrokerPlugin", $_ -NotLike "*LockApp*" -AND $_ -NotLike "*Notepad*" -AND $_ -NotLike "*immersivecontrolpanel*" -AND $_ -NotLike "*ContentDeliveryManager*" -AND $_ -NotLike "*PinningConfirMationDialog*" -AND $_ -NotLike "*SecHealthUI*" -AND $_ -NotLike "*SecureAssessmentBrowser*" -AND $_ -NotLike "*PrintDialog*" -AND $_ -NotLike "*AssignedAccessLockApp*" -AND $_ -NotLike "*OOBENetworkConnectionFlow*" -AND $_ -NotLike "*Apprep.ChxApp*" -AND $_ -NotLike "*CBS*" -AND $_ -NotLike "*OOBENetworkCaptivePortal*" -AND $_ -NotLike "*PeopleExperienceHost*" -AND $_ -NotLike "*ParentalControls*" -AND $_ -NotLike "*Win32WebViewHost*" -AND $_ -NotLike "*InputApp*" -AND $_ -NotLike "*AccountsControl*" -AND $_ -NotLike "*AsyncTextService*" -AND $_ -NotLike "*CapturePicker*" -AND $_ -NotLike "*CredDialogHost*" -AND $_ -NotLike "*BioEnrollMent*" -AND $_ -NotLike "*ShellExperienceHost*" -AND $_ -NotLike "*DesktopAppInstaller*" -AND $_ -NotLike "*WebMediaExtensions*" -AND $_ -NotLike "*WMIC*" -AND $_ -NotLike "*UI.XaML*" } foreach ($pkg in $pkglist) { try { $status = "Removing $pkg" Write-Progress -Activity "Removing Apps" -Status $status -PercentComplete ($counter++/$pkglist.Count*100) Remove-WindowsPackage -Path "$scratchDir" -PackageName $pkg -NoRestart -ErrorAction SilentlyContinue } catch { # This can happen if the package that is being removed is a permanent one, like FodMetadata Write-Host "Could not remove OS package $($pkg)" continue } } Write-Progress -Activity "Removing Apps" -Status "Ready" -Completed } catch { Write-Host "Unable to get information about the packages. MicroWin processing will continue, but packages will not be processed" } } function Remove-ProvisionedPackages([switch] $keepSecurity = $false) { <# .SYNOPSIS Removes AppX packages from a Windows image during MicroWin processing .PARAMETER Name keepSecurity - Boolean that determines whether to keep "Microsoft.SecHealthUI" (Windows Security) in the Windows image .EXAMPLE Remove-ProvisionedPackages -keepSecurity:$false #> $appxProvisionedPackages = Get-AppxProvisionedPackage -Path "$($scratchDir)" | Where-Object { $_.PackageName -NotLike "*AppInstaller*" -AND $_.PackageName -NotLike "*Store*" -and $_.PackageName -NotLike "*dism*" -and $_.PackageName -NotLike "*Foundation*" -and $_.PackageName -NotLike "*FodMetadata*" -and $_.PackageName -NotLike "*LanguageFeatures*" -and $_.PackageName -NotLike "*Notepad*" -and $_.PackageName -NotLike "*Printing*" -and $_.PackageName -NotLike "*Wifi*" -and $_.PackageName -NotLike "*Foundation*" } if ($?) { if ($keepSecurity) { $appxProvisionedPackages = $appxProvisionedPackages | Where-Object { $_.PackageName -NotLike "*SecHealthUI*" }} $counter = 0 foreach ($appx in $appxProvisionedPackages) { $status = "Removing Provisioned $($appx.PackageName)" Write-Progress -Activity "Removing Provisioned Apps" -Status $status -PercentComplete ($counter++/$appxProvisionedPackages.Count*100) try { Remove-AppxProvisionedPackage -Path $scratchDir -PackageName $appx.PackageName -ErrorAction SilentlyContinue } catch { Write-Host "Application $($appx.PackageName) could not be removed" continue } } Write-Progress -Activity "Removing Provisioned Apps" -Status "Ready" -Completed } else { Write-Host "Could not get Provisioned App information. Skipping process..." } } function Copy-ToUSB([string] $fileToCopy) { foreach ($volume in Get-Volume) { if ($volume -and $volume.FileSystemLabel -ieq "ventoy") { $destinationPath = "$($volume.DriveLetter):\" #Copy-Item -Path $fileToCopy -Destination $destinationPath -Force # Get the total size of the file $totalSize = (Get-Item $fileToCopy).length Copy-Item -Path $fileToCopy -Destination $destinationPath -Verbose -Force -Recurse -Container -PassThru | ForEach-Object { # Calculate the percentage completed $completed = ($_.BytesTransferred / $totalSize) * 100 # Display the progress bar Write-Progress -Activity "Copying File" -Status "Progress" -PercentComplete $completed -CurrentOperation ("{0:N2} MB / {1:N2} MB" -f ($_.BytesTransferred / 1MB), ($totalSize / 1MB)) } Write-Host "File copied to Ventoy drive $($volume.DriveLetter)" return } } Write-Host "Ventoy USB Key is not inserted" } function Remove-FileOrDirectory([string] $pathToDelete, [string] $mask = "", [switch] $Directory = $false) { if(([string]::IsNullOrEmpty($pathToDelete))) { return } if (-not (Test-Path -Path "$($pathToDelete)")) { return } $yesNo = Get-LocalizedYesNo Write-Host "[INFO] In Your local takeown expects '$($yesNo[0])' as a Yes answer." # Specify the path to the directory # $directoryPath = "$($scratchDir)\Windows\System32\LogFiles\WMI\RtBackup" # takeown /a /r /d $yesNo[0] /f "$($directoryPath)" > $null # icacls "$($directoryPath)" /q /c /t /reset > $null # icacls $directoryPath /setowner "*S-1-5-32-544" # icacls $directoryPath /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q # Remove-Item -Path $directoryPath -Recurse -Force # # Grant full control to BUILTIN\Administrators using icacls # $directoryPath = "$($scratchDir)\Windows\System32\WebThreatDefSvc" # takeown /a /r /d $yesNo[0] /f "$($directoryPath)" > $null # icacls "$($directoryPath)" /q /c /t /reset > $null # icacls $directoryPath /setowner "*S-1-5-32-544" # icacls $directoryPath /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q # Remove-Item -Path $directoryPath -Recurse -Force $itemsToDelete = [System.Collections.ArrayList]::new() if ($mask -eq "") { Write-Debug "Adding $($pathToDelete) to array." [void]$itemsToDelete.Add($pathToDelete) } else { Write-Debug "Adding $($pathToDelete) to array and mask is $($mask)" if ($Directory) { $itemsToDelete = Get-ChildItem $pathToDelete -Include $mask -Recurse -Directory } else { $itemsToDelete = Get-ChildItem $pathToDelete -Include $mask -Recurse } } foreach($itemToDelete in $itemsToDelete) { $status = "Deleting $($itemToDelete)" Write-Progress -Activity "Removing Items" -Status $status -PercentComplete ($counter++/$itemsToDelete.Count*100) if (Test-Path -Path "$($itemToDelete)" -PathType Container) { $status = "Deleting directory: $($itemToDelete)" takeown /r /d $yesNo[0] /a /f "$($itemToDelete)" icacls "$($itemToDelete)" /q /c /t /reset icacls $itemToDelete /setowner "*S-1-5-32-544" icacls $itemToDelete /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q Remove-Item -Force -Recurse "$($itemToDelete)" } elseif (Test-Path -Path "$($itemToDelete)" -PathType Leaf) { $status = "Deleting file: $($itemToDelete)" takeown /a /f "$($itemToDelete)" icacls "$($itemToDelete)" /q /c /t /reset icacls "$($itemToDelete)" /setowner "*S-1-5-32-544" icacls "$($itemToDelete)" /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q Remove-Item -Force "$($itemToDelete)" } } Write-Progress -Activity "Removing Items" -Status "Ready" -Completed } function New-Unattend { # later if we wont to remove even more bloat EU requires MS to remove everything from English(world) # Below is an example how to do it we probably should create a drop down with common locals # # # # # en-US # # en-US # en-US # en-US # en-US # # # # # # en-US # en-US # en-US # en-US # # # using here string to embedd unattend # # 1 # net user administrator /active:yes # # this section doesn't work in win10/???? # # # 0 # # # false # # $unattend = @' <#REPLACEME#> 1 CMD /C echo LAU GG>C:\Windows\LogAuditUser.txt StartMenu true false false true true true 3 1 cmd.exe /c echo 23>c:\windows\csup.txt 2 CMD /C echo GG>C:\Windows\LogOobeSystem.txt 3 powershell -ExecutionPolicy Bypass -File c:\windows\FirstStartup.ps1 '@ $specPass = @' 0 false '@ if ((Test-CompatibleImage $imgVersion $([System.Version]::new(10,0,22000,1))) -eq $false) { # Replace the placeholder text with an empty string to make it valid for Windows 10 Setup $unattend = $unattend.Replace("<#REPLACEME#>", "").Trim() } else { # Replace the placeholder text with the Specialize pass $unattend = $unattend.Replace("<#REPLACEME#>", $specPass).Trim() } $unattend | Out-File -FilePath "$env:temp\unattend.xml" -Force -Encoding utf8 } function New-CheckInstall { # using here string to embedd firstrun $checkInstall = @' @echo off if exist "C:\windows\cpu.txt" ( echo C:\windows\cpu.txt exists ) else ( echo C:\windows\cpu.txt does not exist ) if exist "C:\windows\SerialNumber.txt" ( echo C:\windows\SerialNumber.txt exists ) else ( echo C:\windows\SerialNumber.txt does not exist ) if exist "C:\unattend.xml" ( echo C:\unattend.xml exists ) else ( echo C:\unattend.xml does not exist ) if exist "C:\Windows\Setup\Scripts\SetupComplete.cmd" ( echo C:\Windows\Setup\Scripts\SetupComplete.cmd exists ) else ( echo C:\Windows\Setup\Scripts\SetupComplete.cmd does not exist ) if exist "C:\Windows\Panther\unattend.xml" ( echo C:\Windows\Panther\unattend.xml exists ) else ( echo C:\Windows\Panther\unattend.xml does not exist ) if exist "C:\Windows\System32\Sysprep\unattend.xml" ( echo C:\Windows\System32\Sysprep\unattend.xml exists ) else ( echo C:\Windows\System32\Sysprep\unattend.xml does not exist ) if exist "C:\Windows\FirstStartup.ps1" ( echo C:\Windows\FirstStartup.ps1 exists ) else ( echo C:\Windows\FirstStartup.ps1 does not exist ) if exist "C:\Windows\winutil.ps1" ( echo C:\Windows\winutil.ps1 exists ) else ( echo C:\Windows\winutil.ps1 does not exist ) if exist "C:\Windows\LogSpecialize.txt" ( echo C:\Windows\LogSpecialize.txt exists ) else ( echo C:\Windows\LogSpecialize.txt does not exist ) if exist "C:\Windows\LogAuditUser.txt" ( echo C:\Windows\LogAuditUser.txt exists ) else ( echo C:\Windows\LogAuditUser.txt does not exist ) if exist "C:\Windows\LogOobeSystem.txt" ( echo C:\Windows\LogOobeSystem.txt exists ) else ( echo C:\Windows\LogOobeSystem.txt does not exist ) if exist "c:\windows\csup.txt" ( echo c:\windows\csup.txt exists ) else ( echo c:\windows\csup.txt does not exist ) if exist "c:\windows\LogFirstRun.txt" ( echo c:\windows\LogFirstRun.txt exists ) else ( echo c:\windows\LogFirstRun.txt does not exist ) '@ $checkInstall | Out-File -FilePath "$env:temp\checkinstall.cmd" -Force -Encoding Ascii } function New-FirstRun { # using here string to embedd firstrun $firstRun = @' # Set the global error action preference to continue $ErrorActionPreference = "Continue" function Remove-RegistryValue { param ( [Parameter(Mandatory = $true)] [string]$RegistryPath, [Parameter(Mandatory = $true)] [string]$ValueName ) # Check if the registry path exists if (Test-Path -Path $RegistryPath) { $registryValue = Get-ItemProperty -Path $RegistryPath -Name $ValueName -ErrorAction SilentlyContinue # Check if the registry value exists if ($registryValue) { # Remove the registry value Remove-ItemProperty -Path $RegistryPath -Name $ValueName -Force Write-Host "Registry value '$ValueName' removed from '$RegistryPath'." } else { Write-Host "Registry value '$ValueName' not found in '$RegistryPath'." } } else { Write-Host "Registry path '$RegistryPath' not found." } } function Stop-UnnecessaryServices { $servicesToExclude = @( "AudioSrv", "AudioEndpointBuilder", "BFE", "BITS", "BrokerInfrastructure", "CDPSvc", "CDPUserSvc_dc2a4", "CoreMessagingRegistrar", "CryptSvc", "DPS", "DcomLaunch", "Dhcp", "DispBrokerDesktopSvc", "Dnscache", "DoSvc", "DusmSvc", "EventLog", "EventSystem", "FontCache", "LSM", "LanmanServer", "LanmanWorkstation", "MapsBroker", "MpsSvc", "OneSyncSvc_dc2a4", "Power", "ProfSvc", "RpcEptMapper", "RpcSs", "SCardSvr", "SENS", "SamSs", "Schedule", "SgrmBroker", "ShellHWDetection", "Spooler", "SysMain", "SystemEventsBroker", "TextInputManagementService", "Themes", "TrkWks", "UserManager", "VGAuthService", "VMTools", "WSearch", "Wcmsvc", "WinDefend", "Winmgmt", "WlanSvc", "WpnService", "WpnUserService_dc2a4", "cbdhsvc_dc2a4", "edgeupdate", "gpsvc", "iphlpsvc", "mpssvc", "nsi", "sppsvc", "tiledatamodelsvc", "vm3dservice", "webthreatdefusersvc_dc2a4", "wscsvc" ) $runningServices = Get-Service | Where-Object { $servicesToExclude -notcontains $_.Name } foreach($service in $runningServices) { Stop-Service -Name $service.Name -PassThru Set-Service $service.Name -StartupType Manual "Stopping service $($service.Name)" | Out-File -FilePath c:\windows\LogFirstRun.txt -Append -NoClobber } } "FirstStartup has worked" | Out-File -FilePath c:\windows\LogFirstRun.txt -Append -NoClobber $Theme = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize" Set-ItemProperty -Path $Theme -Name AppsUseLightTheme -Value 1 Set-ItemProperty -Path $Theme -Name SystemUsesLightTheme -Value 1 # figure this out later how to set updates to security only #Import-Module -Name PSWindowsUpdate; #Stop-Service -Name wuauserv #Set-WUSettings -MicrosoftUpdateEnabled -AutoUpdateOption 'Never' #Start-Service -Name wuauserv Stop-UnnecessaryServices $taskbarPath = "$env:AppData\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar" # Delete all files on the Taskbar Get-ChildItem -Path $taskbarPath -File | Remove-Item -Force Remove-RegistryValue -RegistryPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband" -ValueName "FavoritesRemovedChanges" Remove-RegistryValue -RegistryPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband" -ValueName "FavoritesChanges" Remove-RegistryValue -RegistryPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband" -ValueName "Favorites" # Stop-Process -Name explorer -Force $process = Get-Process -Name "explorer" Stop-Process -InputObject $process # Wait for the process to exit Wait-Process -InputObject $process Start-Sleep -Seconds 3 # Delete Edge Icon from the desktop $edgeShortcutFiles = Get-ChildItem -Path $desktopPath -Filter "*Edge*.lnk" # Check if Edge shortcuts exist on the desktop if ($edgeShortcutFiles) { foreach ($shortcutFile in $edgeShortcutFiles) { # Remove each Edge shortcut Remove-Item -Path $shortcutFile.FullName -Force Write-Host "Edge shortcut '$($shortcutFile.Name)' removed from the desktop." } } Remove-Item -Path "$env:USERPROFILE\Desktop\*.lnk" Remove-Item -Path "C:\Users\Default\Desktop\*.lnk" # ************************************************ # Create WinUtil shortcut on the desktop # $desktopPath = "$($env:USERPROFILE)\Desktop" # Specify the target PowerShell command $command = "powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'irm https://christitus.com/win | iex'" # Specify the path for the shortcut $shortcutPath = Join-Path $desktopPath 'winutil.lnk' # Create a shell object $shell = New-Object -ComObject WScript.Shell # Create a shortcut object $shortcut = $shell.CreateShortcut($shortcutPath) if (Test-Path -Path "c:\Windows\cttlogo.png") { $shortcut.IconLocation = "c:\Windows\cttlogo.png" } # Set properties of the shortcut $shortcut.TargetPath = "powershell.exe" $shortcut.Arguments = "-NoProfile -ExecutionPolicy Bypass -Command `"$command`"" # Save the shortcut $shortcut.Save() # Make the shortcut have 'Run as administrator' property on $bytes = [System.IO.File]::ReadAllBytes($shortcutPath) # Set byte value at position 0x15 in hex, or 21 in decimal, from the value 0x00 to 0x20 in hex $bytes[0x15] = $bytes[0x15] -bor 0x20 [System.IO.File]::WriteAllBytes($shortcutPath, $bytes) Write-Host "Shortcut created at: $shortcutPath" # # Done create WinUtil shortcut on the desktop # ************************************************ Start-Process explorer '@ $firstRun | Out-File -FilePath "$env:temp\FirstStartup.ps1" -Force }