PowerShell

Create a local user or administrator account in Windows using PowerShell

Recently I got a simple task to implement LAPS for the newly created local user instead of using the built-in local administrator account. It seemed easy at first. Go to Group Policies, create a new user, add it to an administrators group, and then follow standard steps to implement LAPS. That is until you find out it's actually not possible anymore due to password encryption key being available in the wild, which made Microsoft block that Group Policy Preference. While that road is blocked, I still need to get my user-created somehow. Let's do it with PowerShell. It's quite simple – use New-LocalUser a few parameters, some random password that I don't need to save as LAPS will overwrite it. Except it's not available on PowerShell 2.0, which is the default for Windows 7 that I have to support. Things get even more complicated if you consider that Administrators group is called differently in different countries. While I stopped supporting anything below PowerShell 5.1, I can't say if it's the project requirement.

While it may look a bit scary there are two commands in play. New-AdminAccount and Add-LocalUserToGroup. When  you run it it creates new user account, and adds it to Administrators group. Since it uses SID's to do that, language doesn't really matter. Whether it's french, swedish or polish edition all those systems shoould work just fine.

function New-AdminAccount {
    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME,
        [string] $User,
        [string] $DisplayName = 'Local Admin',
        [string] $Description
    )
    $LocalUsers = Get-WmiObject Win32_UserAccount -Filter "LocalAccount=true and Name LIKE '$User'"
    if (!$LocalUsers) {
        #"Running New-AdminAccount - $UserName" | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
        $RandomPassword = Get-RandomPassword -LettersLowerCase 7 -LettersHigherCase 7 -Numbers 3 -SpecialChars 10
        if ($User -ne '' -and $DisplayName -ne '') {
            # Create new local Admin user for script purposes
            $Computer = [ADSI]"WinNT://$ComputerName,Computer"
            $LocalAdmin = $Computer.Create("User", $User)
            try {
                $LocalAdmin.SetPassword($RandomPassword)
                $LocalAdmin.SetInfo()
                $LocalAdmin.FullName = $DisplayName
                $LocalAdmin.SetInfo()
                $LocalAdmin.Description = $Description
                #$LocalAdmin.UserFlags = 64 + 65536 # ADS_UF_PASSWD_CANT_CHANGE + ADS_UF_DONT_EXPIRE_PASSWD
                $LocalAdmin.UserFlags = 65536 # ADS_UF_DONT_EXPIRE_PASSWD
                $LocalAdmin.SetInfo()
            } catch {
                $ErrorMessage = $_.Exception.Message -replace [System.Environment]::NewLine
                Write-Warning "New-AdminAccount - Modification of local account failed: $ErrorMessage"
                #"New-AdminAccount - Modification of local account failed: $ErrorMessage" | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
                return
            }
        } else {
            #"New-AdminAccount - Account not created. UserName $User / DisplayName $DisplayName " | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
            Write-Warning "New-AdminAccount - Account not created. UserName $User / DisplayName $DisplayName"
        }
    }
}

function Get-RandomCharacters {
    [CmdletBinding()]
    param(
        [int] $length,
        [string] $characters
    )
    if ($length -ne 0 -and $characters -ne '') {
        $random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length }
        $private:ofs = "" # https://blogs.msdn.microsoft.com/powershell/2006/07/15/psmdtagfaq-what-is-ofs/
        return [String]$characters[$random]
    } else {
        return
    }
}
function Get-RandomPassword {
    [CmdletBinding()]
    param(
        [int] $LettersLowerCase = 4,
        [int] $LettersHigherCase = 2,
        [int] $Numbers = 1,
        [int] $SpecialChars = 0,
        [int] $SpecialCharsLimited = 1
    )
    $Password = @(
        Get-RandomCharacters -length $LettersLowerCase -characters 'abcdefghiklmnoprstuvwxyz'
        Get-RandomCharacters -length $LettersHigherCase -characters 'ABCDEFGHKLMNOPRSTUVWXYZ'
        Get-RandomCharacters -length $Numbers -characters '1234567890'
        Get-RandomCharacters -length $SpecialChars -characters '!$%()=?{@#'
        Get-RandomCharacters -length $SpecialCharsLimited -characters '!$#'
    )
    $StringPassword = $Password -join ''
    $StringPassword = ($StringPassword.ToCharArray() | Get-Random -Count $StringPassword.Length) -join ''
    return $StringPassword
}

