Server 2016 as a desktop

There's scant documentation around the web about running Windows Server 2016 as a daily driver desktop, and probably for good reason. There's a multitude of reasons why you'd be well advised not to use a server SKU on anything except a server. You can also achieve a pretty similar experience by using the Windows 10 LTSB/LTSC.

About a month ago I was getting ready for a clean install on my machine at home, and fatigued by the mere thought of de-bloating the new Windows 10 client out of the box, decided to experiment with Server 2016 instead. I was surprised by how well things went, so I thought I would do this post outlining my steps, the caveats, as well as some pros and cons.

To pre-empt comments I'll get about wasting money on licensing, I have licenses available that I didn't pay for. I would not advocate doing this if it involves you buying a retail license.

Configuring the system

At time of writing, the current build of server that contains the desktop experience role is 1607. There's a 1709 build available, but the WIMs contain only server core. My speculation after reading Microsoft's documentation is that Desktop Experience will be available in whatever build of server is on the long-term servicing channel, while the semi-annual channel will be core only. I used the Standard SKU, as I didn't need any of the additional resource limits Enterprise offers, and I don't intend to use any server roles.

You can install the OS in any of the typical ways. I use Rufus to make bootable Windows installs on USB flash media. My system is a Kaby Lake H270 desktop board, with a 980TI. Most systems are going to be pretty safe driver wise. If you're using the vanilla image, you'll get a one page OOBE asking you to set the Administrator password. At the end you'll get dumped into a desktop and the server manager app will open.

From here you can configure the system like a workstation using PowerShell alone.

First, I created a local user account, added it to the administrators group, and then disabled the built-in Administrator account:

New-LocalUser -FullName '' -Name 'pebkac' -PasswordNeverExpires
Add-LocalGroupMember -Group 'Administrators' -Member 'pebkac'
Disable-LocalUser -Name 'Administrator'

After logging into the new account, you'll have UAC behaving per the Windows 10 defaults, so you'll have to explictly launch an elevated PowerShell to do any heavy lifting.

The Server Manager is set to automatically open at login for all users, with a Scheduled Task. You can turn that off for all users without engaging with the abhorrent UI at all:

Get-ScheduledTask -TaskName 'ServerManager' | Disable-ScheduledTask

Since processor scheduling is defaulted to Background Services in the server SKUs, I changed this to the Programs setting.

You can change this setting in the registry as follows:

Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\PriorityControl' -Name 'Win32PrioritySeparation' -Value 38

A value of 38 is equivalent to setting the Programs radio button.

Next, I activated Windows, applied Windows Updates, and then installed the most recent Nvidia driver for my video card. If you have some other hardware needing drivers that didn't get serviced by Windows Update, you can install those at this point as well.

Desktop composition effects get pretty crippled out of the box, with sensible defaults for a server:

You can change this setting in the registry as well. A value of 1 is equivalent to selecting the Adjust for best appearance radio button.

Set-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\VisualEffects' -Name 'VisualFXSetting' -Value 1

The Shutdown Event Tracker is enabled by default, annoying you with this at system shutdown:

Typically people disable this with the Local Group Policy editor, but since I'm a fan of automation, I again go straight to the registry:

New-Item -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT' -Name 'Reliability'
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Reliability' -Name 'ShutdownReasonOn' -Value 0

I personally have no qualms about pressing Ctrl+Alt+Delete at the welcome/login screen, but if you want to switch this off:

Set-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System' -Name 'DisableCAD' -Value 1

Windows 10 also enables Memory Compression by default, but to get it in Server you'll need to run:

Enable-MMAgent -mc

Now, after a reboot, Server will be behaving about as closely to a vanilla Windows 10 as you can get.

What's missing

To be frank, a whole bunch of stuff. There's nothing there I use, but your mileage may vary.

A non-exhaustive list:

  • Cortana (including start menu web search and search suggestions)
  • Windows Store
  • Almost all user facing default AppX/UWP packages. That includes Edge, Photos, Mail etc.
  • Signing in to Windows with Microsoft accounts
  • Windows Spotlight
  • Game bar, Game DVR, most of the Xbox related features.
  • System restore
  • Windows Hello (goodbye!)

