Table of Contents

Building a Custom Windows Reference Image

Why Custom?

I've never been a fan of the vanilla out-of-box experience in Windows. Consequently, regardless of the deployment tools, I've always felt it necessary to build a custom reference image. Following is a breakdown of my perspective on Vanilla vs Custom images.

Vanilla Pros

Vanilla Cons

Custom Pros

Custom Cons

Trade-off Summary

Ultimately, the goal is to always deliver a consistent out-of-box user experience across all deployments. Going with the custom image means front-loading time and effort on customizing, to save huge amounts of time on deployments in the long run.

Overview

Here is a brief overview of the process.

  1. Create a VM to host the reference build, perform vanilla Windows setup from an ISO, and get in to Audit Mode.
  2. Perform a variety of configuration changes, cleanup, software/utility additions, and Windows Updates to establish a desirable baseline.
  3. Sysprep and capture the disk image.
  4. Take snapshots at critical points throughout the process for convenient roll-back.

Software Used

The Build (Windows 10 1809)

Creating a New Virtual Machine in Hyper-V

  1. New → Virtual Machine
  2. Specify Name and Location
    1. Example name pattern Win10Ent_1809_x64
  3. Specify Generation
    1. Select Generation 2 for 64bit UEFI operating systems, otherwise stick to Generation 1
  4. Assign Memory
    1. Startup Memory 2048
    2. [x] Use Dynamic Memory
  5. Configure Networking
    1. Connection Not Connected
  6. Connect Virtual Hard Disk
    1. Leave (x) Create a virtual hard disk selected with defaults, should be creating a 127 GB disk.
  7. Installation Options
    1. Select Install an operating system later
  8. Finish
  9. Right-Click → Settings on the new VM
  10. Processor
    1. Number of virtual processors 2
  11. SCSI Controller
    1. DVD Drive → Add
  12. Firmware
    1. Boot order, set it to DVD, Network, HDD
  13. Integration Services
    1. Enable all
  14. Checkpoints
    1. Set Checkpoint Type to Standard checkpoints
    2. Set Checkpoint File Location to a dedicated path if using non-default.
    3. Ex: D:\_hyperv\_checkpoints
  15. Smart Paging File Location
    1. Set Smart Paging file location to a dedicated path if using non-default.
    2. Ex: D:\_hyperv\_paging
  16. Close VM Settings
  17. Create a Checkpoint, rename to (timestamp) - Fresh VM

Initial Windows 10 Deployment

  1. Right-Click → Connect on the new VM to connect to its console
  2. Media → DVD Drive → Insert Disk… and mount the appropriate Windows ISO
  3. Start the VM, don't miss the Press any key… prompt or you'll miss setup and need to restart the VM
  4. Windows Setup
    1. Next → Install now
    2. If prompted, select the appropriate operating system edition you want to install, ex: Windows 10 Enterprise
    3. [x] I accept the license terms and Next
  5. Select Custom: Install Windows only (advanced)
  6. Should see Drive 0 Unallocated Space 127 GB so Next
  7. Wait for Installing Windows to complete
    1. If all goes well, the VM will restart (possibly several times) and go in to OOBE (Out of Box Experience), and if you check Settings → Firmware you'll notice the boot order is updated with bootmgfw.efi at the top.
    2. Wait for Let's start with region. Is this right?

From OOBE to Audit Mode

  1. On the Let's start with region. Is this right? screen, press CTRL+SHIFT+F3
  2. You should see Just a moment… and the VM should restart
  3. You should end up logged in as Administrator, with the Sysprep (System Preparation Tool) window open (it will come up after every restart, until we finally use it)
  4. Click Cancel to quit Sysprep
  5. Shutdown Windows so we can checkpoint the VM here
  6. Create a Checkpoint, rename to (timestamp) - Audit Mode

Disable Microsoft consumer experiences

  1. Run → gpedit.msc → Computer Configuration → Administrative Templates → Windows Components → Cloud Content
    1. Enable Turn off Microsoft consumer experiences
  2. Restart Windows

Enable the Network Adapter

  1. VM Settings → Network Adapter → Virtual switch: Hyper-V Virtual Switch
  2. Answer No to network discovery prompt
  3. Good idea to eject the Windows 10 media if you haven't already Media → DVD Drive → Eject

Fix time

  1. Start → Settings → Time & Language
  2. Time zone: Eastern Time
  3. Enable Set time automatically
  4. Enable Set time zone automatically
  5. Confirm clock is accurate

