Making a Sensible Windows 10 Base Image

This isn’t really museum-specific, but I figured I should document it anyway, because it might be useful, and I just built a new image based on Windows 10 1709 (“Fall Creators Update”).

My goal here is to build a base Windows image that I can deploy using Clonezilla (because I have not yet spent the time or energy to figure out how the Microsoft Deployment Toolkit works) and which contains a number of useful programs. Ideally, I would like it not to contain the crap that comes suggested on the Start menu these days — Candy Crush Saga and Minecraft do not need to be on work computers.

The first bit of this is pretty well trodden ground for me. It’s the start menu and suggested app stuff that’s new.

I’m using VirtualBox to do all of the virtual machine stuff. So, first, create a new VM. I do a 50GB hard drive, 8GB RAM, 2 CPUs, because I’ll be expanding the disk of whatever physical machine this image goes onto anyway, and if I keep it small I could theoretically install it on a machine with a small SSD. As long as it’s 50GB or larger.

Boot the new (empty) VM with the latest Windows 10 64-bit ISO. Oh, right, since we’re part of the University of Washington, we get to take advantage of the campus Microsoft license, and I’ll be working with the Enterprise edition of Windows. I have no idea if this works with other versions. I’m pretty sure at least the group policy stuff won’t work with the Home edition.

Anyway, boot with the Windows installation ISO and when you get to the first screen where it looks like you’re going to start configuring an installed system, press control-shift-F3. This will make it reboot into audit mode, which you can identify because it logs you in as Administrator and launches sysprep automatically. Hit “cancel” on sysprep, and now you can start installing software and updates.

I install Office 2016, Project 2016, and Visio 2016 first, because I’ve got their installation ISOs in the same folder as the Windows ISO.

I install a bunch of other software using the “chocolatey” package manager. First, install chocolatey, and then install whatever packages from it you want. This is my current load:

choco install -y putty googlechrome firefox 7zip notepadplusplus keepass winscp filezilla jre8 vlc adobereader pdftk ghostscript irfanview

The JRE installation may appear to stall out; if it’s been going for what seems like an inordinate amount of time, try pressing “return” a few times. I have no idea if that actually does anything or if it was just a coincidence that it started working for me after I did that.

We’ve standardized on Dell desktops and laptops, so I also like to install the driver packages for the supported models at this point. Grab the Windows 10 System CABs for each supported model from Dell and use 7zip to extract them on the host system, not the VM guest. This’ll save time and space. Once the .cab file is extracted to a folder, you can remove the .cab.

You’ll need to install the VirtualBox Guest Additions to your VM for this next bit, so do that (from the “Devices” menu in VirtualBox, select “Insert Guest Additions CD image” and run its setup application). It’ll want to reboot after installing this, so let it do that and then cancel sysprep again once it does.

In the VM’s “machine settings”, add a shared folder using the path of the folder you extracted the driver packages to. You may need to reboot again to get it to show up; I don’t remember. In any case, once you can see all the extracted drivers from within your VM, open a command prompt and cd to the folder which contains them. I’ve been installing each model one at a time, but it occurs to me that you could probably run the installation command from the folder which contains all of the models’ folders and have it work. So, either one at a time in each model’s folder or once from the containing folder, run this command to install the drivers:

for /f %i in ('dir /b /s *.inf') do pnputil.exe -i -a %i

Once all the drivers have been installed, you can run a powershell script to remove any duplicates:

