Public/New-WindowsUserProfile.ps1

#!/usr/bin/env pwsh
Set-StrictMode -Version Latest

if ($IsWindows) {
    function New-WindowsUserProfile {
        <#
        .SYNOPSIS
            Creates a local user and its Windows profile directory.

        .DESCRIPTION
            Creates a local account with New-LocalUser, adds it to Users, or to
            Administrators with -IsAdmin, then creates the profile with the CreateProfile
            function in userenv.dll. Windows normally creates a profile at first logon;
            this creates it immediately.

            When Windows already has a profile registered for the account, the existing
            profile is returned instead. Throws when the profile is registered but its
            directory is missing.

            Requires an elevated session, including for -WhatIf. Supports -WhatIf and
            -Confirm. Windows only.

        .PARAMETER UserName
            The name of the new local account, without a domain or computer prefix.

        .PARAMETER FullName
            The display name of the new local account.

        .PARAMETER Description
            The description of the new local account.

        .PARAMETER IsAdmin
            Adds the account to Administrators instead of Users.

        .INPUTS
            None. You can't pipe objects to this function.

        .OUTPUTS
            System.Management.Automation.PSCustomObject. One object with the account name,
            SID, profile path, and whether the profile was created. Nothing is returned for
            -WhatIf.

        .EXAMPLE
            New-WindowsUserProfile -UserName jdoe -FullName 'Jane Doe' -Description 'Analyst'

            Prompts for a password, creates jdoe as a standard user, and creates the
            profile directory.

        .EXAMPLE
            New-WindowsUserProfile -UserName svc-audit -FullName 'Audit Service' -Description 'Audit tasks' -IsAdmin

            Creates svc-audit as a local administrator with a profile directory.

        .EXAMPLE
            New-WindowsUserProfile -UserName jdoe -FullName 'Jane Doe' -Description 'Analyst' -WhatIf

            Previews the account and profile creation without making changes.
        #>

        [CmdletBinding(SupportsShouldProcess)]
        [OutputType([PSCustomObject])]
        param(
            [Parameter(Mandatory, HelpMessage = 'Name of the new local account, without a domain or computer prefix.')]
            [ValidateNotNullOrEmpty()]
            [ValidatePattern('^[^\\]+$')]
            [string]$UserName,

            [Parameter(Mandatory, HelpMessage = 'Display name of the new local account.')]
            [ValidateNotNullOrEmpty()]
            [string]$FullName,

            [Parameter(Mandatory, HelpMessage = 'Description of the new local account.')]
            [ValidateNotNullOrEmpty()]
            [string]$Description,

            [Parameter(HelpMessage = 'Adds the account to Administrators instead of Users.')]
            [switch]$IsAdmin
        )
        begin {
            Assert-Administrator
        }
        process {
            $group = if ($IsAdmin) {
                'Administrators'
            }
            else {
                'Users'
            }

            if (-not $PSCmdlet.ShouldProcess($UserName, "Create local account, add to $group, and create profile")) {
                return
            }

            [void](New-LocalUser -Name $UserName -FullName $FullName -Description $Description)
            Add-LocalGroupMember -Group $group -Member $UserName

            try {
                $ntAccount = [System.Security.Principal.NTAccount]::new($env:COMPUTERNAME, $UserName)
                $sidObject = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])
            }
            catch [System.Security.Principal.IdentityNotMappedException] {
                throw "$($env:COMPUTERNAME)\$($UserName) does not exist. Please create the account before creating the profile."
            }

            $sid = $sidObject.Value
            $canonicalAccountName = $sidObject.Translate([System.Security.Principal.NTAccount]).Value
            $profileKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid"

            if (Test-Path -LiteralPath $profileKey) {
                $rawPath = (Get-ItemProperty -LiteralPath $profileKey -Name ProfileImagePath).ProfileImagePath
                $existingPath = [System.Environment]::ExpandEnvironmentVariables($rawPath)

                if (-not (Test-Path -LiteralPath $existingPath -PathType Container)) {
                    throw "Windows registered profile $existingPath for $canonicalAccountName ($sid), but the directory is missing. Repair or remove the profile registration then retry."
                }

                [PSCustomObject]@{
                    Account     = $canonicalAccountName
                    Sid         = $sid
                    ProfilePath = $existingPath
                    Created     = $false
                }
            }
            else {
                if (-not ('WindowsProfile.NativeMethods' -as [type])) {
                    Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace WindowsProfile
{
    public static class NativeMethods
    {
        [DllImport("userenv.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
        private static extern int CreateProfile(
            string pszUserSid,
            string pszUserName,
            StringBuilder pszProfilePath,
            uint cchProfilePath
        );

        public static string Create(string sid, string userName)
        {
            const int Capacity = 260;
            var profilePath = new StringBuilder(Capacity);

            int hresult = CreateProfile(sid, userName, profilePath, Capacity);

            if (hresult < 0)
            {
                Marshal.ThrowExceptionForHR(hresult);
            }

            return profilePath.ToString();
        }
    }
}
'@

                }

                $profilePath = [WindowsProfile.NativeMethods]::Create($sid, $UserName)

                if (-not (Test-Path -LiteralPath $profilePath -PathType Container)) {
                    throw "CreateProfile returned $profilePath, but the profile directory was not created."
                }

                [PSCustomObject]@{
                    Account     = $canonicalAccountName
                    Sid         = $sid
                    ProfilePath = $profilePath
                    Created     = $true
                }
            }
        }
    }
}