Understanding Windows Settings URIs and How to Use Them in Enterprise Environments

Windows provides a powerful and flexible management framework that enables organizations to configure, restrict, or simplify the Settings experience for their users. One of the key mechanisms behind this framework is the ms-settings: URI scheme — a consistent, internal navigation system that defines how each Settings page in Windows is accessed.

If you have ever opened Windows Settings and navigated to Privacy → Camera, you were effectively visiting a page identified by a specific internal URI:
ms-settings:privacy-webcam.

These URIs exist for virtually every part of the modern Settings interface — from Windows Update to Bluetooth, Accounts, and Accessibility. Administrators and automation tools can call these URIs directly to open pages, but they can also use them for more advanced purposes, such as controlling which pages are available to users through Group Policy or MDM.

What Are ms-settings URIs?

Each page within Windows Settings has an internal URI (Uniform Resource Identifier) that begins with the prefix ms-settings: followed by a descriptive identifier. For example:

  • ms-settings:windowsupdate – Windows Update
  • ms-settings:bluetooth – Bluetooth settings
  • ms-settings:privacy-microphone – Microphone permissions
  • ms-settings:network-wifi – Wi-Fi configuration

Typing any of these URIs into Win + R or a command prompt immediately opens the corresponding Settings page. These same URIs are also what Group Policy and Intune use when defining visibility rules for Settings.

Over time, new pages and features are added to Windows, and therefore new URIs are introduced. Having a reliable way to extract these URIs directly from Windows helps administrators maintain accurate and consistent configuration policies across versions.

Why ms-settings URIs Matter to Enterprises

In enterprise environments, Windows devices are rarely unmanaged. Organizations typically enforce baseline configurations to meet security, usability, and compliance requirements. Control over the Settings experience can be a crucial part of that strategy.

Common scenarios include:

  • Kiosk and shared PCs – where users should not modify system or network settings.
  • Education or classroom devices – limiting what students can access or change.
  • Corporate desktops – ensuring consistent, compliant configurations across all departments.
  • Call center terminals or point-of-sale systems – providing only task-relevant options.
  • High-security workstations – preventing changes to privacy, update, or device settings.

By controlling access to specific Settings pages, IT administrators can:

  • Reduce accidental misconfiguration.
  • Lower support costs.
  • Improve compliance posture.
  • Simplify user experience for task-focused roles.
  • Prevent exposure of system areas that are centrally managed or locked down.

Managing Settings Visibility with Group Policy

Windows includes a built-in Group Policy setting that allows administrators to control which pages in the Settings app are visible to users.

Policy location:

Computer/User Configuration → Administrative Templates → Control Panel → Settings Page Visibility

This policy uses a semicolon-separated list of rules that reference Settings pages by their ms-settings identifiers. The part after the colon is what the policy recognizes. For example:

Hide selected pages

hide:privacy-webcam;bluetooth;display

Show only selected pages

showonly:windowsupdate;about

Administrators can combine these directives to tailor the Settings experience precisely to the needs of their organization.

This approach is particularly useful in locked-down environments where users have a limited set of configuration options, or where privacy and security policies mandate restricted access to certain features.

The Challenge: Keeping Visibility Rules Current

The Windows Settings app evolves continuously. With each new feature update or release, new categories, pages, and URIs may appear. For administrators maintaining long-term device configurations, that means GPO lists need to be reviewed and updated regularly.

While Microsoft provides extensive documentation for the most common pages, enterprise administrators often need a complete and current list of all URIs available on the system they are managing. This ensures that policies remain accurate and compatible, even when upgrading from one Windows build to another.

Introducing Get-MSSettingsURIs.ps1

To simplify this process, we created a small PowerShell script called Get-MSSettingsURIs.ps1.

This script scans the system’s SystemSettings.dll file — the core component behind the modern Settings interface — and extracts every ms-settings: URI it contains. Because it reads directly from the operating system, it always reflects exactly what that Windows build supports.

You can use the script to:

  • Discover all current Settings URIs on any Windows device.
  • Compare differences between Windows versions (e.g., 23H2 → 25H2).
  • Build or update your Group Policy “Settings Page Visibility” configuration.
  • Validate that newly introduced Settings pages are correctly managed.

How the Script Works

At a high level, Get-MSSettingsURIs.ps1 reads binary data from C:\Windows\ImmersiveControlPanel\SystemSettings.dll and searches for all strings that match the ms-settings: pattern. It supports both ASCII and Unicode encodings to ensure no identifiers are missed.

It then sorts and outputs the unique list of URIs. You can optionally format the results to make them directly usable in a Group Policy setting.

Because the script runs locally and reads system files, it does not require any administrative privileges beyond read access to the Windows directory.

Example Usage

To run the script with default settings:

.\Get-MSSettingsURIs.ps1

This outputs a complete list of all ms-settings: URIs found in the current Windows installation.

If you want the results formatted for direct use in Group Policy (without the ms-settings: prefix):

.\Get-MSSettingsURIs.ps1 -GpoStyle

To scan a copy of SystemSettings.dll from another Windows build (for testing or preparation):

.\Get-MSSettingsURIs.ps1 -Binary "C:\Temp\SystemSettings.dll"

Integrating the Script Into IT Operations

Once you have the list of Settings URIs, you can automate several key tasks:

  • Baseline validation – Ensure the same visibility configuration applies across device groups.
  • Upgrade preparation – Before deploying a new Windows release, compare URI lists to identify new or renamed pages.
  • Compliance auditing – Verify that restricted settings remain hidden as expected.
  • Kiosk image building – Generate a minimal set of allowed pages for kiosk or shared devices.
  • Automation – Integrate the PowerShell output into configuration pipelines, Intune scripts, or Group Policy templates.