$dismOut = dism /online /get-drivers
$Lines = $dismOut | select -Skip 10
$Operation = "theName"
$Drivers = @()
foreach ( $Line in $Lines ) {
    $tmp = $Line
    $txt = $($tmp.Split( ':' ))[1]
    switch ($Operation) {
        'theName' { $Name = $txt
                     $Operation = 'theFileName'
        'theFileName' { $FileName = $txt.Trim()
                         $Operation = 'theEntr'
        'theEntr' { $Entr = $txt.Trim()
                     $Operation = 'theClassName'
        'theClassName' { $ClassName = $txt.Trim()
                          $Operation = 'theVendor'
        'theVendor' { $Vendor = $txt.Trim()
                       $Operation = 'theDate'
        'theDate' { # change the date format for easy sorting
                     $tmp = $txt.split( '.' )
                     $txt = "$($tmp[2]).$($tmp[1]).$($tmp[0].Trim())"
                     $Date = $txt
                     $Operation = 'theVersion'
        'theVersion' { $Version = $txt.Trim()
                        $Operation = 'theNull'
                        $params = [ordered]@{ 'FileName' = $FileName
                                              'Vendor' = $Vendor
                                              'Date' = $Date
                                              'Name' = $Name
                                              'ClassName' = $ClassName
                                              'Version' = $Version
                                              'Entr' = $Entr
                        $obj = New-Object -TypeName PSObject -Property $params
                        $Drivers += $obj
         'theNull' { $Operation = 'theName'
Write-Host "All installed third-party  drivers"
$Drivers | sort Filename | ft
Write-Host "Different versions"
$last = ''
$NotUnique = @()
foreach ( $Dr in $($Drivers | sort Filename) ) {
    if ($Dr.FileName -eq $last  ) {  $NotUnique += $Dr  }
    $last = $Dr.FileName
$NotUnique | sort FileName | ft
Write-Host "Outdated drivers"
$list = $NotUnique | select -ExpandProperty FileName -Unique
$ToDel = @()
foreach ( $Dr in $list ) {
    Write-Host "duplicate found" -ForegroundColor Yellow
    $sel = $Drivers | where { $_.FileName -eq $Dr } | sort date -Descending | select -Skip 1
    $sel | ft
    $ToDel += $sel
Write-Host "Drivers to remove" -ForegroundColor Red
$ToDel | ft
Write-Host "Press any key to continue ..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
# removing old drivers
foreach ( $item in $ToDel ) {
    $Name = $($item.Name).Trim()
    Write-Host "deleting $Name" -ForegroundColor Yellow
    Write-Host "pnputil.exe -d $Name" -ForegroundColor Yellow
    Invoke-Expression -Command "pnputil.exe -d $Name"

Next, configure updates: go to Windows Update Settings, click on Advanced Options, “Give me updates for other Microsoft products”. Then check for updates. (Incidentally, can you really not choose which updates to apply any more, except by running your own WSUS setup? I don’t think I care for that.) Updates will probably take a couple hours and reboots to finish up.

While you’re in the update settings, you can also choose which update track you want and how long updates can be deferred. I’m choosing the “Semi-Annual Channel” branch, which is what they’re calling the “Current Branch for Business” now. I’m allowing feature updates to be deferred for a year, and quality updates for 3 days. I suppose I really should look into running my own WSUS server soon.

Up until now, this is all stuff I’ve done before. But now I’d like to strip out the crap from the Start menu, and I’m trying a new method (because the last thing I tried didn’t work).

First, I want to unprovision all of the installed apps, so that new user accounts don’t get them. This powershell command does that:

Get-AppXProvisionedPackage -Online | Remove-AppxProvisionedPackage -Online

(More information is available on the page I got that from.) If you don’t want to unprovision all of them, there’s a script here which you could modify to just remove the ones you want gone.

I don’t know if this next step is necessary, but it doesn’t seem to have hurt anything for me. It’s a powershell command which uninstalls all apps that may have been installed for the Administrator user audit mode logs you in as:

Get-AppxPackage | % {if (!($_.IsFramework -or $_.PublisherId -eq "cw5n1h2txyewy")) {$_}} | Remove-AppxPackage

Next up, I’m making some changes in the local group policy. Launch gpedit.msc and make the following changes:

  • Computer Configuration -> Administrative Templates -> Windows Components -> Cloud Content -> Turn off Microsoft consumer experiences = “Enabled”
  • Computer Configuration -> Administrative Templates -> Windows Components -> Store -> Disable all apps from Windows Store = “Enabled”
  • Computer Configuration -> Administrative Templates -> Windows Components -> Store -> Turn off the Store application = “Enabled”
  • Computer Configuration -> Administrative Templates -> Start Menu and Taskbar -> Start Layout = “Enabled”, Start Layout File = “C:\empty-layout.xml”

Then save the following file as c:\empty-layout.xml:

<LayoutModificationTemplate xmlns:defaultlayout="" xmlns:start="" Version="1" xmlns="">
  <LayoutOptions StartTileGroupCellWidth="6" />
      <defaultlayout:StartLayout GroupCellWidth="6" />

What I think this all should do is turn off all the suggested apps and ads in the Start Menu, turn off the Windows Store, and change the layout Start Menu to get rid of all the tiles on the right hand side. It makes the Start menu more like it was in Windows 7. Setting the Start Layout in GP does prevent users from modifying their start menu layout, though. I wish that there were a way to set it to empty when the user profile is created, and then allow them to make changes if they want, but there doesn’t appear to be a way to do that. And, having removed/unprovisioned all the apps, if I don’t set the layout myself, it looks pretty terrible:

So, I think these are all the changes I want to make for right now. Others I can make via group policy once the deployed computers are on the domain. So I need to create the image. Shut the VM down and take a snapshot of it, so you have something to revert to after you’ve run sysprep. Once the snapshot is taken, boot the VM and this time instead of cancelling sysprep, tell it to enter the out-of-the-box experience and generalize the system, then shut down.

When sysprep has finished running and the VM has shut down, attach the clonezilla ISO to its optical drive and boot into clonezilla. Make an image of the system.

Now you can restore that image to a new empty VM or to a physical system, and in theory it should boot up as a fresh Windows install with all the changes you made. I’m just about to test that part. I’ll update once I know what happened.

Update: It… seems to have worked?

The start menu doesn’t have any tiles, anyway, and I don’t see any of the suggestions (like “Get Office”, which made no sense on a system which already had Office installed). I’m provisionally calling this a success. Hooray!

5 thoughts on “Making a Sensible Windows 10 Base Image”

  1. Josh L.

    There is a way to empty the start menu for new users and will allow them to add to it later. It is explained here But the import command needs to be similar to Import-StartLayout -LayoutPath .\CustomStartScreenLayout.bin -MountPath C:\ instead of Import-StartLayout -LayoutPath .\CustomStartScreenLayout.bin -MountPath %SystemDrive%\

    Hope this helps.

  2. Thank you for this. I was using this and ran into an issue where the Windows update tried to install a Feature update, this does not work in Audit mode and left us with a partially stuck update process. To avoid issue, I would recommend using most recent feature release of Windows 10 (in our case this was the 1809 release last updated in January). We then proceeded to disable feature updates for a year on the image as you show above.

    Thanks again.

  3. Updates for 1809:

    The step that I wasn’t sure was necessary, that uninstalls packages installed for the admin user in audit mode? Yeah, it’s necessary, and it’s easier to just wipe everything out, like so:


    I couldn’t get sysprep to run without doing that first. Found that suggestion here, and it worked for me, so I’m going to keep using it.

Leave a Reply

Your email address will not be published. Required fields are marked *