function Get-InvariantGroup {
    [CmdletBinding()]
    param(
        [ValidateSet(
            'Access Control Assistance Operators',
            'Administrators'                     ,
            'Backup Operators'                   ,
            'Cryptographic Operators'            ,
            'Device Owners'                      ,
            'Distributed COM Users'              ,
            'Event Log Readers'                  ,
            'Guests'                             ,
            'Hyper-V Administrators'             ,
            'IIS_IUSRS'                          ,
            'Network Configuration Operators'    ,
            'Performance Log Users'              ,
            'Performance Monitor Users'          ,
            'Power Users'                        ,
            'Remote Desktop Users'               ,
            'Remote Management Users'            ,
            'Replicator'                         ,
            'System Managed Accounts Group'      ,
            'Users'
        )] $GroupName
    )
    $GroupSID = @{
        'Access Control Assistance Operators' = 'S-1-5-32-579'
        'Administrators'                      = 'S-1-5-32-544'
        'Backup Operators'                    = 'S-1-5-32-551'
        'Cryptographic Operators'             = 'S-1-5-32-569'
        'Device Owners'                       = 'S-1-5-32-583'
        'Distributed COM Users'               = 'S-1-5-32-562'
        'Event Log Readers'                   = 'S-1-5-32-573'
        'Guests'                              = 'S-1-5-32-546'
        'Hyper-V Administrators'              = 'S-1-5-32-578'
        'IIS_IUSRS'                           = 'S-1-5-32-568'
        'Network Configuration Operators'     = 'S-1-5-32-556'
        'Performance Log Users'               = 'S-1-5-32-559'
        'Performance Monitor Users'           = 'S-1-5-32-558'
        'Power Users'                         = 'S-1-5-32-547'
        'Remote Desktop Users'                = 'S-1-5-32-555'
        'Remote Management Users'             = 'S-1-5-32-580'
        'Replicator'                          = 'S-1-5-32-552'
        'System Managed Accounts Group'       = 'S-1-5-32-581'
        'Users'                               = 'S-1-5-32-545'
    }
    $GroupSID[$GroupName]
}

function Add-LocalUserToGroup {
    [CmdletBinding()]
    param(
        [string] $Computer = $Env:ComputerName,
        [string] $User,
        [ValidateSet(
            'Access Control Assistance Operators',
            'Administrators'                     ,
            'Backup Operators'                   ,
            'Cryptographic Operators'            ,
            'Device Owners'                      ,
            'Distributed COM Users'              ,
            'Event Log Readers'                  ,
            'Guests'                             ,
            'Hyper-V Administrators'             ,
            'IIS_IUSRS'                          ,
            'Network Configuration Operators'    ,
            'Performance Log Users'              ,
            'Performance Monitor Users'          ,
            'Power Users'                        ,
            'Remote Desktop Users'               ,
            'Remote Management Users'            ,
            'Replicator'                         ,
            'System Managed Accounts Group'      ,
            'Users'
        )] $GroupName
    )
    # "Running Add-LocalUserToGroup" | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
    $LocalUsers = Get-WmiObject Win32_UserAccount -Filter "LocalAccount=true and Name LIKE '$User'"
    if ($LocalUsers) {
        $SID = Get-InvariantGroup -GroupName $GroupName
        $ObjSID = New-Object System.Security.Principal.SecurityIdentifier($SID)
        $LocalAdminsGroup = (($ObjSID.Translate([System.Security.Principal.NTAccount]) ).Value).Split("\")[1]
        try {
            Write-Verbose "Adding security principal: $User to the $LocalAdminsGroup group..."
            #"Add-LocalUserToGroup - Adding security principal: $User to the $LocalAdminsGroup group..." | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
            $group = [ADSI]"WinNT://$Computer/$LocalAdminsGroup,group"
            $ismember = $false
            @($group.Invoke("Members")) | ForEach-Object {
                If ($User -eq $_.GetType.Invoke().InvokeMember("Name", 'GetProperty', $null, $_, $null)) {
                    $ismember = $true
                }
            }
            If ($ismember -eq $true) {
                Write-Verbose "User $User is already a member of $localadminsgroup"
                #"Add-LocalUserToGroup- User $User is already a member of $localadminsgroup" | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
            } Else {
                $result = $group.Add("WinNT://$User,user")
                Write-Verbose "User $User is added to $localadminsgroup"
                #"Add-LocalUserToGroup - User $User is added to $localadminsgroup" | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
            }
        } Catch {
            $ErrorMessage = $_.Exception.Message -replace [System.Environment]::NewLine
            Write-Verbose "Add-LocalUserToGroup - $ErrorMessage"
            #"Add-LocalUserToGroup - Adding $User to $LocalAdminsGroup failed: $ErrorMessage" | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
        }
    } else {
        Write-Warning "Add-LocalUserToGroup - $User doesn't exists. Terminating."
        #"Add-LocalUserToGroup - $User doesn't exists. Terminating." | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
    }
}
New-AdminAccount -User 'LocalAdmin' -DisplayName 'Local Administrator'
Add-LocalUserToGroup -User 'LocalAdmin' -GroupName Administrators

Now all you have to do is to deploy this via GPO and you're good. Of course, code above is not the only way to do it. You could use NET USER or if you have PowerShell 5.1 New-LocalUser command. If you have that option using New-LocalUser would be my preffered way to do this.

This post was last modified on %s = human-readable time difference 16:46

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Upgrade Azure Active Directory Connect fails with unexpected error

Today, I made the decision to upgrade my test environment and update the version of…

1 month ago

Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster

Have you ever looked at your Active Directory and wondered, "Why do I still have…

2 months ago

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

7 months ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

11 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

1 year ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

1 year ago