Because the script runs without external dependencies, it can easily be distributed as part of enterprise configuration management or imaging workflows.

Example Workflow for Administrators

  1. Run the PowerShell script on a reference Windows build.
  2. Save the resulting URI list to a text file, such as MSSettings_URIs_23H2.txt.
  3. Use the -GpoStyle option to generate a GPO-ready list.
  4. Configure the Settings Page Visibility policy in Group Policy or Intune.
  5. Optionally rerun the script after major OS updates to check for new entries.

By incorporating this simple step into your management process, you ensure that every system in your environment reflects the intended configuration and that new Settings pages introduced by future Windows versions are quickly identified.

Conclusion

The ms-settings: URI system is one of Windows’ most useful yet under-appreciated administrative capabilities. For enterprises that depend on configuration consistency, compliance, or controlled user experiences, understanding and managing these URIs is key.

The Get-MSSettingsURIs.ps1 script gives administrators an easy way to extract, review, and apply these identifiers directly from any Windows installation. Combined with Group Policy or MDM, it provides a fast and reliable method to shape the Settings experience for users — ensuring that each system remains secure, focused, and predictable.

Script Code

param(
  [string]$Path = "C:\Windows\ImmersiveControlPanel\SystemSettings.dll",
  [string]$OutFile,
  [switch]$GpoKeysOnly
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

if (-not (Test-Path -LiteralPath $Path)) {
  throw "Datei nicht gefunden: $Path"
}

# --- High-performance Scanner in C# (compiled on-the-fly) ---
$cs = @"
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

public static class MsSettingsScanner
{
    static readonly byte[] AsciiPrefix = Encoding.ASCII.GetBytes("ms-settings:");
    static readonly byte[] Utf16Prefix = Encoding.Unicode.GetBytes("ms-settings:");

    public static List<string> ExtractAll(string filePath)
    {
        byte[] data = File.ReadAllBytes(filePath); // 10 MB: ok & schnell
        var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        foreach (var s in FindAscii(data))
            set.Add(s);
        foreach (var s in FindUtf16(data))
            set.Add(s);

        var list = new List<string>(set);
        list.Sort(StringComparer.OrdinalIgnoreCase);
        return list;
    }

    // ---- ASCII ----
    static IEnumerable<string> FindAscii(byte[] data)
    {
        foreach (int idx in FindAll(data, AsciiPrefix))
        {
            int i = idx + AsciiPrefix.Length;
            var sb = new StringBuilder("ms-settings:");
            while (i < data.Length)
            {
                char c = (char)data[i];
                if (!IsValidUriChar(c)) break;
                sb.Append(c);
                i++;
            }
            yield return sb.ToString();
        }
    }

    // ---- UTF-16LE ----
    static IEnumerable<string> FindUtf16(byte[] data)
    {
        foreach (int idx in FindAll(data, Utf16Prefix))
        {
            int pos = idx + Utf16Prefix.Length;
            var sb = new StringBuilder("ms-settings:");
            // Nach dem Präfix: 2-Byte-Schritte (LE). Nur ASCII (hi=0) akzeptieren.
            while (pos + 1 < data.Length)
            {
                byte lo = data[pos];
                byte hi = data[pos + 1];
                if (hi != 0) break; // nur ASCII-Zeichen
                char c = (char)lo;
                if (!IsValidUriChar(c)) break;
                sb.Append(c);
                pos += 2;
            }
            yield return sb.ToString();
        }
    }

    // Boyer–Moore–Horspool über Bytes (schnell, allokationsarm)
    static IEnumerable<int> FindAll(byte[] haystack, byte[] needle)
    {
        int n = haystack.Length, m = needle.Length;
        if (m == 0 || n < m) yield break;

        int[] skip = new int[256];
        for (int i = 0; i < skip.Length; i++) skip[i] = m;
        for (int i = 0; i < m - 1; i++) skip[needle[i]] = m - 1 - i;

        int pos = 0;
        int last = m - 1;
        while (pos <= n - m)
        {
            int j = last;
            while (j >= 0 && haystack[pos + j] == needle[j]) j--;
            if (j < 0)
            {
                yield return pos;
                pos += m; // nächster möglicher Start
            }
            else
            {
                pos += skip[haystack[pos + last]];
            }
        }
    }

    // erlaubte Zeichen nach "ms-settings:"
    static bool IsValidUriChar(char c)
    {
        if ((c >= 'a' && c <= 'z') ||
            (c >= 'A' && c <= 'Z') ||
            (c >= '0' && c <= '9'))
            return true;

        switch (c)
        {
            case '-': case '_': case '.': case ':':
            case '/': case ';': case '?': case '&':
            case '=': case '#': case '%': case '+':
            case ',': case '@':
                return true;
            default:
                return false;
        }
    }
}
"@

# Make sure to only compile once per session
$loaded = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetTypes() -as [type] } | Where-Object { $_.GetType("MsSettingsScanner") }
if (-not $loaded) {
  Add-Type -TypeDefinition $cs -Language CSharp -IgnoreWarnings
}

$uris = [MsSettingsScanner]::ExtractAll($Path)

if ($GpoKeysOnly) {
  $uris = $uris | ForEach-Object {
    if ($_ -like 'ms-settings:*') { $_.Substring(12) } else { $_ }
  } | Where-Object { $_ }
}

if ($OutFile) {
  $uris | Set-Content -LiteralPath $OutFile -Encoding UTF8
  Write-Host ("Fertig. {0} Einträge → {1}" -f $uris.Count, $OutFile)
} else {
  $uris
  Write-Host ("`nTotal: {0}" -f $uris.Count)
}

by

Tags:

Comments

Leave a Reply