Microsoft Account automation in Windows 8

New Tool: Script to bind a new Local User Account with a Microsoft Account for non-domain joined machines.

Microsoft Accounts

Microsoft Accounts are the federated accounts Microsoft has been using recently for all of their internet properties. If you have logged into Technet, Hotmail, Xbox, etc.. then you have logged in using your Microsoft Account, For me it’s a Hotmail.com account I’ve had for a while now. In the past they were also called “Windows Live ID”, but they’re Microsoft Accounts now.

In Windows 8, Microsoft has been extending this account information onto the local machine. It’s the primary account used to manage your Windows Store apps, the built in E-Mail application automatically syncs to your Hotmail account, and more. One of the best ways Microsoft has extended the Microsoft Account is by allowing Windows 8 to use the Microsoft Account as a Single Sign On (SSO) for logging into Windows 8 itself. My Hotmail password becomes my Windows logon password.

I’ve been playing around with various Microsoft Accounts with my home machines. One of the things that bugged me is that when created, the account name and profile path is not something that I chose, instead Windows 8 generates a unique profile path that could look something like Keith_000 or Keith_001. I can’t tell which profile account goes with which Microsoft Account, in fact it depends on which account I logged in as first.

There are ways to create local accounts using manually generated account names, and then bind the user to a Microsoft Account later, but I thought the process was a bit cumbersome. I was also looking for a way to programmatically create accounts using scripting. Seemed like a good challenge.

Domain users are a different problem, something to investigate when I have a domain account again.

Reverse Engineering

I started looking at the local accounts in general, how the accounts were created, and what tools knew about accounts.

  • If I was to create an account manually, login to that account, and then attempt to bind the local account to a Microsoft Account, it asks me for the password. I didn’t like this method, because I didn’t want to be responsible for handing sensitive data like the password.
  • If I were to use the new “Modern” PC Settings tool to Add a user, all I have to do is add the e-mail address, no passwords are exchanged until the user logs in for the first time.
  • I checked the “Manage Accounts” page in the legacy Control Panel, and noticed that the applet was able to distinguish between a Local Account and a Microsoft Account.

Some further digging revealed that the Control Panel applet most likely used the API: NetUserGetInfo(), with the USER_INFO_24 Structure

typedef struct _USER_INFO_24 {
  BOOL   usri24_internet_identity;
  DWORD  usri24_flags;
  LPWSTR usri24_internet_provider_name;
  LPWSTR usri24_internet_principal_name;
  PSID   usri24_user_sid;
} USER_INFO_24, *PUSER_INFO_24, *LPUSER_INFO_24;

However this turned out to be a bust, as the NetUserSetInfo() API didn’t accept USER_INFO_24 data structure. Bust!

I loaded up the SysInternals tool Process Explorer (ProcExp.exe) to see if I could find out any more information. It took a while, but I was finally able to track down some writes to the registry where the Modern “PC Settings” tool and the “Manage Accounts” legacy control panel applet were doing the work (from the lsass.exe process). The unfortunate thing is the registry entries were in the HKLM:\SAM registry key, which are given only System access and are protected from even Administrators.

Luckily it appeared that there were only three additional registry entries added to associate a Microsoft Account with a Local User:

    Hive: HKEY_LOCAL_MACHINE\sam\sam\domains\Account\users
[…]
000003ED     F                    : {2, 0, 1, 0...}
             V                    : {0, 0, 0, 0...}
             ForcePasswordReset   : {0, 0, 0, 0}
             InternetUserName     : {109, 0, 97, 0...}
             InternetProviderGUID : {143, 136, 249, 215...}

ForcePasswordReset and InternetProviderGUID were small binary arrays, and the InternetUserName was a binary representation of a Unicode Microsoft Account. The hard part was trying to find the correct user account under HKLM:\SAM. The Wikipedia page on “Security_Identifier” showed how to find the SID.

Scripting Solition

The script is written in powershell, and designed to work with PSExec.exe to elevate itself to write to the protected space.

The script follows the following logic:

  • Verify the current user is running elevated as an Administrator
  • If PSExec.exe (from SysInternals) is not found locally, then download.
  • If the current process is not running as LocalSystem, use PSExec.exe to relaunch as LocalSystem.
  • Find the SID of the local user account
  • Enumerate through all accounts under HKLM:\Sam\Sam\Domains\Account\Users and search for a substring of the SID.
  • Once Found, add the necessary registry entries.

It assumes that you already have the accounts created, also you may need to make the user a local administrator if necessary. There is an example of how to create the accounts below.

This script is primarily designed for users in a non-domain joined environment. If you want to grant the user permissions on the local machine, just run the script. It might be interesting to add this script to MDT, a simple wizard would allow you to input.

MDTUsers
(Some project to add for another day).

 

