I was recently tasked with deploying Windows 10 Kiosk Mode for a customer. This is without Intune.

Kiosk Mode can be easily deployed via Intune but if you are not using that as a deployment mechanism then it’s still possible but requires a bit more manual graft. There were some interesting observations along the way so I’ll capture these in this document and hopefully this will help you avoid the pitfalls.

So, the plan was to deploy a multi-app kiosk. Multi-app kiosks are allowed from Windows 10 1709 onward, make sure you have at least this version on your device. I’m going to be mentioning certain baselines here, since certain features are only allowed for certain baselines or simply because I had problems and the fixes were to deploy a particular release or hotfix. Make sure, also, that you are running either the Enterprise, Education, Pro or S SKU. Windows 10 Home is not supported.

Kiosks use the Assigned Access CSP feature and you can read about this here. Applications can be either Win32 apps or UWP apps. For UWP apps you must provide the App User Model ID (AUMID) and for Win32 apps the full path of the executable file in your allowed apps list and we’ll take a look at this shortly.

The basics of kiosk mode are that we must create a XML file which will contain a profile or set of profiles which are assigned to configs. The wording from Microsoft is as such:

  • A configuration xml can define multiple profiles. Each profile has a unique Id and defines a set of applications that are allowed to run, whether the taskbar is visible, and can include a custom Start layout.
  • A configuration xml can have multiple config sections. Each config section associates a non-admin user account to a default profile Id.
  • Multiple config sections can be associated to the same profile.
  • A profile has no effect if it’s not associated to a config section.

For the example here, we are going to keep it simple by creating one profile and one config.

Start off by generating a unique GUID which will be used to associate the profile with the config. You can do this online. I’ve used the site https://www.guidgenerator.com/online-guid-generator.aspx.

Now we can start to construct the XML file. Microsoft has lots of examples in their documentation so let’s take an example from there with my generated GUID added.

<?xml version="1.0" encoding="utf-8" ?>
<AssignedAccessConfiguration
    xmlns="https://schemas.microsoft.com/AssignedAccess/2017/config"
    xmlns:rs5="https://schemas.microsoft.com/AssignedAccess/201810/config"
>     <Profiles>
        <Profile Id="{bc38b341-6836-449d-ad4f-49672ab8e8a2}">
            <AllAppsList>
                <AllowedApps>
                    ...
                </AllowedApps>
            </AllAppsList>
            <rs5:FileExplorerNamespaceRestrictions>
                <rs5:AllowedNamespace Name="Downloads"/>
            </rs5:FileExplorerNamespaceRestrictions>
            <StartLayout>
                ...
            </StartLayout>
            <Taskbar ShowTaskbar="true"/>
        </Profile>
    </Profiles>
</AssignedAccessConfiguration>

This example is the basic structure of the <PROFILE> section of the XML. Here assigned apps, start menu layout and Taskbar status can be defined.

Let’s expand this out slightly and add in some detail.

<?xml version="1.0" encoding="utf-8" ?>
<AssignedAccessConfiguration
    xmlns="http://schemas.microsoft.com/AssignedAccess/2017/config"
    xmlns:r1809="http://schemas.microsoft.com/AssignedAccess/201810/config"
>
    <Profiles>
        <Profile Id="{bc38b341-6836-449d-ad4f-49672ab8e8a2}">
            <AllAppsList>
                <AllowedApps>
                    <App DesktopAppPath="C:\Program Files (x86)\Internet Explorer\IEXPLORE.EXE" r1809:AutoLaunch="true" />
                    <App DesktopAppPath="C:\Program Files\Internet Explorer\IEXPLORE.EXE" />
                    <App DesktopAppPath="C:\WINDOWS\SYSTEM32\CMD.EXE" />
                </AllowedApps>
            </AllAppsList>
            <StartLayout>
                <![CDATA[<LayoutModificationTemplate xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout" xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout" Version="1" xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification">
  <LayoutOptions StartTileGroupCellWidth="6" />
  <DefaultLayoutOverride>
    <StartLayoutCollection>
      <defaultlayout:StartLayout GroupCellWidth="6">
        <start:Group Name="">
          <start:DesktopApplicationTile Size="2x2" Column="0" Row="0" DesktopApplicationID="Microsoft.InternetExplorer.Default"  />
          <start:DesktopApplicationTile Size="2x2" Column="2" Row="0" DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\System Tools\Command Prompt.lnk" />
        </start:Group>
      </defaultlayout:StartLayout>
    </StartLayoutCollection>
  </DefaultLayoutOverride>
</LayoutModificationTemplate>
                ]]>
            </StartLayout>
            <Taskbar ShowTaskbar="true"/>
        </Profile>
    </Profiles>
    <Configs>
        <Config>
            <Account>temp</Account>
            <DefaultProfile Id="{bc38b341-6836-449d-ad4f-49672ab8e8a2}"/>
        </Config>
    </Configs>
</AssignedAccessConfiguration>

So what’s been added here?

Well I want to allow Internet Explorer and CMD to run on my kiosk. So I have defined these in the AllowedApps tags. For Internet Explorer I have also set the command parameter r1809:AutoLaunch=”true”. This is a new feature from Windows 10 1809, the ability to auto launch an app. You also have to add the line xmlns:r1809=”http://schemas.microsoft.com/AssignedAccess/201810/config&#8221; in the <AssignedAccessConfiguration> section of the XML.

Note from the field – take a look at the code in the example from Microsoft and compare with what I have added. xmlns:rs5= or xmlns:r1809= ? Well MS has mixed messages since their documentation references each but xmlns:r1809= is the one to use. They need to update their documentation to reflect this.

Note from the field – I’ve had zero success autolaunching when using Windows 10 1809, even though the code is written for it. Maybe I needed a hotfix but nothing is stated. In the end, I pushed 1903 out to the endpoint and the code works perfectly.

