Peter Hinchley

Learning in Public

✪ Disable Microsoft Edge First-Run Welcome Page for New Users

It sounds simple. You want to prevent the "first-run" welcome page from appearing for new users when they open Microsoft Edge on Windows 10. Unfortunately, I couldn't find a group policy setting, or any other simple registry hack, that would to do the trick.

After digging about with Process Monitor, I noticed that on start, Edge looks for a registry value named IE10TourNoShow. This value is stored in the registry under:

HKEY_CURRENT_USER\SOFTWARE\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Storage\microsoft.microsoftedge_8wekyb3d8bbwe\MicrosoftEdge\Main

The most common method for setting a specific registry value for new users is to predefine the value in C:\Users\Default\ntuser.dat. However, the SOFTWARE\Classes key is actually a mount point for a separate registry hive that is not based on ntuser.dat. This second hive is created dynamically at first logon; it is not based on a pre-existing template. Note: The hive is also mounted under HKEY_USERS, where it is named after the user's SID, with a _CLASSES suffix. It is stored as %LOCALAPPDATA%\Microsoft\Windows\UsrClass.dat.

As we cannot predefine the required registry value for new users, we will need to dynamically configure it during first logon. Perhaps the easiest way of doing so would be to trigger the execution of a script via the RunOnce registry key (which can be pre-staged via ntuser.dat). However, it's not uncommon for this method to be disabled (as it is sometimes used as a vector for running malware).

An alternate method is to apply the change using Windows Installer via Active Setup. For this method to work, we first need to create a custom MSI.

The following PowerShell code will create a minimalist MSI which we will subsequently extend to configure Edge using Active Setup. I've isolated the code, as it is useful on its own for creating an installer that can be used as the starting point for a wide range of "setup" activities. Note: The code has only been tested on Windows 10.

function guid() {
  return ("{" + ([guid]::NewGuid()).guid + "}").ToUpper()
}

function query($sql) {
  $view = $db.OpenView($sql)
  $view.Execute()
  $view.Close()
}

function ies($a, $c, $s) {
  query "INSERT INTO InstallExecuteSequence (Action, Condition, Sequence) VALUES ('$a', '$c', '$s')"
}

$name    = "empty.msi"
$author  = "Peter Hinchley"
$title   = "Empty"
$version = "1.0"

$msi  = New-Object -ComObject WindowsInstaller.Installer
$db   = $msi.OpenDatabase($name, 3)

query "CREATE TABLE Component (Component char(72) NOT NULL, ComponentId char(38), Directory_ char(72) NOT NULL, Attributes short NOT NULL, Condition char(255), KeyPath char(72) PRIMARY KEY Component)"
query "CREATE TABLE CustomAction (Action char(72) NOT NULL, Type short NOT NULL, Source char(72), Target char(255), ExtendedType long PRIMARY KEY Action)"
query "CREATE TABLE Directory (Directory char(72) NOT NULL, Directory_Parent char(72), DefaultDir char(255) NOT NULL LOCALIZABLE PRIMARY KEY Directory)"
query "CREATE TABLE Feature (Feature char(38) NOT NULL, Feature_Parent char(38), Title char(64) LOCALIZABLE, Description char(255) LOCALIZABLE, Display short, Level short NOT NULL, Directory_ char(72), Attributes short NOT NULL PRIMARY KEY Feature)"
query "CREATE TABLE FeatureComponents (Feature_ char(38) NOT NULL, Component_ char(72) NOT NULL PRIMARY KEY Feature_, Component_)"
query "CREATE TABLE InstallExecuteSequence (Action char(72) NOT NULL, Condition char(255), Sequence short PRIMARY KEY Action)"
query "CREATE TABLE Media (DiskId short NOT NULL, LastSequence short NOT NULL, DiskPrompt char(64) LOCALIZABLE, Cabinet char(255), VolumeLabel char(32), Source char(72) PRIMARY KEY DiskId)"
query "CREATE TABLE Property (Property char(72) NOT NULL, Value char NOT NULL LOCALIZABLE PRIMARY KEY Property)"
query "CREATE TABLE Registry (Registry char(72) NOT NULL, Root short NOT NULL, ``Key`` char(255) NOT NULL LOCALIZABLE, Name char(255) LOCALIZABLE, Value char LOCALIZABLE, Component_ char(72) NOT NULL PRIMARY KEY Registry)"