[CmdletBinding(SupportsShouldProcess=$true)]
param(
       [Parameter(Mandatory=$true,Position=0)]
       [string] $UserName = ".",
       [Parameter(Mandatory=$true,Position=1)]
       [string] $MicrosoftAccount,
       [Parameter(Mandatory=$false,Position=2)]
       [string] $PSCommand = "PSExec.exe"
)

WRite-Verbose "Test for administrative privelages"

if (!([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ))
{
    throw "Not Running in the Administrative Context"
}

Write-Verbose "Run under the local System Context"

If (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).Identities.User.Value -ne "S-1-5-18")
{
    Write-Verbose "Find the PSExec.exe command"
    if ( (get-command $PSCommand -ErrorAction SilentlyContinue) -eq $null )
    {
        WRite-host "Missing PSExec.exe command. Force local download..."
        Start-BitsTransfer -Source "http://live.sysinternals.com/psexec.exe" -Destination "$env:SystemRoot\System32\PSExec.exe" -DisplayName "PSExec" -TransferType Download
    }

    Write-Verbose "NotRunnin in the System Context - run PSExec.exe"
    & psexec.exe /AcceptEula -e -i -s Powershell.exe  -noprofile -executionpolicy bypass $MyInvocation.Line
    exit
}

Write-Verbose "Find the Local User Account $UserName"

$objUser = New-Object System.Security.Principal.NTAccount($UserName)
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
$c = New-Object 'byte[]' $strsid.BinaryLength
$strSID.GEtBinaryForm($c,0)

Write-Verbose "Enumerate through all local Accounts in SAM registry and find the SID: $($StrSID.Value)"

$FoundUser = $NULL
foreach ($user in get-childitem "HKLM:\Sam\Sam\Domains\Account\Users")
{ 
    if ( $User.GetValue("V").length -gt 0 )
    {
        $v = $User.GetValue("V")
        foreach ( $i in ($v.length-$c.Length)..0) 
        {
            if ((compare-object $c $v[$i..($i-1+$c.length)] -sync 0).length -eq 0)
            {
                $FoundUSer = $User
                break
            }
        }
    }
}

if ($FoundUser -is [object])
{

    write-Host "Found USer: $($FoundUSer.PSPAth) now write $MicrosoftAccount"

    if ( $FoundUSer.GetValue("InternetUserName") -is [byte[]] )
    {
        write-warning "UserName already entered: $($user.GetValue('InternetUserName'))"
    }
    else
    {
        Set-ItemProperty $FoundUser.PSPath "ForcePasswordReset"   ([byte[]](0,0,0,0))
        Set-ItemProperty $FoundUser.PSPath "InternetUserName"     ([System.Text.Encoding]::UniCode.GetBytes($MicrosoftAccount))
        Set-ItemProperty $FoundUser.PSPath "InternetProviderGUID" ([GUID]("d7f9888f-e3fc-49b0-9ea6-a85b5f392a4f")).TOByteArray()
    }
}
Advertisements

6 thoughts on “Microsoft Account automation in Windows 8

  1. Instead of binding, do you have a way to use this script to do the switch from local to microsoft account. This is something I want to automate after some deployment with local accounts.

    • I don’t understand your distinction. This binding *is* switching from a Local Account to a Microsoft Account.

      However, I must admit that I did not try running this script within an existing account.
      Instead this script was designed to be run from the local “Administrator” account, just after you run the “net.exe user /add ” command. The trick is syncing the Microsoft Account password from live.com. If you run this script *before* you login to the Local Account, Windows will take care of syncing the account information for you. However if you try converting the Local Account to a Microsoft Account after logging into to that Account, the OS requires your Password information so it can sync the information from Live.com, I didn’t want to solve *that* problem.

      Hopefully, Microsoft will provide better management tools moving forwards to address the need to bind Microsoft Accounts to local (and more importantly Domain) accounts.

      • Then for a basic installation from usb key with an autounattend.xml file, is there a way to automate this process ? WSIM allows me to create local account with the xml file but I didn’t find a way for a microsoft account

  2. This script would associate the newly created accounts with your Microsoft Account. I don’t know if it would work in OOBE, but in MDT the script would be run from the local administrator account.

  3. I tried to run this script on my Windows 8.1 machine but it fails (PowerShell.exe exited with error code 1). Is this only for Windows 8? I have been looking a way to change the behaviour of Windows 8.1 and MS Account. If I want to use OneDrive integration in 8.1, I’m forced to replace my local account with MS account. After this I cannot log-in to my pc anymore with local account. I don’t like this.

    • For Windows 8 and 8.1, Windows *Binds* your local account with your external Microsoft account (in your case username@live.com). When you login to your new *bound* account it will look to live.com for the password validation. You are still logging in to a local account (as compared to a domain account).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s