What else is happening in the code? Well I have set a Start Menu to display my IE and CMD shortcuts and I’m allowing the taskbar to be shown.

I’ve also created the <CONFIG> section and in this I am creating a link between the <CONFIG> and <PROFILE> section via DefaultProfile Id=. The GUID matches that of the Profile Id= in the <PROFILE> section. Therefore, the account associated with the <CONFIG> will have the <PROFILE> settings applied to it when logged in.

I have referenced a local account in the <ACCOUNT> tag, <Account>temp</Account>, however this can be a domain account, reference with domain\account or an Azure AD account.

Note from the field – when applying the XML the account must exist for the XML to apply successfully.

There is other functionality which you can add to the XML, such as configuring automatic logon, changing the display name which appears when logging in or allowing access to the Download folder for storage. As I say, I’m keeping this simple and showing you the basics to get up and running. Check out Set up a multi-app kiosk  for more tips

With our XML ready to go we can apply the code by wrapping this in PowerShell and using the MDM bridge to apply.

So we enter

$nameSpaceName="root\cimv2\mdm\dmmap"
$className="MDM_AssignedAccess"
$obj = Get-CimInstance -Namespace $namespaceName -ClassName $className
Add-Type -AssemblyName System.Web
$obj.Configuration = [System.Web.HttpUtility]::HtmlEncode(@"
<OUR XML CODE>
"@)
Set-CimInstance -CimInstance $obj

Here’s my example:

$nameSpaceName="root\cimv2\mdm\dmmap"
$className="MDM_AssignedAccess"
$obj = Get-CimInstance -Namespace $namespaceName -ClassName $className
Add-Type -AssemblyName System.Web
$obj.Configuration = [System.Web.HttpUtility]::HtmlEncode(@"
<?xml version="1.0" encoding="utf-8" ?>
<AssignedAccessConfiguration
    xmlns="http://schemas.microsoft.com/AssignedAccess/2017/config"
    xmlns:r1809="http://schemas.microsoft.com/AssignedAccess/201810/config"
>
    <Profiles>
        <Profile Id="{bc38b341-6836-449d-ad4f-49672ab8e8a2}">
            <AllAppsList>
                <AllowedApps>
                    <App DesktopAppPath="C:\Program Files (x86)\Internet Explorer\IEXPLORE.EXE" r1809:AutoLaunch="true" />
                    <App DesktopAppPath="C:\Program Files\Internet Explorer\IEXPLORE.EXE" />
                    <App DesktopAppPath="C:\WINDOWS\SYSTEM32\CMD.EXE" />
                </AllowedApps>
            </AllAppsList>
            <StartLayout>
                <![CDATA[<LayoutModificationTemplate xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout" xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout" Version="1" xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification">
  <LayoutOptions StartTileGroupCellWidth="6" />
  <DefaultLayoutOverride>
    <StartLayoutCollection>
      <defaultlayout:StartLayout GroupCellWidth="6">
        <start:Group Name="">
          <start:DesktopApplicationTile Size="2x2" Column="0" Row="0" DesktopApplicationID="Microsoft.InternetExplorer.Default"  />
          <start:DesktopApplicationTile Size="2x2" Column="2" Row="0" DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\System Tools\Command Prompt.lnk" />
        </start:Group>
      </defaultlayout:StartLayout>
    </StartLayoutCollection>
  </DefaultLayoutOverride>
</LayoutModificationTemplate>
                ]]>
            </StartLayout>
            <Taskbar ShowTaskbar="true"/>
        </Profile>
    </Profiles>
    <Configs>
        <Config>
            <Account>temp</Account>
            <DefaultProfile Id="{bc38b341-6836-449d-ad4f-49672ab8e8a2}"/>
        </Config>
    </Configs>
</AssignedAccessConfiguration>
"@)
Set-CimInstance -CimInstance $obj

To inject this, we need to be running as SYSTEM. If you are using ConfigMgr to apply the PowerShell then this is nice and simple as you can simply deploy out in your Task Sequence as a Run PowerShell script step.

To manually do it follow these steps:

  • Grab a copy of PSTools
  • From an administrator CMD prompt run PSEXEC -i -s cmd to launch CMD as SYSTEM.

A quick whoami will confirm you are running as SYSTEM

Launch PowerShell from CMD and Set-ExecutionPolicy Unrestricted. Then run the PS1 script containing the code. If you get an error you may need to validate your code. As I mentioned earlier, make sure your account exists or can be referenced.

I’m using the local temp account but it’s not been defined.

After creating the account I can inject the PS1 code successfully.

You can use the first three lines of the PS1 script to query the AssignedAccess MDM to ensure that the code has been injected OK, or if you update the code and re-inject and need to check your changes have been accepted.

Check the $Obj variable to confirm.

Now when logging in as the assigned user the lockdowns and assigned access will take effect.

If anything fails to run check the AppLocker logon the device for blocks and update your XML file with the correct details.

Note from the field – There is a bug with printing from IE and you must run Windows 10 1903 with latest October KB’s to fix the problem. The error reports as a block in policy. The problem is also resolved in Windows 10 1909.

Note from the field – AppLocker blocked me from running CMD from anywhere except from the Start Menu tile, which points to the location %APPDATA%\Microsoft\Windows\Start Menu\Programs\System Tools\Command Prompt.lnk. i was attempting to run the shortcut to CMD from another location on the c: drive. Not sure why this happened. Most kiosks wouldn’t want to allow CMD in the first instance but this was something I noted as part of my testing. Be aware.

With everything up and running you’ll have a locked down kiosk in full effect.

Feel free to comment with your experiences and let me know how you got on with adding in auto logon, folder access and more.