query "INSERT INTO Component (Component, ComponentId, Directory_, Attributes) VALUES ('DEFAULTCOMPONENT', '$(guid)', 'TARGETDIR', 2)"
query "INSERT INTO Directory (Directory, DefaultDir) VALUES ('TARGETDIR', 'SourceDir')"
query "INSERT INTO Feature (Feature, Level, Attributes) VALUES ('DEFAULTFEATURE', 1, 0)"
query "INSERT INTO FeatureComponents (Feature_, Component_) VALUES ('DEFAULTFEATURE', 'DEFAULTCOMPONENT')"
query "INSERT INTO Media (DiskId, LastSequence) VALUES (1, 1)"
query "INSERT INTO Property (Property, Value) VALUES ('ProductName', '$title')"
query "INSERT INTO Property (Property, Value) VALUES ('ProductVersion', '$version')"
query "INSERT INTO Property (Property, Value) VALUES ('ProductCode', '$(guid)')"
query "INSERT INTO Property (Property, Value) VALUES ('ProductLanguage', '1033')"
query "INSERT INTO Property (Property, Value) VALUES ('ProductID', 'none')"
query "INSERT INTO Property (Property, Value) VALUES ('Manufacturer', '$author')"
query "INSERT INTO Property (Property, Value) VALUES ('UpgradeCode', '$(guid)')"
query "INSERT INTO Property (Property, Value) VALUES ('ARPSYSTEMCOMPONENT', '0')"

ies 'AllocateRegistrySpace' 'NOT Installed' '1550'
ies 'AppSearch' '' '400'
ies 'BindImage' '' '4300'
ies 'CCPSearch' 'NOT Installed' '500'
ies 'CostFinalize' '' '1000'
ies 'CostInitialize' '' '800'
ies 'CreateFolders' '' '3700'
ies 'CreateShortcuts' '' '4500'
ies 'DeleteServices' 'VersionNT' '2000'
ies 'DuplicateFiles' '' '4210'
ies 'FileCost' '' '900'
ies 'InstallFiles' '' '4000'
ies 'InstallFinalize' '' '6600'
ies 'InstallInitialize' '' '1500'
ies 'InstallODBC' '' '5400'
ies 'InstallServices' 'VersionNT' '5800'
ies 'InstallValidate' '' '1400'
ies 'LaunchConditions' '' '100'
ies 'MoveFiles' '' '3800'
ies 'MsiPublishAssemblies' '' '6250'
ies 'PatchFiles' '' '4090'
ies 'ProcessComponents' '' '1600'
ies 'PublishComponents' '' '6200'
ies 'PublishFeatures' '' '6300'
ies 'PublishProduct' '' '6400'
ies 'RMCCPSearch' 'NOT Installed' '600'
ies 'RegisterClassInfo' '' '4600'
ies 'RegisterComPlus' '' '5700'
ies 'RegisterExtensionInfo' '' '4700'
ies 'RegisterFonts' '' '5300'
ies 'RegisterMIMEInfo' '' '4900'
ies 'RegisterProduct' '' '6100'
ies 'RegisterProgIdInfo' '' '4800'
ies 'RegisterTypeLibraries' '' '5500'
ies 'RegisterUser' '' '6000'
ies 'RemoveDuplicateFiles' '' '3400'
ies 'RemoveEnvironmentStrings' '' '3300'
ies 'RemoveFiles' '' '3500'
ies 'RemoveFolders' '' '3600'
ies 'RemoveIniValues' '' '3100'
ies 'RemoveODBC' '' '2400'
ies 'RemoveRegistryValues' '' '2600'
ies 'RemoveShortcuts' '' '3200'
ies 'SelfRegModules' '' '5600'
ies 'SelfUnregModules' '' '2200'
ies 'SetODBCFolders' '' '1100'
ies 'StartServices' 'VersionNT' '5900'
ies 'StopServices' 'VersionNT' '1900'
ies 'UnpublishComponents' '' '1700'
ies 'UnpublishFeatures' '' '1800'
ies 'UnregisterClassInfo' '' '2700'
ies 'UnregisterComPlus' '' '2100'
ies 'UnregisterExtensionInfo' '' '2800'
ies 'UnregisterFonts' '' '2500'
ies 'UnregisterMIMEInfo' '' '3000'
ies 'UnregisterProgIdInfo' '' '2900'
ies 'UnregisterTypeLibraries' '' '2300'
ies 'ValidateProductID' '' '700'
ies 'WriteEnvironmentStrings' '' '5200'
ies 'WriteIniValues' '' '5100'
ies 'WriteRegistryValues' '' '5000'

