<# .SYNOPSIS Creates (or repairs) a dedicated local admin account with a random, never-seen password, ready for Windows LAPS to manage. .DESCRIPTION Makes sure a named local account exists, is enabled, and is in the local Administrators group. When it creates the account, it gives it a long random password generated on the device and never written, logged, or returned. Nobody knows it, and nobody needs to: point your Windows LAPS policy's AdministratorAccountName at this account and LAPS takes over the password from there. Optionally disables the built-in Administrator (RID 500). Safe to run again; it only fixes what's off. .PARAMETER Name The account name. 20 characters max, no \ / [ ] : ; | = , + * ? < > @ or quotes. .PARAMETER Description Description shown on the account. .PARAMETER DisableBuiltInAdministrator Also disable the built-in Administrator account. .EXAMPLE .\Initialize-LapsAdminAccount.ps1 -Name lapsadmin -WhatIf .EXAMPLE .\Initialize-LapsAdminAccount.ps1 -Name lapsadmin -DisableBuiltInAdministrator #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [ValidateLength(1, 20)] [ValidatePattern('^[^\\/\[\]:;|=,+*?<>@"]+$')] [string]$Name, [ValidateLength(0, 48)][string]$Description = 'Local admin managed by Windows LAPS', [switch]$DisableBuiltInAdministrator ) function New-RandomSecureString { param([int]$Length = 32) $chars = [char[]]'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!#%+-=?@_' $bytes = New-Object byte[] $Length $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() $rng.GetBytes($bytes); $rng.Dispose() $secure = New-Object System.Security.SecureString foreach ($b in $bytes) { $secure.AppendChar($chars[$b % $chars.Length]) } $secure.MakeReadOnly() $secure } function Write-Step { param([string]$Step, [string]$Status, [string]$Detail = '') [pscustomobject]@{ ComputerName = $env:COMPUTERNAME; Account = $Name; Step = $Step; Status = $Status; Detail = $Detail } if ($Status -eq 'Failed') { $script:failed = $true } } $failed = $false $adminsGroup = Get-LocalGroup -SID 'S-1-5-32-544' # "Administrators" in any language # 1. The account itself. $user = Get-LocalUser -Name $Name -ErrorAction SilentlyContinue if (-not $user) { if ($PSCmdlet.ShouldProcess($Name, 'Create local account with a random password')) { try { $user = New-LocalUser -Name $Name -Password (New-RandomSecureString) -Description $Description -PasswordNeverExpires -AccountNeverExpires -ErrorAction Stop Write-Step -Step 'Create account' -Status 'Done' } catch { Write-Step -Step 'Create account' -Status 'Failed' -Detail $_.Exception.Message } } else { Write-Step -Step 'Create account' -Status 'WhatIf' Write-Step -Step 'Add to Administrators' -Status 'WhatIf' } } else { Write-Step -Step 'Create account' -Status 'AlreadyExists' if (-not $user.Enabled -and $PSCmdlet.ShouldProcess($Name, 'Enable account')) { try { Enable-LocalUser -Name $Name -ErrorAction Stop; Write-Step -Step 'Enable account' -Status 'Done' } catch { Write-Step -Step 'Enable account' -Status 'Failed' -Detail $_.Exception.Message } } } # 2. Administrators membership. Add-LocalGroupMember tells us if it's already there, which dodges # Get-LocalGroupMember's habit of failing on groups that contain orphaned SIDs. if ($user -and $PSCmdlet.ShouldProcess($adminsGroup.Name, "Add $Name")) { try { Add-LocalGroupMember -Group $adminsGroup -Member $user -ErrorAction Stop Write-Step -Step 'Add to Administrators' -Status 'Done' } catch [Microsoft.PowerShell.Commands.MemberExistsException] { Write-Step -Step 'Add to Administrators' -Status 'AlreadyMember' } catch { Write-Step -Step 'Add to Administrators' -Status 'Failed' -Detail $_.Exception.Message } } # 3. Optionally switch off the built-in Administrator, but never the account we just set up. if ($DisableBuiltInAdministrator) { $builtIn = Get-LocalUser | Where-Object { $_.SID.Value -match '^S-1-5-21-.+-500$' } if ($builtIn.Name -eq $Name) { Write-Step -Step 'Disable built-in Administrator' -Status 'Skipped' -Detail 'That is the account you named.' } elseif (-not $builtIn.Enabled) { Write-Step -Step 'Disable built-in Administrator' -Status 'AlreadyDisabled' } elseif ($PSCmdlet.ShouldProcess($builtIn.Name, 'Disable built-in Administrator')) { try { Disable-LocalUser -SID $builtIn.SID -ErrorAction Stop; Write-Step -Step 'Disable built-in Administrator' -Status 'Done' } catch { Write-Step -Step 'Disable built-in Administrator' -Status 'Failed' -Detail $_.Exception.Message } } } exit ([int]$failed)