It's glorious, I know.

What's different

  • Start menu search actually works, and consistently too, just like with your comrade Windows 7.
  • No preloaded OneDrive, as well as no OneDrive shell extensions clogging up your explorer windows and context menus.
  • Windows Update doesn't reboot your machine unless you want it to, and doesn't push a new build on you more often than you change your clothes.
  • The ability to turn off the Defender AV engine in the Settings app, without it switching itself back on.
  • A bunch of default tiles in the start menu (for example, mstsc, taskmgr, and eventvwr.msc are there) I left them alone because they're infinitely more suitable defaults than Twitter and Microsoft Solitaire Collection.
  • A lot less Telemetry, and the ability to opt-out more easily with the UI.
  • calc.exe, a shining beacon in the darkness. I've missed you old friend.

What about gaming?

Aside from AppX/UWP, work off the assumption it's Windows 10. DirectX 12 is there. Vulkan installs and works fine. Nvidia/AMD drivers, GeForce Experience, and Steam all work fine. I even found some older titles like Company of Heroes actually worked better, since Server is lacking the "Full screen optimizations" feature Microsoft sneaked into a recent Windows 10 build. I didn't test the EA Origin client, because I have self respect.

Performance wise, in the overwhelming majority of cases, is identical to Windows 10, or within a few FPS either way. Anyone who advocates strongly for either 2k16 or Windows 10 for the best gaming performance is just wrong, and made assumptions without doing any testing.

Quality of life improvements

  • You'll naturally install another web browser according to your preference. Internet Exploder 11 is never a valid choice.
  • Instead of the Photos app, you can restore the Windows 7 style viewer, or use an app like ImageGlass.
  • For other media, I personally like MPC-HC, but VLC is also a good and more popular choice.
  • If you don't want advanced PDF functionality, give SumatraPDF a shot as a viewer.
  • There's a slightly out of date article on Technet with some extra system services you can disable, should you feel inclined.
  • Not really specific to this article, but get Chocolatey and change your life.

Wrapping up

I'm still using Server 2016 on my workstation at time of writing, and I'm yet to encounter any dealbreakers. The system is leaner and more liberated from the bloat of the Windows 10 client, and behaves more like I would prefer out of the box, given my 20 years of experience with Windows..

Windows Server 2016 RTM in Azure

Despite the fact that there are no Windows Server 2016 RTM images up in Azure yet, after a bit of experimentation I've managed to get an RTM build (14393.206) installed and activated just fine. It seems that Microsoft enabled KMS activation around the same time as Ignite began. It's been awfully handy having the RTM up and running ahead of the official release.

Windows Update doesn't mind servicing these machines as normal. I've got no idea if Microsoft approve of this behaviour though, so use caution.

If you've managed to obtain any of the RTM ISOs, simply install the Datacenter SKU in a local Hyper-V VM, Sysprep and generalize, copy your image up to your Azure storage account, and create a VM with it. You can read the Microsoftian on how to do that here.

Once your VM is up and running in Azure, install a 2016 VL product key by running this from an elevated PowerShell:

iex “$env:windir\system32\cscript.exe $env:windir\system32\slmgr.vbs /ipk CB7KF-BWN84-R7R2Y-793K2-8XDDG”

The final step is activation, and it's dead simple. Deliver this sweet one liner to point your box at the friendly Azure KMS:

iex “$env:windir\system32\cscript.exe $env:windir\system32\slmgr.vbs /skms”




Mikrotik CHR in Azure: Part Two

A lot has changed since I first wrote about CHR in Microsoft Azure. The latest RouterOS is 6.37 and our routers are working better than ever.

To update my earlier observations:

  • CHR VMs will now gracefully Start and Stop without the risk of them ending up in an inconsistent state.
  • Recent RouterOS upgrades we've performed have completed without issue.
  • You still need to convert the Mikrotik supplied VHDX to VHD before uploading.
  • Redeploying/resizing a CHR will result in the software ID changing.
  • We get much better performance by using Standard VM SKUs as opposed to Basic