Rename Computer

  1. Run this in an elevated CMD to change the Workgroup to set this machine apart, if desired, and rename it:
    wmic computersystem where name="%computername%" call rename name="Win10Ent_1809"
    wmic computersystem where name="%computername%" call joindomainorworkgroup name="NewWorkgroup"
  2. Shutdown Windows
  3. Create a Checkpoint, rename to (timestamp) - Pre-Cleanup

Cleanup Libraries & Public Folder

  1. Windows Explorer → View → Options
    1. General tab
      1. Open File Explorer to: This PC
      2. In Privacy section, click Clear
    2. View tab
      1. [ ] Hide empty drives
      2. [ ] Hide extensions
      3. [x] Expand to open folder
      4. [x] Show all folders
      5. [x] Show libraries
    3. Click Ok
  2. Windows Explorer → Libraries
    1. Select all libraries and delete them (they'll regenerate later)
  3. Windows Explorer → This PC → C:\Users\Public
    1. Select all folders and delete them
  4. Create a Live Checkpoint (don't shutdown), rename to (timestamp) - Live - Cleanup1

Cleanup Appx Packages

Provisioned Windows 10 apps are documented here.

NOTE: All actions should be performed in an elevated PowerShell console.

# To get deployed packages across all user profiles:
Get-AppxPackage -AllUsers | select PackageFullName| sort PackageFullName
 
# To get provisioned packages which are ready to deploy:
Get-AppxProvisionedPackage -Online | select PackageName | sort PackageName
 
# To remove deployed packages via partial name matching:
Get-AppxPackage -AllUsers | ?{$_.PackageFullName -like "*name*"} | Remove-AppxPackage
 
# To remove provisioned packages via partial name matching:
Get-AppxProvisionedPackage -Online | ?{$_.PackageName -like "*name*"} | Remove-AppxProvisionedPackage -online
 
# Here is a full dump of Get-AppxPackage and Get-AppxProvisionedPackage from build 1809,
# and packages that are not to be removed are commented out:
$appx = @(
    #"1527c705-839a-4832-9118-54d4Bd6a0c89_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"c5e2524a-ea46-4f67-841f-6a9465d9d515_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"E2A4F912-2574-4A75-9BB0-0D023378592B_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"InputApp_1000.17763.1.0_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.AAD.BrokerPlugin_1000.17763.1.0_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.AccountsControl_10.0.17763.1_neutral__cw5n1h2txyewy"
    #"Microsoft.Advertising.Xaml_10.1804.2.0_x64__8wekyb3d8bbwe"
    #"Microsoft.Advertising.Xaml_10.1804.2.0_x86__8wekyb3d8bbwe"
    #"Microsoft.AsyncTextService_10.0.17763.1_neutral__8wekyb3d8bbwe"
    "Microsoft.BingWeather_4.25.12127.0_x64__8wekyb3d8bbwe"
    #"Microsoft.BioEnrollment_10.0.17763.1_neutral__cw5n1h2txyewy"
    #"Microsoft.CredDialogHost_10.0.17763.1_neutral__cw5n1h2txyewy"
    #"Microsoft.DesktopAppInstaller_1.0.22011.0_x64__8wekyb3d8bbwe"
    #"Microsoft.ECApp_10.0.17763.1_neutral__8wekyb3d8bbwe"
    "Microsoft.GetHelp_10.1706.10441.0_x64__8wekyb3d8bbwe"
    "Microsoft.Getstarted_6.13.11581.0_x64__8wekyb3d8bbwe"
    #"Microsoft.HEIFImageExtension_1.0.11792.0_x64__8wekyb3d8bbwe"
    #"Microsoft.LockApp_10.0.17763.1_neutral__cw5n1h2txyewy"
    "Microsoft.Messaging_3.43.27001.0_x64__8wekyb3d8bbwe"
    "Microsoft.Microsoft3DViewer_4.1808.15012.0_x64__8wekyb3d8bbwe"
    #"Microsoft.MicrosoftEdge_44.17763.1.0_neutral__8wekyb3d8bbwe"
    #"Microsoft.MicrosoftEdgeDevToolsClient_1000.17763.1.0_neutral_neutral_8wekyb3d8bbwe"
    "Microsoft.MicrosoftOfficeHub_17.8918.5926.0_x64__8wekyb3d8bbwe"
    "Microsoft.MicrosoftSolitaireCollection_4.1.5252.0_x86__8wekyb3d8bbwe"
    #"Microsoft.MicrosoftStickyNotes_2.0.13.0_x64__8wekyb3d8bbwe"
    "Microsoft.MixedReality.Portal_2000.18081.1242.0_x64__8wekyb3d8bbwe"
    #"Microsoft.MSPaint_4.1807.12027.0_x64__8wekyb3d8bbwe"
    #"Microsoft.NET.Native.Framework.1.6_1.6.24903.0_x64__8wekyb3d8bbwe"
    #"Microsoft.NET.Native.Framework.1.6_1.6.24903.0_x86__8wekyb3d8bbwe"
    #"Microsoft.NET.Native.Framework.1.7_1.7.25531.0_x64__8wekyb3d8bbwe"
    #"Microsoft.NET.Native.Framework.1.7_1.7.25531.0_x86__8wekyb3d8bbwe"
    #"Microsoft.NET.Native.Runtime.1.6_1.6.24903.0_x64__8wekyb3d8bbwe"
    #"Microsoft.NET.Native.Runtime.1.6_1.6.24903.0_x86__8wekyb3d8bbwe"
    #"Microsoft.NET.Native.Runtime.1.7_1.7.25531.0_x64__8wekyb3d8bbwe"
    #"Microsoft.NET.Native.Runtime.1.7_1.7.25531.0_x86__8wekyb3d8bbwe"
    "Microsoft.Office.OneNote_16001.10228.20003.0_x64__8wekyb3d8bbwe"
    "Microsoft.OneConnect_5.1807.1991.0_x64__8wekyb3d8bbwe"
    "Microsoft.People_10.1805.1361.0_x64__8wekyb3d8bbwe"
    #"Microsoft.PPIProjection_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Print3D_3.0.1521.0_x64__8wekyb3d8bbwe"
    #"Microsoft.ScreenSketch_10.1806.2112.0_x64__8wekyb3d8bbwe"
    #"Microsoft.Services.Store.Engagement_10.0.1610.0_x64__8wekyb3d8bbwe"
    #"Microsoft.Services.Store.Engagement_10.0.1610.0_x86__8wekyb3d8bbwe"
    #"Microsoft.SkypeApp_14.26.95.0_x64__kzf8qxf38zg5c"
    #"Microsoft.StorePurchaseApp_11805.1001.8.0_x64__8wekyb3d8bbwe"
    #"Microsoft.VCLibs.140.00_14.0.25426.0_x64__8wekyb3d8bbwe"
    #"Microsoft.VCLibs.140.00_14.0.25426.0_x86__8wekyb3d8bbwe"
    #"Microsoft.VP9VideoExtensions_1.0.12342.0_x64__8wekyb3d8bbwe"
    "Microsoft.Wallet_2.2.18179.0_x64__8wekyb3d8bbwe"
    #"Microsoft.WebMediaExtensions_1.0.12341.0_x64__8wekyb3d8bbwe"
    #"Microsoft.WebpImageExtension_1.0.11551.0_x64__8wekyb3d8bbwe"
    #"Microsoft.Win32WebViewHost_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.Apprep.ChxApp_1000.17763.1.0_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.AssignedAccessLockApp_1000.17763.1.0_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.CapturePicker_10.0.17763.1_neutral__cw5n1h2txyewy"
    #"Microsoft.Windows.CloudExperienceHost_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.ContentDeliveryManager_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.Cortana_1.11.5.17763_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.NarratorQuickStart_10.0.17763.1_neutral_neutral_8wekyb3d8bbwe"
    #"Microsoft.Windows.OOBENetworkCaptivePortal_10.0.17763.1_neutral__cw5n1h2txyewy"
    #"Microsoft.Windows.OOBENetworkConnectionFlow_10.0.17763.1_neutral__cw5n1h2txyewy"
    #"Microsoft.Windows.ParentalControls_1000.17763.1.0_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.PeopleExperienceHost_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.Photos_2018.18051.21218.0_x64__8wekyb3d8bbwe"
    #"Microsoft.Windows.PinningConfirmationDialog_1000.17763.1.0_neutral__cw5n1h2txyewy"
    #"Microsoft.Windows.SecHealthUI_10.0.17763.1_neutral__cw5n1h2txyewy"
    #"Microsoft.Windows.SecureAssessmentBrowser_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.ShellExperienceHost_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.Windows.XGpuEjectDialog_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"Microsoft.WindowsAlarms_10.1805.1361.0_x64__8wekyb3d8bbwe"
    #"Microsoft.WindowsCalculator_10.1805.1201.0_x64__8wekyb3d8bbwe"
    #"Microsoft.WindowsCamera_2018.425.120.0_x64__8wekyb3d8bbwe"
    "microsoft.windowscommunicationsapps_17.9330.21365.0_x64__8wekyb3d8bbwe"
    "Microsoft.WindowsFeedbackHub_1.1805.2331.0_x64__8wekyb3d8bbwe"
    "Microsoft.WindowsMaps_5.1805.1431.0_x64__8wekyb3d8bbwe"
    #"Microsoft.WindowsSoundRecorder_10.1805.1941.0_x64__8wekyb3d8bbwe"
    #"Microsoft.WindowsStore_11805.1001.49.0_x64__8wekyb3d8bbwe"
    "Microsoft.Xbox.TCUI_1.11.28003.0_x64__8wekyb3d8bbwe"
    "Microsoft.XboxApp_41.41.18001.0_x64__8wekyb3d8bbwe"
    #"Microsoft.XboxGameCallableUI_1000.17763.1.0_neutral_neutral_cw5n1h2txyewy"
    "Microsoft.XboxGameOverlay_1.32.17005.0_x64__8wekyb3d8bbwe"
    "Microsoft.XboxGamingOverlay_2.20.22001.0_x64__8wekyb3d8bbwe"
    "Microsoft.XboxIdentityProvider_12.44.20001.0_x64__8wekyb3d8bbwe"
    "Microsoft.XboxSpeechToTextOverlay_1.17.29001.0_x64__8wekyb3d8bbwe"
    "Microsoft.YourPhone_0.0.12084.0_x64__8wekyb3d8bbwe"
    "Microsoft.ZuneMusic_10.18052.20211.0_x64__8wekyb3d8bbwe"
    "Microsoft.ZuneVideo_10.18052.20211.0_x64__8wekyb3d8bbwe"
    #"Windows.CBSPreview_10.0.17763.1_neutral_neutral_cw5n1h2txyewy"
    #"windows.immersivecontrolpanel_10.0.2.1000_neutral_neutral_cw5n1h2txyewy"
    #"Windows.PrintDialog_6.2.1.0_neutral_neutral_cw5n1h2txyewy"
)
$appxpp = @(
    "Microsoft.BingWeather_4.25.12127.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.DesktopAppInstaller_2018.720.2137.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.GetHelp_10.1706.10441.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.Getstarted_6.13.11581.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.HEIFImageExtension_1.0.11792.0_x64__8wekyb3d8bbwe"
    "Microsoft.Messaging_2018.727.1430.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.Microsoft3DViewer_4.1808.15012.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.MicrosoftOfficeHub_2017.1219.520.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.MicrosoftSolitaireCollection_4.1.5252.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.MicrosoftStickyNotes_2.0.13.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.MixedReality.Portal_2000.18081.1242.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.MSPaint_4.1807.12027.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.Office.OneNote_16001.10228.20003.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.OneConnect_5.1807.1991.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.People_2018.516.2011.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.Print3D_3.0.1521.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.ScreenSketch_2018.731.48.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.SkypeApp_14.26.95.0_neutral_~_kzf8qxf38zg5c"
    #"Microsoft.StorePurchaseApp_11805.1001.813.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.VP9VideoExtensions_1.0.12342.0_x64__8wekyb3d8bbwe"
    "Microsoft.Wallet_2.2.18179.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.WebMediaExtensions_1.0.12341.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.WebpImageExtension_1.0.11551.0_x64__8wekyb3d8bbwe"
    #"Microsoft.Windows.Photos_2018.18051.21218.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.WindowsAlarms_2018.516.2059.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.WindowsCalculator_2018.501.612.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.WindowsCamera_2018.425.120.0_neutral_~_8wekyb3d8bbwe"
    "microsoft.windowscommunicationsapps_2015.9330.21365.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.WindowsFeedbackHub_2018.822.2.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.WindowsMaps_2018.523.2143.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.WindowsSoundRecorder_2018.713.2154.0_neutral_~_8wekyb3d8bbwe"
    #"Microsoft.WindowsStore_11805.1001.4913.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.Xbox.TCUI_1.11.28003.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.XboxApp_41.41.18001.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.XboxGameOverlay_1.32.17005.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.XboxGamingOverlay_2.20.22001.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.XboxIdentityProvider_12.44.20001.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.XboxSpeechToTextOverlay_1.17.29001.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.YourPhone_2018.727.2137.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.ZuneMusic_2019.18052.20211.0_neutral_~_8wekyb3d8bbwe"
    "Microsoft.ZuneVideo_2019.18052.20211.0_neutral_~_8wekyb3d8bbwe"
)
 
# This will use the above lists to remove the packages:
Get-AppxPackage -AllUsers | Where-Object { $appx -contains $PSItem.PackageFullName } | Remove-AppxPackage -AllUsers
Get-AppxProvisionedPackage -Online | Where-Object { $appxpp -contains $PSItem.PackageName } | Remove-AppxProvisionedPackage -Online -AllUsers

Cleanup OneDrive

OneDrive has a proper Uninstaller in 1809, and (as of time of this documentation) running the cleanup script alone leaves the uninstall entry orphaned in Programs and Features, along with some shortcuts in the start menu.

  1. Win+X → Apps and Features
  2. Search for OneDrive and Uninstall it, and close the Settings window
    1. It may look like it's not doing anything at first, it's probably updating to the latest OneDrive version first. Give it a few min, and then try to Uninstall again, it should clear out.
  3. Run the below cleanup script

Stole this code from here.

NOTE: This should be performed in an elevated PowerShell console.

function Takeown-File($path) {
    takeown.exe /A /F $path
    $acl = Get-Acl $path
 
    # get administraor group
    $admins = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
    $admins = $admins.Translate([System.Security.Principal.NTAccount])
 
    # add NT Authority\SYSTEM
    $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($admins, "FullControl", "None", "None", "Allow")
    $acl.AddAccessRule($rule)
 
    Set-Acl -Path $path -AclObject $acl
}
 
function Takeown-Folder($path) {
    Takeown-File $path
    foreach ($item in Get-ChildItem $path) {
        if (Test-Path $item -PathType Container) {
            Takeown-Folder $item.FullName
        } else {
            Takeown-File $item.FullName
        }
    }
}
 
Write-Output "Kill OneDrive process"
taskkill.exe /F /IM "OneDrive.exe"
taskkill.exe /F /IM "explorer.exe"
 
Write-Output "Remove OneDrive"
if (Test-Path "$env:systemroot\System32\OneDriveSetup.exe") {
    & "$env:systemroot\System32\OneDriveSetup.exe" /uninstall
}
if (Test-Path "$env:systemroot\SysWOW64\OneDriveSetup.exe") {
    & "$env:systemroot\SysWOW64\OneDriveSetup.exe" /uninstall
}
 
Write-Output "Removing OneDrive leftovers"
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$env:localappdata\Microsoft\OneDrive"
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$env:programdata\Microsoft OneDrive"
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$env:systemdrive\OneDriveTemp"
# check if directory is empty before removing:
If ((Get-ChildItem "$env:userprofile\OneDrive" -Recurse | Measure-Object).Count -eq 0) {
    Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$env:userprofile\OneDrive"
}
 
Write-Output "Remove Onedrive from explorer sidebar"
New-PSDrive -PSProvider "Registry" -Root "HKEY_CLASSES_ROOT" -Name "HKCR"
mkdir -Force "HKCR:\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}"
Set-ItemProperty "HKCR:\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" "System.IsPinnedToNameSpaceTree" 0
mkdir -Force "HKCR:\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}"
Set-ItemProperty "HKCR:\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" "System.IsPinnedToNameSpaceTree" 0
Remove-PSDrive "HKCR"
 
Write-Output "Removing run hook for new users"
reg load "hku\Default" "C:\Users\Default\NTUSER.DAT"
reg delete "HKEY_USERS\Default\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /v "OneDriveSetup" /f
reg unload "hku\Default"
 
Write-Output "Removing startmenu entry"
Remove-Item -Force -ErrorAction SilentlyContinue "$env:userprofile\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk"
 
Write-Output "Removing scheduled task"
Get-ScheduledTask -TaskPath '\' -TaskName 'OneDrive*' -ea SilentlyContinue | Unregister-ScheduledTask -Confirm:$false
 
Write-Output "Restarting explorer"
Start-Process "explorer.exe"
 
Write-Output "Waiting for explorer to complete loading"
Start-Sleep 10
 
Write-Output "Removing additional OneDrive leftovers"
foreach ($item in (Get-ChildItem "$env:WinDir\WinSxS\*onedrive*")) {
    Takeown-Folder $item.FullName
    Remove-Item -Recurse -Force $item.FullName
}

Cleanup Start/Taskbar

  1. Unpin all shortcuts from the Start Menu and Taskbar, and remove any Desktop shortcuts, except for Recycle Bin.
  2. Create a Live Checkpoint (don't shutdown), rename to (timestamp) - Live - Cleanup4

Deploy .NET 3.5

  1. Win+X → Apps and Features → Programs and Features (at the bottom)
  2. Click Turn Windows features on or off
  3. Click once on tick .NET Framework 3.5 (includes .NET 2.0 and 3.0)
  4. Click OK and then Let Windows Update download the files for you
    1. If this method fails, you'll need to use DISM:
      1. Mount the Win10 installation ISO
      2. Open elevated shell prompt and run the command:
        dism /online /enable-feature /featurename:NetFX3 /All /Source:D:\sources\sxs /LimitAccess
      3. The D:\ above is the drive letter of the mounted ISO
  5. Close all windows once requested changes are completed
  6. Create a Live Checkpoint (don't shutdown), rename to (timestamp) - Live - .NET 3.5

Deploy VC++ Runtimes

I've aggregated the runtime installers from these official and unofficial listings. I keep them on the MDT server in \_prep\_apps\VC++ Runtimes.

  1. Run all of the installers in order
    1. The VC2005 x64 installer drops msdia80.dll to the root of the biggest disk, so clean that up:
      1. Move C:\msdia80.dll to C:\Program Files\Common Files\Microsoft Shared\VC\msdia80.dll
      2. Run:
        regsvr32 "C:\Program Files\Common Files\Microsoft Shared\VC\msdia80.dll"
  2. Create a Live Checkpoint (don't shutdown), rename to (timestamp) - Live - VC++ Runtimes

Activate Windows

Customization via the Settings app, along with some other features, will not be available without activating Windows. In order to activate in Audit Mode we need to use slmgr.

Run the following at an elevated prompt:

slmgr /ipk <product key>

After confirmation pop-up, run:

slmgr /ato

After confirmation pop-up, run:

slmgr /dli

Customize User Experience

  1. Right-Click empty space on the Task Bar → Task Manager
    1. Click More details, this will become the default view now
  2. Right-Click empty space on the Task Bar → Cortana → Show Cortana icon
  3. Right-Click empty space on the Task Bar → Taskbar settings
    1. On Lock the taskbar
    2. Off Automatically hide the taskbar in desktop mode
    3. Off Automatically hide the taskbar in tablet mode
    4. Off Use small taskbar buttons
    5. On Use Peek
    6. On Replace Command Prompt with Windows PowerShell
    7. On Show badges on taskbar buttons
    8. Taskbar location on screen: Bottom
    9. Combine taskbar buttons: Always, hide labels
    10. Notification area → Select which icons appear on the taskbar
      1. On Always show all icons in the notification area
      2. Click
    11. Off Show contacts on the taskbar
  4. Personalization → Start
    1. Off Show more tiles on Start
    2. On Show app list in Start menu
    3. Off Show recently added apps
    4. Off Show most used apps
    5. Off Use Start full screen
    6. On Show recently opened items in Jump Lists on Start or the taskbar
    7. Choose which folders appear on Start
      1. On File Explorer
      2. On Settings
      3. On Documents
      4. On Downloads
      5. Off Music
      6. On Pictures
      7. Off Videos
      8. Off Network
      9. On Personal folder
      10. Click
  5. Personalization → Colors
    1. Choose your default app mode: Dark
  6. Home → Apps → Default apps
    1. Web browser: Internet Explorer
  7. Home → System → Power & sleep
    1. Screen: When plugged in, turn off after: 15 min
  8. In order to load the Start and Taskbar pinned item layout in to new user profiles, we'll need to export/import the layout.
    1. To export the layout do:
      Export-StartLayout -UseDesktopApplicationID -Path "\\MDT-Server\_prep\_startlayouts\layout.xml"
    2. Modify layout.xml according to the documentation available here in order to further customize it. For example, taskbar pinned items are not exported and need to be added manually.
    3. For reference, here is the XML for the layout I prefer, which clears all the pinned items from the Start menu and Taskbar:
      <?xml version="1.0" encoding="utf-8"?>
      <LayoutModificationTemplate
        xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification"
        xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout"
        xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout"
        xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout"
        Version="1">
        <LayoutOptions StartTileGroupCellWidth="6" />
        <DefaultLayoutOverride>
          <StartLayoutCollection>
            <defaultlayout:StartLayout GroupCellWidth="6" />
          </StartLayoutCollection>
        </DefaultLayoutOverride>
          <CustomTaskbarLayoutCollection PinListPlacement="Replace">
            <defaultlayout:TaskbarLayout>
              <taskbar:TaskbarPinList>
                <taskbar:DesktopApp DesktopApplicationLinkPath="#leaveempty" />
              </taskbar:TaskbarPinList>
            </defaultlayout:TaskbarLayout>
          </CustomTaskbarLayoutCollection>
      </LayoutModificationTemplate>
    4. To import the layout do:
      Import-Startlayout -LayoutPath "\\MDT-Server\_prep\_startlayouts\layout.xml" -MountPath $env:SystemDrive\
  9. Because we're going to be using copyprofile in our deployment answer file, we need to also import the layout in to the current Administrator profile.
    1. To do this, run:
      Copy-Item -Path "\\MDT-Server\_prep\_startlayouts\layout.xml" -Destination $env:LOCALAPPDATA\Microsoft\Windows\Shell\LayoutModification.xml
  10. Create a Live Checkpoint (don't shutdown), rename to (timestamp) - Live - CustomUE

Customize Internet Explorer

  1. Launch Internet Explorer
  2. Select Use recommended security and compatibility settings
  3. Gear → Internet Options → Advanced
    1. Click Restore advanced settings
    2. Click Apply
    3. Click Reset, [x] Delete personal settings, Reset
  4. Restart Internet Explorer
  5. Gear → Internet Options
    1. General
    2. Security
      1. Internet zone
        1. [ ] Enable Protected Mode
      2. Trusted sites zone
        1. Slide security level to low
        2. Click Sites
          1. [ ] Require server verification
          2. Add *.ufl.edu
          3. [x] Require server verification
          4. Click Close
    3. Privacy
      1. Pop-up Blocker → Settings
        1. Add *.ufl.edu
        2. Click Close
    4. Programs
      1. HTML editor Notepad
  6. Click Manage add-ons
    1. Accelerators
      1. Remove all the accelerators in the list
    2. Search Providers
      1. Click Find more search providers
        1. Add Google Search
        2. Add: EasyList Standard
        3. Close the new window
    3. Close Manage add-ons
  7. Click Manage add-ons again
    1. Search Providers
      1. Set Google as default and delete Bing
    2. Tracking Protection
      1. Confirm EasyList is shown and Enabled
    3. Close Manage add-ons
  8. Ok Internet Options
  9. Confirm any warning
  10. Close and open Internet Explorer a few times to make sure you get to clear any lingering warnings/popups about the current settings
  11. Create a Live Checkpoint (don't shutdown), rename to (timestamp) - Live - CustomIE

Configure and Run Windows Updates

  1. Start → Settings → Update & Security
  2. Change active hours → 6am - 8pm
  3. Advanced options
    1. Enable Give me updates for other Microsoft products when I update Windows.
    2. Enable Show a notification when your PC requires a restart to finish updating
    3. Click in upper left to return to Windows Update screen
  4. Check for updates
    1. Updates will automatically download and install
    2. May need to restart and retry Check for updates to confirm nothing remains pending
  5. Shutdown Windows
  6. Create a Checkpoint, rename to (timestamp) - WU

[OPTIONAL] - Cisco VPN (UF)

I keep the MSI's on the MDT server in \_prep\_apps\VPN.

  1. Deploy the following in this order:
    anyconnect-win-4.6.01103-vpn-predeploy-k9.msi
    anyconnect-win-4.6.01103-gina-predeploy-k9.msi
  2. The Gina installer will ask to restart, go ahead and restart
  3. Find the Cisco AnyConnect icon in the system tray and click it to open the connection window
    1. Type in vpn.ufl.edu and Connect
      1. This first connection will get the latest configuration profile, and will also update the client, if it's out of date, and may ask to restart
  4. Click the Cisco AnyConnect icon in the system tray again
    1. Disconnect and then switch to profile Gatorlink VPN and Connect
  5. Click the Cisco AnyConnect icon in the system tray one last time
    1. Disconnect and close the window
  6. We need to clean our account name from the cache
    1. Edit c:\users\administrator\appdata\local\cisco\cisco anyconnect secure mobility client\preferences.xml
      1. Clean out the account name from the <DefaultUser></DefaultUser> tag so it's empty
      2. Save and close the file
  7. Create a Live Checkpoint (don't shutdown), rename to (timestamp) - Live - VPN

Final Online Cleanup

De-Activate Windows

Sysprep will not clear the activation key we used earlier, so we need to de-activate manually.

Run the following at an elevated prompt:

slmgr /upk

After confirmation pop-up, run:

slmgr /cpky

PowerShell History

We don't want every new profile to have our PowerShell history, so clear it by running:

Clear-History
Remove-Item (Get-PSReadlineOption).HistorySavePath

CCleaner

I have a good track record using CCleaner to sanitize a system prior to Sysprep, so I keep a portable version on the MDT server in \_prep\_apps\CCleaner_portable.

  1. Open CCleaner and ensure the following settings
    1. Options → Settings
      1. CCleaner Home Screen Custom Clean
    2. Options → Updates
      1. Uncheck all the options
    3. Options → Advanced
      1. Uncheck all the options, except:
        1. [x] Hide warning messages
        2. [x] Save all settings to INI file
        3. [x] Skip User Account Control warning
    4. Options → Privacy
      1. Uncheck all the options
    5. Custom Clean → Windows
      1. Enable all options by double clicking the root categories, accept any warnings
      2. Uncheck Wipe Free Space under Advanced
    6. Custom Clean → Applications
      1. Enable all options by double clicking the root categories
  2. Once ready to clean up…
    1. Custom Clean → Run Cleaner
      1. [x] Do not show me this message again and Continue/Yes/Ok if any warnings come up
      2. If any errors about Edge come up, just terminate it via Task Manager and Run Cleaner again
    2. Run Cleaner a few times to confirm nothing is left over
    3. Registry → Scan for Issues → Fix selected issues…
      1. Repeat a few times to confirm nothing is left over
  3. Create a Live Checkpoint (don't shutdown), rename to (timestamp) - Live - Final Cleanup

Capture Phase 1 - Sysprep

Since our goal is to use copyprofile, we need a prepared XML answer file to feed Sysprep. Here is what that looks like:

sysprep_CopyProfile.xml
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="specialize">
        <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">
            <CopyProfile>true</CopyProfile>
        </component>
    </settings>
</unattend>

To simplify executing Sysprep correctly, every time, I prepared the following batch file:

sysprep_CopyProfile.bat
@echo off
set DRIVE=%~d0
set PATH=%~p0
@echo on
 
%windir%\system32\sysprep\sysprep.exe /generalize /oobe /shutdown /unattend:%DRIVE%%PATH%sysprep_CopyProfile.xml
  1. I keep both the XML and the batch file on the MDT server, so to kick off Sysprep I run \\MDT-Server\_prep\_sysprep\sysprep_CopyProfile.bat
  2. Sysprep should run without any issues and then shutdown Windows
  3. Create a Checkpoint, rename to (timestamp) - SYSPREP

Capture Phase 2 - Profile Cleanup

Copyprofile is going to copy a variety of content from the Administrator profile to the Default profile. Some of this content will be polluted due to our time in Audit Mode, and will corrupt Edge and possibly other apps for new profiles.

We need to clean out this content, and it is best to do this in WinPE after Sysprep, so none of the content is locked by running processes.

While we're doing this, may as well clean up some other items, if they exist.

  1. Boot the VM into WinPE, I use a mountable ISO from our MDT environment
    1. Mount the ISO and update the Firmware → Boot Order of the VM so the DVD Drive is at the top
  2. Perform the following in the shell:
    rmdir /s /q "C:\Users\Administrator\AppData\Local\Microsoft\WindowsApps\"
    rmdir /s /q "C:\Users\Administrator\AppData\Local\Microsoft\Windows\INetCache\"
    rmdir /s /q "C:\Users\Administrator\AppData\Local\Microsoft\Windows\WebCache\"
    rmdir /s /q "C:\Users\Administrator\AppData\Local\MicrosoftEdge\"
    rmdir /s /q "C:\Users\Administrator\MicrosoftEdgeBackups\"
    rmdir /s /q "C:\Users\Administrator\Favorites\"
    rmdir /s /q "C:\Users\Administrator\.cisco\"
    rmdir /s /q "C:\Users\Default\AppData\Local\Microsoft\WindowsApps\"
    rmdir /s /q "C:\Users\Default\AppData\Local\Microsoft\Windows\INetCache\"
    rmdir /s /q "C:\Users\Default\AppData\Local\Microsoft\Windows\WebCache\"
    rmdir /s /q "C:\Users\Default\AppData\Local\MicrosoftEdge\"
    rmdir /s /q "C:\Users\Default\MicrosoftEdgeBackups\"
    rmdir /s /q "C:\Users\Default\Favorites\"
    rmdir /s /q "C:\Users\Default\.cisco\"
    rmdir /s /q "C:\Users\Public\"
    del /q "C:\Users\Administrator\AppData\Local\Microsoft\Windows\WebCacheLock.dat"
    del /q "C:\Users\Default\AppData\Local\Microsoft\Windows\WebCacheLock.dat"
  3. Stay in WinPE for Capture Phase 3
  4. Create a Live Checkpoint (don't shutdown), rename to (timestamp) - Live - WinPE Profile Cleanup

Capture Phase 3 - Capture the WIM

  1. First we need to mount our capture directory:
    net use z: \\MDT-Server\_wimcap
  2. Then we need to capture the WIM:
    dism /capture-image /imagefile:z:\Win10Ent_x64_1809.wim /capturedir:c:\ /name:"Windows 10 Enterprise - Customized" /compress:maximum