$db.Commit()
$db = $null
[GC]::Collect()

$msi  = New-Object -ComObject WindowsInstaller.Installer
$info = $msi.SummaryInformation($name, 9)

$info.Property(1) = 1252
$info.Property(2) = $title
$info.Property(3) = "Empty"
$info.Property(4) = $author
$info.Property(7) = "Intel;1033"
$info.Property(9) = $(guid)
$info.Property(14) = 200 # Minimum required for 64bit. Windows Installer 2.0.
$info.Property(15) = 0
$info.Property(18) = "Windows Installer"

$info.Persist()

Most of the code in the above script is used to construct the database tables required by a valid MSI. I won't go into the gory details, but if you are after more information, take a look at MSDN. It covers everything you need, whether it be working out how to interact with the MSI database, or deciphering the properties within the Summary Information table.

Having run the previous code to create empty.msi, let's now modify the MSI for the purpose of configuring Microsoft Edge. In particular, we will:

Here's the code:

function guid() {
  return ("{" + ([guid]::NewGuid()).guid + "}").ToUpper()
}

function query($sql) {
  $view = $db.OpenView($sql)
  $view.Execute()
  $view.Close()
}

function ies($a, $c, $s) {
  query "INSERT INTO InstallExecuteSequence (Action, Condition, Sequence) VALUES ('$a', '$c', '$s')"
}

$source = "empty.msi"
$target = "edge.msi"
$title  = "Disable Microsoft Edge Welcome"

Copy-Item $source $target

$msi = New-Object -ComObject WindowsInstaller.Installer
$db  = $msi.OpenDatabase($target, 2)

query "UPDATE Property SET Value = '$title' WHERE Property = 'ProductName'"
query "UPDATE Property SET Value = '$(guid)' WHERE Property = 'ProductCode'"
query "UPDATE Component SET ComponentId = '$(guid)' WHERE Component = 'DEFAULTCOMPONENT'"
query "INSERT INTO CustomAction (Action, Type, Target) VALUES ('CUSTOMACTION1', '37', 'var k,r=GetObject(`"winmgmts:!root/default:StdRegProv`");while(r.EnumKey(0x80000001,`"SOFTWARE\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppContainer\\Storage\\microsoft.microsoftedge_8wekyb3d8bbwe`",k)){}')"
query "INSERT INTO Registry (Registry, Root, ``Key``, Name, Value, Component_) VALUES ('ACTIVESETUP.VERSION', '2', 'Software\Microsoft\Active Setup\Installed Components\[ProductCode]', 'Version', '1.0', 'DEFAULTCOMPONENT')"
query "INSERT INTO Registry (Registry, Root, ``Key``, Name, Value, Component_) VALUES ('ACTIVESETUP.STUBPATH', '2', 'Software\Microsoft\Active Setup\Installed Components\[ProductCode]', 'StubPath', 'msiexec.exe /fu [ProductCode]', 'DEFAULTCOMPONENT')"
query "INSERT INTO Registry (Registry, Root, ``Key``, Name, Value, Component_) VALUES ('REG1', '1', 'Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Storage\microsoft.microsoftedge_8wekyb3d8bbwe\MicrosoftEdge\Main', 'IE10TourNoShow', '`#1', 'DEFAULTCOMPONENT')"

ies 'CUSTOMACTION1' 'Installed' '4950'

$db.Commit()
$db = $null
[GC]::Collect()

$msi  = New-Object -ComObject WindowsInstaller.Installer
$info = $msi.SummaryInformation($target, 3)

$info.Property(2) = $title
$info.Property(9) = $(guid)
$info.Persist()

There are a few items in the above script that require further explanation:

Having run the above two scripts, you should have a file named edge.msi. To install it, open an elevated command prompt and run:

msiexec.exe /i Edge.msi /qb ALLUSERS=1

Now log into the same computer with a new user account (a user that hasn't previously logged on, or at the very least, as a user that hasn't launched Edge). Now, try opening Edge. If everything has gone to plan, the annoying welcome page won't be displayed.