As promised, here are the basic steps to get one up and running. I'm time poor so I'm assuming you already know your way around the ARM portal and can connect to your Azure account with the PowerShell module.

Firstly, to save you the time of converting the Mikrotik VHDX, here's a VHD of 6.37.1 I prepared earlier.

You'll need a resource group, NIC, and storage account if you don't already have these. It's also a good idea to attach a public IP address to the NIC, and set up a security group allowing winbox access, if you don't have VPN access to your Azure environment, or a pre-existing jump host. Note that your new CHR will have a blank password, so be conscious of exposing it to the internet in this configuration.

Upload the VHD to your storage account using PowerShell, or the Azure Storage Explorer. This VHD will be attached to your CHR, so name it accordingly.

You can then modify the following PowerShell for VM creation:

$SubscriptionId = "12345678-1234-1234-a123-1a23b4cde56f"
$VMName = "vm-chr-test"
$ResourceGroupName = "rg-test-env"
$StorageAccountName = "sa-pebkac-test"
$LocationName = "US West"
$VMSize = "Standard F1"
$InterfaceName = "nic-chr-test"
$SourceImageUri = "https://$"

$StorageAccount = Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName
$Interface = Get-AzureRmNetworkInterface -Name $InterfaceName -ResourceGroupName $ResourceGroupName
$VirtualMachine = New-AzureRmVMConfig -VMName $VMName -VMSize $VMSize
$VirtualMachine = Add-AzureRmVMNetworkInterface -VM $VirtualMachine -Id $Interface.Id
$VirtualMachine = Set-AzureRmVMOSDisk -VM $VirtualMachine -Name $VMName -VhdUri $SourceImageUri -Caching "ReadWrite" -CreateOption "Attach" -Linux
New-AzureRmVM -ResourceGroupName $ResourceGroupName -Location $LocationName -VM $VirtualMachine -Verbose

After this, your VM will take a few minutes to create, and you'll be able to login and continue configuration.

Here's one of our busy VPN routers cruising along:


Mikrotik CHR in Microsoft Azure (ARM)

At the time of writing, Mikrotik's current CHR build is 6.35rc49.

While purported to work in a Hyper-V VM, there are no instructions I could find to get one up and running in Microsoft Azure (with ARM).

Knowing that it should be theoretically possible, given Azure utilises a hypervisor with the same origins as Hyper-V, I went down the rabbit hole.

The salient points are as follows:

  • It works! We have stable CHRs in ARM VMs supporting production workloads.
  • You must convert the Mikrotik supplied VHDX to a VHD before uploading to Azure's blob storage, as Azure doesn't support the newer format. (I installed the Hyper-V role on my Windows box, which includes a utility to do the conversion)
  • You must use the -CreateOption Attach parameter with the Set-AzureRmVMOSDisk cmdlet, otherwise you'll end up with an Azure VM object stuck in a "Provisioning" or "Creating" state. You can just use the URL for the VHD you upload to blob storage for the -VhdUri parameter.
  • The CHR VM doesn't respond to Stop or Restart requests from the Resource Manager Portal or PowerShell cmdlets. Attempting these actions can put the CHR VM into an inconsistent state.
  • You can safely delete CHR VMs and the disks are left intact in blob storage and can be re-attached to a new VM. (Would need to re-license)
  • You can restart the router from within Winbox / CLI as well as perform upgrades. (Back up your config first, sometimes the upgrades fail)
  • A lot of scenarios will require IP forwarding to be enabled on your CHR NIC. This can only be done with PowerShell, set the EnableIPForwarding property to true on a NIC object and then update with Set-AzureRmNetworkInterface. Update: IP Forwarding can now be enabled from the Azure Portal in the NIC IP Properties.

If Mikrotik are not forthcoming with some proper Azure instructions, I'll try and expand on these notes with a full set of step by step instructions.

NB: If you're at the point where you're still choosing your cloud provider, CHR is presently much more mature in AWS on EC2 instances.


Converting an OpenSUSE 11 Guest from VMWare to Hyper-V

Tasked with a V2V of an OpenSUSE 11.3 web app server, I fired up Microsoft Virtual Machine Converter and expected to be met with little resistance. Unfortunately things didn't go smoothly.

I found a few places on the internet referring to a blog post at but it looks as though the URL schema for their blog changed in a recent site redesign.

The updated URL is

Steps three and four of this post solved my problem in short order. Cheers Jeremy.

Win 10: Changing the default app association for PDF files

Microsoft have apparently modified the handling of file associations in Windows 10, adding additional layers of protection for some file types in an effort to prevent file association hijacking.

A number of clients I work with have encountered issues with Edge as the default PDF viewer, namely with printing. Unfortunately, existing GPOs to modify these default app associations appear to be broken by the additional protections. Some hasty troubleshooting revealed that when the registry changes are made, a desktop notification appears to the user informing them that "An app default was reset", and Edge is promptly reverted back to the the default app for PDF files.

It seems that Microsoft's official method to set app associations is by generating an "AppAssociations.xml" file on a reference computer, and then applying this by group policy or directly with dism.exe. This works fine for any new users created on the system, but those with existing profiles are not affected - making this pretty useless for devices already deployed.

After some searching, I managed to piece the following together from a few different articles, which is resolving the problem when applied to our 10586 clients. I have received feedback that this doesn't work on build 10240 though, probably because the ProgID for that build differs.

Windows Registry Editor Version 5.00






I found the ProgID for the current version of Edge here:
2016-02-16 17_39_06-Registry Editor

It is the first registry change above that appears to do the magic of preventing Edge from muscling back in when the other changes are made.

This is a frustrating limitation given the supposed design goals of Windows 10. File associations ought to be trivial to update by group policy, and I am sceptical of the rationale behind Microsoft's decision to cripple this functionality.

Installing the Novell Client from a SCCM Task Sequence

Last week I was asked to install the cancer that is the Novell Client (Version 2 SP4 IR1) as part of an SCCM task sequence for a Windows 10 deployment.

Assuming you've already got an install.ini and a novellclientproperties file, you will find that your install is mostly hands free, aside from a prompt asking you to install some Novell drivers, as they are not signed with a trusted cert.

Obviously I didn't want this prompt in the task sequence, so I needed to add Novell's certificate to the trusted certificates store on the machine prior to the client installation.

Microsoft distribute a utility called certmgr.exe inside the Windows SDK which can be given command line arguments to achieve this. I simply copied the utility into the installation directory of my Novell Client SCCM Package, along with the certificate file.

I then created a command line install script in the same directory, as follows:

START "Cert Install" /WAIT "%~dp0certmgr.exe" -add -c %~dp0Novell.cer -s -r localMachine trustedpublisher
START "Client Install" /WAIT "%~dp0setup.exe"

Finally, I created a "Run Command Line" task sequence step, pointing it at my install script and Novell Client package.


Now, when deploying my task sequence, I see Novell's shitty installer open and obligingly install the client with no user input required. It's not elegant, but no environment still using Novell puts any emphasis on elegance anyway.

Suppressing download dialog with Awesomium.NET

Recently I wanted to automatically trigger and extract some backup files from a web application. Their smug support guy told me this wasn't possible, so I started to throw together a little scraper to prove them wrong.

This particular web app does some funky stuff with javascript when authenticating users, so I couldn't use the HttpWebRequest or WebClient classes. Given that the built-in Web Browser control is a pile of shit, I installed the Awesomium SDK which turned out to be very pleasant to work with. You can just inject your own JS which gives you a lot of flexibility.

I ran across an issue when triggering the actual download of my backup file. A download file dialog displays by default, which is less than ideal. The documentation doesn't do a great job of explaining how to override this behavior, so here's a quick overview of what I did in VB.NET:

Pop this in with your import statements:

Imports Awesomium.Core

Add an event handler:

AddHandler WebCore.Download, AddressOf SkipDLPath

And then put something like this in your handler method:

Private Sub SkipDLPath(sender As Object, e As DownloadEventArgs)
Dim DLPath as String = "C:\Backups\"
e.Handled = True
e.SelectedFile = DLPath
End Sub

The file chooser prompt should be suppressed and Awesomium will happily start downloading the file. I monitored the progress and completion using the DownloadItem class.