Indented.SecurityPolicy.psm1

using namespace Indented.SecurityPolicy
using namespace System.Collections.Generic
using namespace System.Management.Automation
using namespace System.Security.Principal

enum AllocateDASD {
    Administrators
    AdministratorsAndPowerUsers
    AdministratorsAndInteractiveUsers
}

enum AuditNTLMInDomain {
    Disable                                = 0
    EnableForDomainAccountsToDomainServers = 1
    EnableForDomainAccounts                = 3
    EnableForDomainServers                 = 5
    EnableAll                              = 7
}

enum AuditReceivingNTLMTraffic {
    Disable
    EnableForDomainAccounts
    EnableForAllAccounts
}

enum ConsentPromptBehaviorAdmin {
    NoPrompting
    PromptForCredentialsOnSecureDesktop
    PromptForConsentOnSecureDesktop
    PromptForCredentials
    PromptForConsent
    PromptForConsentForNonWindowsBinaries
}

enum ConsentPromptBehaviorUser {
    AutomaticallyDeny                   = 0
    PromptForCredentialsOnSecureDesktop = 1
    PromptForCredentials                = 3
}

enum DontDisplayLockedUserId {
    All
    DisplayNameOnly
    None
}

enum ForceGuest {
    Classic
    GuestOnly
}

enum ForceKeyProtection {
    UserInputNotRequired
    PromptOnFirstUse
    PromptAlways
}

enum LdapClientIntegrity {
    None
    NegotiateSigning
    RequireSigning
}

enum LdapServerIntegrity {
    None
    RequireSigning
}

enum LmCompatibilityLevel {
    SendLMAndNTLMResponses
    SendLMAndNTLM
    SendNTLMResponseOnly
    SendNTLMv2ResponseOnly
    SendNTLMv2ResponseOnlyRefuseLM
    SendNTLMv2ResponseOnlyRefuseLMAndNTLM
}

enum NoConnectedUser {
    Disabled        = 0
    DenyAdd         = 1
    DenyAddAndLogon = 3
}

[Flags()]
enum NTLMMinSec {
    RequireNTLMv2SessionSecurity = 524288
    Require128BitEncryption      = 536870912
}

enum RestrictNTLMInDomain {
    Disable                           = 0
    DenyDomainAccountsToDomainServers = 1
    DenyDomainAccounts                = 3
    DenyDomainServers                 = 5
    DenyAll                           = 7
}

enum RestrictReceivingNTLMTraffic {
    AllowAll
    DenyDomainAccounts
    DenyAll
}

enum RestrictSendingNTLMTraffic {
    AllowAll
    AuditAll
    DenyAll
}

enum ScRemoveOption {
    NoAction
    LockWorkstation
    ForceLogoff
    Disconnect
}

enum SmbServerNameHardeningLevel {
    Off
    Accept
    Required
}

[Flags()]
enum SupportedEncryptionTypes {
    DES_CBC_CRC      = 1
    DES_CBC_MD5      = 2
    RC4_HMAC_MD5     = 4
    AES128_HMAC_SHA1 = 8
    AES256_HMAC_SHA1 = 16
    Future           = 2147483616
}

enum Enabled {
    Disabled
    Enabled
}

enum Ensure {
    Absent
    Present
}

enum RegistryValueType {
    DWord
    QWord
    String
    MultiString
    ExpandString
    Binary
}

[DscResource()]
class GroupManagedServiceAccount {
    [DscProperty()]
    [Ensure]$Ensure = 'Present'

    [DscProperty(Key)]
    [String]$Name

    [GroupManagedServiceAccount] Get() {
        if (Test-GroupManagedServiceAccount -AccountName $this.Name) {
            $this.Ensure = 'Present'
        } else {
            $this.Ensure = 'Absent'
        }

        return $this
    }

    [Void] Set() {
        if ($this.Ensure -eq 'Present' -and -not (Test-GroupManagedServiceAccount -AccountName $this.Name)) {
            Install-GroupManagedServiceAccount -AccountName $this.Name
        } elseif ($this.Ensure -eq 'Absent' -and (Test-GroupManagedServiceAccount -AccountName $this.Name)) {
            Uninstall-GroupManagedServiceAccount -AccountName $this.Name
        }
    }

    [Boolean] Test() {
        if ($this.Ensure -eq 'Present') {
            return Test-GroupManagedServiceAccount -AccountName $this.Name
        } elseif ($this.Ensure -eq 'Absent') {
            return -not (Test-GroupManagedServiceAccount -AccountName $this.Name)
        }
        return $true
    }
}

[DscResource()]
class RegistryPolicy {
    [DscProperty()]
    [Ensure]$Ensure = 'Present'

    [DscProperty(Key)]
    [String]$Name

    [DscProperty(Key)]
    [String]$Path

    [DscProperty()]
    [String[]]$Data = @()

    [DscProperty()]
    [RegistryValueType]$ValueType = 'String'

    Hidden [Object] $ParsedData

    Hidden [Boolean] CompareValue() {
        $value = Get-ItemPropertyValue -Path $this.Path -Name $this.Name
        if ($this.ValueType -eq 'MultiString') {
            if (Compare-Object @($this.ParsedData) @($value) -SyncWindow 0) {
                return $false
            }
        } elseif ($value -ne $this.ParsedData) {
            return $false
        }

        return $true
    }

    Hidden [Void] ParseData() {
        try {
            $this.ParsedData = switch ($this.ValueType) {
                'DWord'       { [UInt32]::Parse($this.Data[0]); break }
                'QWord'       { [UInt64]::Parse($this.Data[0]); break }
                'MultiString' { $this.Data; break }
                'Binary'      {
                    foreach ($value in $this.Data -split ' ') {
                        [Convert]::ToByte($value, 16)
                    }
                    break
                }
                default       { $this.Data[0] }
            }
        } catch {
            throw
        }
    }

    [RegistryPolicy] Get() {
        $this.ParseData()

        if (-not $this.Test()) {
            $this.Ensure = $this.Ensure -bxor 1
        }

        return $this
    }

    [Void] Set() {
        $this.ParseData()

        $params = @{
            Name  = $this.Name
            Path  = $this.Path
        }

        if ($this.Ensure -eq 'Present') {
            if (Test-Path $this.Path) {
                $key = Get-Item $this.Path
            } else {
                $key = New-Item $this.Path -ItemType Key -Force
            }

            if ($this.Name -in $key.GetValueNames()) {
                if ($key.GetValueKind($this.Name).ToString() -eq $this.ValueType) {
                    if (-not $this.CompareValue()) {
                        Set-ItemProperty -Value $this.ParsedData @params
                    }
                } else {
                    Remove-ItemProperty @params
                    New-ItemProperty -PropertyType $this.ValueType -Value $this.ParsedData @params
                }
            } else {
                New-ItemProperty -PropertyType $this.ValueType -Value $this.ParsedData @params
            }
        } elseif ($this.Ensure -eq 'Absent') {
            if (Test-Path $this.Path) {
                $key = Get-Item $this.Path
                if ($this.Name -in $key.GetValueNames()) {
                    Remove-ItemProperty @params
                }
            }
        }
    }

    [Boolean] Test() {
        $this.ParseData()

        if ($this.Ensure -eq 'Present') {
            if (-not (Test-Path $this.Path)) {
                return $false
            }

            $key = Get-Item $this.Path
            if ($key.GetValueNames() -notcontains $this.Name) {
                return $false
            }

            if ($key.GetValueKind($this.Name).ToString() -ne $this.ValueType) {
                return $false
            }

            return $this.CompareValue()
        } elseif ($this.Ensure -eq 'Absent') {
            if (Test-Path $this.Path) {
                $key = Get-Item $this.Path
                if ($key.GetValueNames() -contains $this.Name) {
                    return $false
                }
            }
        }

        return $true
    }
}

[DscResource()]
class SecurityOption {
    [DscProperty()]
    [Ensure]$Ensure = 'Present'

    [DscProperty(Key)]
    [String]$Name

    [DscProperty()]
    [String[]]$Value

    [DscProperty(NotConfigurable)]
    [String]$Description

    [Object]$ParsedValue

    Hidden [Void] ParseValue() {
        $securityOptionInfo = Resolve-SecurityOption $this.Name
        $valueType = $securityOptionInfo.ValueType -as [Type]

        $candidateValue = $this.Value
        if ($valueType.BaseType -ne [Array]) {
            $candidateValue = $this.Value[0]
        }
        if ($valueType.BaseType -eq [Enum]) {
            $enumValue = 0 -as $valueType
            if ($valueType::TryParse([String]$candidateValue, $true, [Ref]$enumValue)) {
                $this.ParsedValue = $enumValue
            }
        } else {
            $this.ParsedValue = $candidateValue -as $securityOptionInfo.ValueType
        }
    }

    Hidden [Boolean] CompareValue([Object]$ReferenceValue, [Object]$DifferenceValue) {
        if ($ReferenceValue -is [Array] -or $DifferenceValue -is [Array]) {
            return ($ReferenceValue -join ' ') -eq ($DifferenceValue -join ' ')
        } else {
            return $ReferenceValue -eq $DifferenceValue
        }
    }

    [SecurityOption] Get() {
        $securityOption = Get-SecurityOption -Name $this.Name

        $this.Name = $securityOption.Name
        $this.Value = $securityOption.Value
        $this.Description = $securityOption.Description

        return $this
    }

    [Void] Set() {
        if ($this.Ensure -eq 'Present') {
            $this.ParseValue()

            Set-SecurityOption -Name $this.Name -Value $this.ParsedValue
        } elseif ($this.Ensure -eq 'Absent') {
            Reset-SecurityOption -Name $this.Name
        }
    }

    [Boolean] Test() {
        $securityOption = Get-SecurityOption -Name $this.Name

        if ($this.Ensure -eq 'Present') {
            $this.ParseValue()

            return $this.CompareValue($this.ParsedValue, $securityOption.Value)
        } elseif ($this.Ensure -eq 'Absent') {
            $securityOptionInfo = Resolve-SecurityOption $this.Name

            return $this.CompareValue($securityOptionInfo.Default, $securityOption.Value)
        }

        return $true
    }
}

[DscResource()]
class UserRightAssignment {
    [DscProperty()]
    [Ensure]$Ensure = 'Present'

    [DscProperty(Key)]
    [String]$Name

    [DscProperty()]
    [String[]]$AccountName

    [DscProperty()]
    [Boolean]$Replace

    [DscProperty(NotConfigurable)]
    [String]$Description

    Hidden [SecurityIdentifier[]]$requestedIdentities

    Hidden [SecurityIdentifier[]]$currentIdentities

    Hidden [Void] InitializeRequest() {
        try {
            $userRight = Resolve-UserRight $this.Name
            if (@($userRight).Count -ne 1) {
                throw 'The requested user right is ambiguous, matched right names: {0}' -f
                    ($userRight.UserRight -join ', ')
            }
            $this.Name = $userRight.Name
            $this.Description = $userRight.Description
        } catch {
            throw
        }

        if ($this.Ensure -eq 'Present' -and $this.AccountName.Count -eq 0) {
            throw 'Invalid request. AccountName cannot be empty when ensuring a right is present.'
        }
        if ($this.Ensure -eq 'Absent' -and $this.Replace) {
            throw 'Replace may only be set when ensuring a set of accounts is present.'
        }

        $this.requestedIdentities = foreach ($identity in $this.AccountName) {
            ([NTAccount]$identity).Translate([SecurityIdentifier])
        }
        $this.currentIdentities = foreach ($identity in (Get-UserRight -Name $this.Name).AccountName) {
            if ($identity -is [NTAccount]) {
                $identity.Translate([SecurityIdentifier])
            } else {
                $identity
            }
        }
    }

    Hidden [Boolean] CompareAccountNames() {
        return [Boolean](-not (Compare-Object @($this.requestedIdentities) @($this.currentIdentities)))
    }

    Hidden [Boolean] IsAssignedRight([String]$Identity) {
        return $this.currentIdentities -contains ([NTAccount]$Identity).Translate([SecurityIdentifier])
    }

    [UserRightAssignment] Get() {
        try {
            $this.InitializeRequest()
            $this.AccountName = (Get-UserRight -Name $this.Name).AccountName

            return $this
        } catch {
            throw
        }
    }

    [Void] Set() {
        try {
            $this.InitializeRequest()
            if ($this.Ensure -eq 'Present') {
                if ($this.Replace) {
                    Set-UserRight -Name $this.Name -AccountName $this.AccountName
                } else {
                    foreach ($identity in $this.AccountName) {
                        if (-not $this.IsAssignedRight($identity)) {
                            Grant-UserRight -Name $this.Name -AccountName $identity
                        }
                    }
                }
            } elseif ($this.Ensure -eq 'Absent') {
                if ($this.AccountName.Count -eq 0 -and $this.currentIdentities.Count -gt 0) {
                    Clear-UserRight -Name $this.Name
                } elseif ($this.AccountName -gt 0) {
                    foreach ($identity in $this.AccountName) {
                        if ($this.IsAssignedRight($identity)) {
                            Revoke-UserRight -Name $this.Name -AccountName $identity
                        }
                    }
                }
            }
        } catch {
            throw
        }
    }

    [Boolean] Test() {
        try {
            $this.InitializeRequest()
            $userRight = Get-UserRight -Name $this.Name

            if ($this.Ensure -eq 'Present') {
                if ($this.Replace) {
                    if ($this.currentIdentities.Count -eq 0) {
                        return $false
                    }
                    return $this.CompareAccountNames()
                } else {
                    foreach ($identity in $this.AccountName) {
                        if (-not $this.IsAssignedRight($identity)) {
                            return $false
                        }
                    }
                }
            } elseif ($this.Ensure -eq 'Absent') {
                if ($this.AccountName.Count -eq 0 -and $userRight.AccountName) {
                    return $false
                } elseif ($this.AccountName.Count -gt 0) {
                    foreach ($identity in $this.AccountName) {
                        if ($this.IsAssignedRight($identity)) {
                            return $false
                        }
                    }
                }
            }

            return $true
        } catch {
            throw
        }
    }
}

class AccountBase {
    [WellKnownSidType]$SidType

    [SecurityIdentifier]$MachineSid = (GetMachineSid)

    [SecurityIdentifier] GetSid() {
        $domainRole = (Get-CimInstance Win32_ComputerSystem -Property DomainRole).DomainRole
        if ($domainRole -in 4, 5) {
            $searcher = [ADSISearcher]'(objectClass=domainDNS)'
            $null = $searcher.PropertiesToLoad.Add('objectSID')
            $domainSid = [SecurityIdentifier]::new($searcher.FindOne().Properties['objectSID'][0], 0)

            return [SecurityIdentifier]::new($this.SidType, $domainSid)
        } else {
            return [SecurityIdentifier]::new($this.SidType, $this.MachineSid)
        }
    }
}

class AccountStatus : AccountBase {
    [Enabled]$Value

    [AccountStatus] Get() {
        $localUser = GetIadsLocalUser -Sid $this.GetSid()
        $this.Value = [Enabled][Int]$localUser.Enabled

        return $this
    }

    [Void] Set() {
        if ([Boolean][Int]$this.Value) {
            EnableIadsLocalUser -Sid $this.GetSid()
        } else {
            DisableIadsLocalUser -Sid $this.GetSid()
        }
    }

    [Boolean] Test() {
        $localUser = GetIadsLocalUser -Sid $this.GetSid()

        return $localUser.Enabled -eq ([Boolean][Int]$this.Value)
    }
}

class AccountStatusAdministrator : AccountStatus {
    [WellKnownSidType]$SidType = 'AccountAdministratorSid'
}

class AccountStatusGuest : AccountStatus {
    [WellKnownSidType]$SidType = 'AccountGuestSid'
}

class RenameAccount : AccountBase {
    [String]$Value

    [RenameAccount] Get() {
        $this.Value = (GetIadsLocalUser -Sid $this.GetSid()).Name

        return $this
    }

    [Void] Set() {
        RenameIadsLocalUser -Sid $this.GetSid() -NewName $this.Value
    }

    [Boolean] Test() {
        $localUser = GetIadsLocalUser -Sid $this.GetSid()

        return $localUser.Name -eq $this.Value
    }
}

class RenameAccountAdministrator : RenameAccount {
    [WellKnownSidType]$SidType = 'AccountAdministratorSid'
}

class RenameAccountGuest : RenameAccount {
    [WellKnownSidType]$SidType = 'AccountGuestSid'
}

function CloseLsaPolicy {
    <#
    .SYNOPSIS
        Close the LSA policy handle if it is open.
    .DESCRIPTION
        Close the LSA policy handle if it is open.
    #>


    [CmdletBinding()]
    param (
        [AllowNull()]
        [Object]$lsa
    )

    if ($lsa) {
        $lsa.Dispose()
    }
}

function DisableIadsLocalUser {
    <#
    .SYNOPSIS
        ADSI based alternative to Disable-LocalUser.
    .DESCRIPTION
        This simple ADSI-based version of Disable-LocalUser allows use the AccountStatus security option on PowerShell Core.
    #>


    [CmdletBinding()]
    param (
        # The SID of a user.
        [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
        [SecurityIdentifier]$Sid
    )

    process {
        $iadsUser = GetIadsLocalUser -Sid $Sid -AsIadsUser

        $userFlags = $iadsUser.Get('userFlags')
        if (($userFlags -band 2) -eq 0) {
            $iadsUser.Put(
                'userFlags',
                $userFlags -bor 2
            )
            $iadsUser.SetInfo()
        }
    }
}

function EnableIadsLocalUser {
    <#
    .SYNOPSIS
        ADSI based alternative to Enable-LocalUser.
    .DESCRIPTION
        This simple ADSI-based version of Enable-LocalUser allows use the AccountStatus security option on PowerShell Core.
    #>


    [CmdletBinding()]
    param (
        # The SID of a user.
        [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
        [SecurityIdentifier]$Sid
    )

    process {
        $iadsUser = GetIadsLocalUser -Sid $Sid -AsIadsUser

        $userFlags = $iadsUser.Get('userFlags')
        if (($userFlags -band 2) -eq 2) {
            $iadsUser.Put(
                'userFlags',
                $userFlags -bxor 2
            )
            $iadsUser.SetInfo()
        }
    }
}

function GetIadsLocalUser {
    <#
    .SYNOPSIS
        ADSI based alternative to Get-LocalUser.
    .DESCRIPTION
        This simple ADSI-based version of Get-LocalUser allows use the AccountStatus and RenameAccount security options on PowerShell Core.
    #>


    [CmdletBinding()]
    param (
        # The exact name of a user.
        [String]$Name,

        # The SID of a user.
        [SecurityIdentifier]$Sid,

        # Return the IAdsUser object as-is.
        [Switch]$AsIadsUser
    )

    $iadsComputer = [ADSI]('WinNT://{0}' -f $env:COMPUTERNAME)
    $null = $iadsComputer.Children.SchemaFilter.Add('user')

    if ($Name) {
        $iadsUserCollection = $iadsComputer.Children.Find(
            $Name,
            'user'
        )
    } else {
        $iadsUserCollection = $iadsComputer.Children
    }

    foreach ($iadsUser in $iadsUserCollection) {
        $iadsUserSid = [SecurityIdentifier]::new([Byte[]]$iadsUser.Get('objectSID'), 0)

        if (-not $Sid -or $iadsUserSid -eq $Sid) {
            if ($AsIadsUser) {
                $iadsUser
            } else {
                [PSCustomObject]@{
                    Name    = $iadsUser.Get('Name')
                    Enabled = -not ($iadsUser.Get('userFlags') -band 2)
                    SID     = $iadsUserSid
                }
            }
        }
    }
}

function GetMachineSid {
    <#
    .SYNOPSIS
        Get the SID of the current machine.
    .DESCRIPTION
        Get the SID of the current machine.
 
        The current machine SID should not be confused with a SID used by Active Directory.
    #>


    [CmdletBinding()]
    [OutputType([System.Security.Principal.SecurityIdentifier])]
    param ( )

    [Indented.SecurityPolicy.Account]::LookupAccountName($env:COMPUTERNAME, $env:COMPUTERNAME)
}

function GetSecurityOptionData {
    <#
    .SYNOPSIS
        Get option data for the named security option.
    .DESCRIPTION
        Get option data for the named security option.
    #>


    [CmdletBinding()]
    param (
        # The name of the security option.
        [Parameter(Mandatory)]
        [String]$Name
    )

    if ($Script:securityOptionData.Contains($Name)) {
        $securityOptionData = $Script:securityOptionData[$Name]
        if (-not $securityOptionData.Name) {
            $securityOptionData.Name = $Name
        }
        $securityOptionData
    } else {
        $errorRecord = [ErrorRecord]::new(
            [ArgumentException]::new('{0} is an invalid security option name' -f $Name),
            'InvalidSecurityOptionName',
            'InvalidArgument',
            $Name
        )
        throw $errorRecord
    }
}

function ImportSecurityOptionData {
    <#
    .SYNOPSIS
        Import and merge localized security option data.
    .DESCRIPTION
        Import and merge localized security option data.
    #>


    [CmdletBinding()]
    param ( )

    try {
        $params = @{
            FileName        = 'securityOptions'
            BindingVariable = 'localizedSecurityOptions'
            BaseDirectory   = $myinvocation.MyCommand.Module.ModuleBase
            ErrorAction     = 'Stop'
        }
        Import-LocalizedData @params
    } catch {
        Import-LocalizedData @params -UICulture en
    }

    $path = Join-Path $myinvocation.MyCommand.Module.ModuleBase 'data\securityOptions.psd1'
    $Script:securityOptionData = Import-PowerShellDataFile $path

    # Create the lookup helper
    $Script:securityOptionLookupHelper = @{}
    # Merge localized descriptions and fill the helper
    foreach ($key in [String[]]$Script:securityOptionData.Keys) {
        $value = $Script:securityOptionData[$key]

        $description = $localizedSecurityOptions[$key]
        $category, $shortDescription = $description -split ': *', 2

        $value.Add('Category', $category)
        $value.Add('Description', $description)
        $value.Add('ShortDescription', $shortDescription)
        $value.Add('PSTypeName', 'Indented.SecurityPolicy.SecurityOptionInfo')

        if (-not $value.Contains('Name')) {
            $value.Add('Name', $key)
        }

        $value = [PSCustomObject]$value

        $Script:securityOptionData[$key] = $value
        $Script:securityOptionLookupHelper.Add($description, $key)
    }
}

function ImportUserRightData {
    <#
    .SYNOPSIS
        Import and merge localized user rights data.
    .DESCRIPTION
        Import and merge localized user rights data.
    #>


    [CmdletBinding()]
    param ( )

    try {
        $params = @{
            FileName        = 'userRights'
            BindingVariable = 'localizedUserRights'
            BaseDirectory   = $myinvocation.MyCommand.Module.ModuleBase
            ErrorAction     = 'Stop'
        }
        Import-LocalizedData @params
    } catch {
        Import-LocalizedData @params -UICulture en
    }

    $Script:userRightData = @{}
    $Script:userRightLookupHelper = @{}

    foreach ($key in $localizedUserRights.Keys) {
        $value = [PSCustomObject]@{
            Name        = [UserRight]$key
            Description = $localizedUserRights[$key]
            PSTypeName  = 'Indented.SecurityPolicy.UserRightInfo'
        }
        $Script:userRightData.Add($key, $value)
        $Script:userRightLookupHelper.Add($value.Description, $key)
    }
}

function NewImplementingType {
    <#
    .SYNOPSIS
        A short helper to create a named type.
    .DESCRIPTION
        A short helper to create a named type. Persent to help mocking.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [String]$Name
    )

    ($Name -as [Type])::new()
}

function OpenLsaPolicy {
    <#
    .SYNOPSIS
        Open the LSA policy handle.
    .DESCRIPTION
        Open the LSA policy handle.
    #>


    [CmdletBinding()]
    [OutputType([Indented.SecurityPolicy.Lsa])]
    param ( )

    try {
        return [Indented.SecurityPolicy.Lsa]::new()
    } catch {
        $innerException = $_.Exception.GetBaseException()
        if ($innerException -is [UnauthorizedAccessException]) {
            $exception = [UnauthorizedAccessException]::new('Cannot open LSA: Access denied', $innerException)
            $category = 'PermissionDenied'
        } else {
            $exception = [InvalidOperationException]::new('An error occurred when opening the LSA', $innerException)
            $category = 'OperationStopped'
        }
        $errorRecord = [ErrorRecord]::new(
            $exception,
            'CannotOpenLSA',
            $category,
            $null
        )
        throw $errorRecord
    }
}

function RenameIadsLocalUser {
    <#
    .SYNOPSIS
        ADSI based alternative to Rename-LocalUser.
    .DESCRIPTION
        This simple ADSI-based version of Rename-LocalUser allows use the RenameAccount security option on PowerShell Core.
    #>


    [CmdletBinding()]
    param (
        # The SID of a user.
        [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
        [SecurityIdentifier]$Sid,

        # The new name of the user.
        [Parameter(Mandatory)]
        [String]$NewName
    )

    process {
        $iadsUser = GetIadsLocalUser -Sid $Sid -AsIadsUser

        if ($iadsUser.Get('Name') -ne $NewName) {
            $iadsUser.Rename($NewName)
        }
    }
}

function Clear-UserRight {
    <#
    .SYNOPSIS
        Clears the accounts from the specified user right.
    .DESCRIPTION
        Clears the accounts from the specified user right.
    .EXAMPLE
        Clear-UserRight 'Log on as a batch job'
 
        Clear the SeBatchLogonRight right.
    #>


    [CmdletBinding()]
    param (
        # The name of the user right to clear.
        [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateScript( { $_ | Resolve-UserRight } )]
        [Alias('UserRight')]
        [String[]]$Name
    )

    begin {
        try {
            $lsa = OpenLsaPolicy
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }

    process {
        foreach ($userRight in $Name | Resolve-UserRight) {
            try {
                foreach ($identity in $lsa.EnumerateAccountsWithUserRight($userRight.Name)) {
                    $lsa.RemoveAccountRights($identity, [UserRight[]]$userRight.Name)
                }
            } catch {
                Write-Error -ErrorRecord $_
            }
        }
    }

    end {
        CloseLsaPolicy $lsa
    }
}

function Get-AssignedUserRight {
    <#
     .SYNOPSIS
        Gets all user rights granted to a principal.
     .DESCRIPTION
        Get a list of all the user rights granted to one or more principals. Does not expand group membership.
     .EXAMPLE
        Get-AssignedUserRight
 
        Returns a list of all defined for the current user.
    .EXAMPLE
        Get-AssignedUserRight Administrator
 
        Get the list of rights assigned to the administrator account.
    #>


    [CmdletBinding()]
    [OutputType('Indented.SecurityPolicy.AssignedUserRight')]
    param (
        # Find rights for the specified account names. By default AccountName is the current user.
        [Parameter(Position = 1, ValueFromPipelineByPropertyName, ValueFromPipeline)]
        [String[]]$AccountName = $env:USERNAME
    )

    begin {
        try {
            $lsa = OpenLsaPolicy
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }

    process {
        foreach ($account in $AccountName) {
            try {
                [PSCustomObject]@{
                    AccountName = $account
                    Name        = $lsa.EnumerateAccountRights($account)
                    PSTypeName  = 'Indented.SecurityPolicy.AssignedUserRight'
                }
            } catch {
                Write-Error -ErrorRecord $_
            }
        }
    }

    end {
        CloseLsaPolicy $lsa
    }
}

function Get-SecurityOption {
    <#
    .SYNOPSIS
        Get the value of a security option.
    .DESCRIPTION
        Get the value of a security option.
    .EXAMPLE
        Get-SecurityOption 'Accounts: Administrator account status'
 
        Gets the current value of the administrator account status policy (determined by the state of the account).
    .EXAMPLE
        Get-SecurityOption 'EnableLUA'
 
        Get the value of the "User Account Control: Run all administrators in Admin Approval Mode" policy.
    #>


    [CmdletBinding()]
    param (
        # The name of the security option to set.
        [Parameter(Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [String[]]$Name
    )

    process {
        foreach ($securityOptionInfo in $Name | Resolve-SecurityOption | Sort-Object Category, ShortDescription) {
            try {
                $value = $securityOptionInfo.Default

                if ($securityOptionInfo.Key) {
                    Write-Debug ('Registry value type: {0}' -f $securityOption.ValueName)

                    if (Test-Path $securityOptionInfo.Key) {
                        $key = Get-Item $securityOptionInfo.Key -ErrorAction Stop

                        if ($key.GetValueNames() -contains $securityOptionInfo.Name) {
                            $value = Get-ItemPropertyValue -Path $securityOptionInfo.Key -Name $securityOptionInfo.Name -ErrorAction Stop
                        }
                    }
                } else {
                    Write-Debug ('Class-handled value type: {0}' -f $securityOptionInfo.Name)

                    $class = NewImplementingType $securityOptionInfo.Class
                    $value = $class.Get().Value
                }

                if ($value -ne 'Not Defined') {
                    $value = $value -as ($securityOptionInfo.ValueType -as [Type])
                }

                [PSCustomObject]@{
                    Name             = $securityOptionInfo.Name
                    Description      = $securityOptionInfo.Name
                    Value            = $value
                    Category         = $securityOptionInfo.Category
                    ShortDescription = $securityOptionInfo.ShortDescription
                    PSTypeName       = 'Indented.SecurityPolicy.SecurityOptionSetting'
                }
            } catch {
                $innerException = $_.Exception.GetBaseException()
                $errorRecord = [ErrorRecord]::new(
                    [InvalidOperationException]::new(
                        ('An error occurred retrieving the security option {0}: {1}' -f $securityOptionInfo.ValueName, $innerException.Message),
                        $innerException
                    ),
                    'FailedToRetrieveSecurityOptionSetting',
                    'OperationStopped',
                    $null
                )
                Write-Error -ErrorRecord $errorRecord
            }
        }
    }
}

function Get-UserRight {
    <#
     .SYNOPSIS
       Gets all accounts that are assigned a specified user right.
     .DESCRIPTION
       Gets a list of all accounts that hold a specified user right.
 
       Group membership is not evaluated, the values returned are explicitly listed under the specified user rights.
     .EXAMPLE
       Get-UserRight SeServiceLogonRight
 
       Returns a list of all accounts that hold the "Log on as a service" right.
     .EXAMPLE
       Get-UserRight SeServiceLogonRight, SeDebugPrivilege
 
       Returns accounts with the SeServiceLogonRight and SeDebugPrivilege rights.
    #>


    [CmdletBinding()]
    [OutputType('Indented.SecurityPolicy.UserRight')]
    param (
        # The user right, or rights, to query.
        [Parameter(Position = 1, ValueFromPipelineByPropertyName, ValueFromPipeline)]
        [ValidateScript( { $_ | Resolve-UserRight } )]
        [Alias('UserRight')]
        [String[]]$Name
    )

    begin {
        try {
            $lsa = OpenLsaPolicy
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }

    process {
        foreach ($userRight in $Name | Resolve-UserRight) {
            try {
                [PSCustomObject]@{
                    Name        = $userRight.Name
                    Description = $userRight.Description
                    AccountName = $lsa.EnumerateAccountsWithUserRight($userRight.Name)
                    PSTypeName  = 'Indented.SecurityPolicy.UserRightSetting'
                }
            } catch {
                $innerException = $_.Exception.GetBaseException()
                $errorRecord = [ErrorRecord]::new(
                    [InvalidOperationException]::new(
                        ('An error occurred retrieving the user right {0}: {1}' -f $userRight.Name, $innerException.Message),
                        $innerException
                    ),
                    'FailedToRetrieveUserRight',
                    'OperationStopped',
                    $null
                )
                Write-Error -ErrorRecord $errorRecord
            }
        }
    }

    end {
        CloseLsaPolicy $lsa
    }
}

function Grant-UserRight {
    <#
    .SYNOPSIS
        Grant rights to an account.
    .DESCRIPTION
        Grants one or more rights to the specified accounts.
    .EXAMPLE
        Grant-UserRight -Name 'Allow logon locally' -AccountName 'Administrators'
 
        Grants the allow logon locally right to the Administrators group.
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param (
        # The user right, or rights, to query.
        [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
        [ValidateScript( { $_ | Resolve-UserRight } )]
        [Alias('UserRight')]
        [String[]]$Name,

        # Grant rights to the specified accounts. Each account may be a string, an NTAccount object, or a SecurityIdentifier object.
        [Parameter(Mandatory, Position = 2, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Object[]]$AccountName
    )

    begin {
        try {
            $lsa = OpenLsaPolicy
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }

    process {
        foreach ($account in $AccountName) {
            try {
                $userRights = $Name | Resolve-UserRight

                if ($pscmdlet.ShouldProcess(('Adding {0} to {1}' -f $account, $userRights.Name -join ', '))) {
                    $lsa.AddAccountRights($account, $userRights.Name)
                }
            } catch {
                Write-Error -ErrorRecord $_
            }
        }
    }

    end {
        CloseLsaPolicy $lsa
    }
}

function Install-GroupManagedServiceAccount {
    <#
    .SYNOPSIS
        Adds a Group Managed Service Account to the local machine.
    .DESCRIPTION
        Adds a Group Managed Service Account to the local machine.
    .EXAMPLE
        Install-GroupManagedServiceAccount -AccountName domain\name$
    #>


    [CmdletBinding()]
    param (
        # The name of the Group Managed Service Account.
        [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [String]$AccountName
    )

    process {
        try {
            [ServiceAccount]::AddServiceAccount($AccountName)
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }
}

function Reset-SecurityOption {
    <#
    .SYNOPSIS
        Reset the value of a security option to its default.
    .DESCRIPTION
        Reset the value of a security option to its default.
    .EXAMPLE
        Reset-SecurityOption FIPSAlgorithmPolicy
 
        Resets the FIPSAlgorithmPolicy policy to the default value, Disabled.
    .EXAMPLE
        Reset-SecurityOption 'Interactive logon: Message text for users attempting to log on'
 
        Resets the LegalNoticeText policy to an empty string.
    .EXAMPLE
        Reset-SecurityOption ForceKeyProtection
 
        Resets the ForceKeyProtection policy to "Not Defined" by removing the associated registry value.
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param (
        # The name of the security option to set.
        [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [String[]]$Name
    )

    process {
        foreach ($securityOptionInfo in $Name | Resolve-SecurityOption | Sort-Object Category, ShortDescription) {
            $value = $securityOptionInfo.Default

            if ($value -eq 'Not Defined' -and $securityOptionInfo.Key) {
                if (Test-Path $securityOptionInfo.Key) {
                    $key = Get-Item -Path $securityOptionInfo.Key
                    if ($key.GetValueNames() -contains $securityOptionInfo.Name) {
                        Remove-ItemProperty -Path $key.PSPath -Name $securityOptionInfo.Name
                    }
                }
            } else {
                Set-SecurityOption -Name $securityOptionInfo.Name -Value $value
            }
        }
    }
}

function Resolve-SecurityOption {
    <#
    .SYNOPSIS
        Resolves the name of a security option as shown in the local security policy editor.
    .DESCRIPTION
        Resolves the name of a security option as shown in the local security policy editor to either the registry value name, or the name of an implementing class.
    .EXAMPLE
        Resolve-SecurityOption "User Account Control: Run all administrators in Admin Approval Mode"
 
        Returns information about the security option.
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
    [CmdletBinding()]
    param (
        # The name or description of a user right. Wildcards are supported for description values.
        [Parameter(Position = 1, ValueFromPipeline, ParameterSetName = 'ByName')]
        [String]$Name,

        # Get the policies under a specific category, for example "Network security".
        [Parameter(Mandatory, ParameterSetName = 'ByCategory')]
        [ArgumentCompleter( {
            param (
                [String]$CommandName,
                [String]$ParameterName,
                [String]$WordToComplete
            )

            [System.Collections.Generic.HashSet[String]](Resolve-SecurityOption).Category -like "$WordToComplete*"
        } )]
        [String]$Category
    )

    process {
        if ($Name) {
            if ($Script:securityOptionData.Contains($Name)) {
                $Script:securityOptionData[$Name]
            } elseif ($Script:securityOptionLookupHelper.Contains($Name)) {
                $Script:securityOptionData[$Script:securityOptionLookupHelper[$Name]]
            } else {
                $isLikeDescription = $false
                foreach ($value in $Script:securityOptionLookupHelper.Keys -like $Name) {
                    $isLikeDescription = $true
                    $Script:securityOptionData[$Script:securityOptionLookupHelper[$value]]
                }
                if (-not $isLikeDescription) {
                    $errorRecord = [ErrorRecord]::new(
                        [ArgumentException]::new('"{0}" does not resolve to a security option' -f $Name),
                        'CannotResolveSecurityOption',
                        'InvalidArgument',
                        $Name
                    )
                    $pscmdlet.ThrowTerminatingError($errorRecord)
                }
            }
        } elseif ($Category) {
            $Script:securityOptionData.Values.Where{ $_.Category -like $Category }
        } else {
            $Script:securityOptionData.Values
        }
    }
}

function Resolve-UserRight {
    <#
    .SYNOPSIS
        Resolves the name of a user right as shown in the local security policy editor to its constant name.
    .DESCRIPTION
        Resolves the name of a user right as shown in the local security policy editor to its constant name.
    .EXAMPLE
        Resolve-UserRight "Generate security audits"
 
        Returns the value SeAuditPrivilege.
    .EXAMPLE
        Resolve-UserRight "*batch*"
 
        Returns SeBatchLogonRight and SeDenyBatchLogonRight via the description.
    .EXAMPLE
        Resolve-UserRight SeBatchLogonRight
 
        Returns the name and description of the user right.
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
    [CmdletBinding()]
    param (
        # The name or description of a user right. Wildcards are supported for description values.
        [Parameter(Position = 1, ValueFromPipeline)]
        [String]$Name
    )

    process {
        if ($Name) {
            if ($Script:userRightData.Contains($Name)) {
                $Script:userRightData[$Name]
            } elseif ($Script:userRightLookupHelper.Contains($Name)) {
                $Script:userRightData[$Script:userRightLookupHelper[$Name]]
            } else {
                $isLikeDescription = $false
                foreach ($value in $Script:userRightLookupHelper.Keys -like $Name) {
                    $isLikeDescription = $true
                    $Script:userRightData[$Script:userRightLookupHelper[$value]]
                }
                if (-not $isLikeDescription) {
                    $errorRecord = [ErrorRecord]::new(
                        [ArgumentException]::new('"{0}" does not resolve to a user right' -f $Name),
                        'UserRightCannotResolve',
                        'InvalidArgument',
                        $Name
                    )
                    $pscmdlet.ThrowTerminatingError($errorRecord)
                }
            }
        } else {
            $Script:userRightData.Values
        }
    }
}

function Revoke-UserRight {
    <#
    .SYNOPSIS
        Revokes rights granted to an account.
    .DESCRIPTION
        Revokes the rights granted to an account or set of accounts.
 
        The All switch may be used to revoke all rights granted to the specified accounts.
    .EXAMPLE
        Revoke-UserRight -Name 'Log on as a service' -AccountName 'JonDoe'
 
        Revokes the logon as a service right granted to the account named JonDoe.
    .EXAMPLE
        Revoke-UserRight -AccountName 'JonDoe' -All
 
        Revokes all rights which have been granted to the identity JonDoe.
    #>


    [CmdletBinding(DefaultParameterSetName = 'List', SupportsShouldProcess)]
    param (
        # The user right, or rights, to query.
        [Parameter(Mandatory, Position = 1, ParameterSetName = 'List')]
        [ValidateScript( { $_ | Resolve-UserRight } )]
        [Alias('UserRight')]
        [String[]]$Name,

        # Grant rights to the specified principals. The principal may be a string, an NTAccount object, or a SecurityIdentifier object.
        [Parameter(Mandatory, Position = 2)]
        [Object[]]$AccountName,

        # Clear all rights granted to the specified accounts.
        [Parameter(Mandatory, ParameterSetName = 'All')]
        [Switch]$AllRights
    )

    begin {
        try {
            $lsa = OpenLsaPolicy
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }

    process {
        foreach ($account in $AccountName) {
            try {
                if ($pscmdlet.ParameterSetName -eq 'All' -and $AllRights) {
                    if ($pscmdlet.ShouldProcess(('Removing all rights from {0}' -f $account))) {
                        $lsa.RemoveAllAccountRights($account)
                    }
                } elseif ($pscmdlet.ParameterSetName -eq 'List') {
                    $userRights = $Name | Resolve-UserRight

                    if ($pscmdlet.ShouldProcess(('Removing {0} from {1}' -f $account, $userRights.Name -join ', '))) {
                        $lsa.RemoveAccountRights($account, $userRights.Name)
                    }
                }
            } catch {
                Write-Error -ErrorRecord $_
            }
        }
    }

    end {
        CloseLsaPolicy $lsa
    }
}

function Set-SecurityOption {
    <#
    .SYNOPSIS
        Set the value of a security option.
    .DESCRIPTION
        Set the value of a security option.
    .PARAMETER Value
        The value to set.
    .EXAMPLE
        Set-SecurityOption EnableLUA Enabled
 
        Enables the "User Account Control: Run all administrators in Admin Approval Mode" policy.
    .EXAMPLE
        Set-SecurityOption LegalNoticeText ''
 
        Sets the value of the LegalNoticeText policy to an empty string.
    .EXAMPLE
 
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param (
        # The name of the security option to set.
        [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [String]$Name,

        # The value to set.
        [Parameter(Mandatory, Position = 2, ValueFromPipelineByPropertyName)]
        [AllowNull()]
        [AllowEmptyString()]
        [AllowEmptyCollection()]
        [Object]$Value
    )

    process {
        if ($value -eq 'Not Defined') {
            $errorRecord = [ErrorRecord]::new(
                [ArgumentException]::new('Cannot set "Not Defined" for {0}. Please use Reset-SecurityOption' -f $Name),
                'CannotUseSetToReset',
                'InvalidArgument',
                $Value
            )
            $pscmdlet.ThrowTerminatingError($errorRecord)
        }

        $securityOptionInfo = Resolve-SecurityOption $Name

        $valueType = $securityOptionInfo.ValueType -as [Type]
        if ($valueType.BaseType -eq [Enum]) {
            $parsedValue = 0 -as $valueType
            if ($valueType::TryParse([String]$Value, $true, [Ref]$parsedValue)) {
                $Value = $parsedValue
            } else {
                $errorRecord = [ErrorRecord]::new(
                    [ArgumentException]::new(('{0} is not a valid value for {1}. Valid values are: {2}' -f
                        $Value,
                        $securityOptionInfo.ValueName,
                        ([Enum]::GetNames($valueType) -join ', ')
                    )),
                    'InvalidValueForSecurityOption',
                    'InvalidArgument',
                    $Value
                )
                $pscmdlet.ThrowTerminatingError($errorRecord)
            }
        }

        try {
            if ($securityOptionInfo.Key) {
                Write-Debug ('Registry value type: {0}' -f $securityOption.Name)

                if (Test-Path $securityOptionInfo.Key) {
                    $key = Get-Item -Path $securityOptionInfo.Key
                } else {
                    $key = New-Item -Path $securityOptionInfo.Key -ItemType Key -Force
                }

                if ($key.GetValueNames() -contains $securityOptionInfo.Name) {
                    $currentValue = Get-ItemPropertyValue -Path $key.PSPath -Name $securityOptionInfo.Name

                    $shouldSet = $false
                    if ($value -is [Array] -or $currentValue -is [Array]) {
                        $shouldSet = ($currentValue -join ' ') -ne ($value -join ' ')
                    } elseif ($currentValue -ne $Value) {
                        $shouldSet = $true
                    }

                    if ($shouldSet -and $pscmdlet.ShouldProcess(('Setting policy {0} to {1}' -f $securityOption.Name, $Value))) {
                        Set-ItemProperty -Path $key.PSPath -Name $securityOptionInfo.Name -Value $Value
                    }
                } else {
                    $propertyType = switch ($securityOptionInfo.ValueType) {
                        { $valueType.BaseType -eq [Enum] } { $_ = ($_ -as [Type]).GetEnumUnderlyingType().Name }
                        'Int32'                            { 'DWord'; break }
                        'Int64'                            { 'QWord'; break }
                        'String'                           { 'String'; break }
                        'String[]'                         { 'MultiString'; break }
                        default                            { throw 'Invalid or unhandled registry property type' }
                    }

                    if ($pscmdlet.ShouldProcess(('Setting policy {0} to {1} with value type {2}' -f $securityOption.Name, $Value, $propertyType))) {
                        New-ItemProperty -Path $key.PSPath -Name $securityOptionInfo.Name -Value $Value -PropertyType $propertyType
                    }
                }
            } else {
                Write-Debug ('Class-handled value type: {0}' -f $securityOption.Name)

                $class = NewImplementingType $securityOptionInfo.Class
                $class.Value = $Value

                if (-not $class.Test()) {
                    if ($pscmdlet.ShouldProcess(('Setting policy {0} to {1}' -f $securityOption.Name, $Value))) {
                        $class.Set()
                    }
                }
            }
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }
}

function Set-UserRight {
    <#
    .SYNOPSIS
        Set the value of a user rights assignment to the specified list of principals.
    .DESCRIPTION
        Set the value of a user rights assignment to the specified list of principals, replacing any existing entries.
    .EXAMPLE
        Set-UserRight -Name SeShutdownPrivilege -AccountName 'Administrators'
 
        Replaces the accounts granted the SeShutdownPrivilege right with Administrators.
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param (
        # The user right, or rights, to query.
        [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
        [ValidateScript( { $_ | Resolve-UserRight } )]
        [Alias('UserRight')]
        [String[]]$Name,

        # The list of identities which should set for the specified policy.
        [Parameter(Mandatory, Position = 2, ValueFromPipelineByPropertyName)]
        [Object[]]$AccountName
    )

    begin {
        try {
            $lsa = OpenLsaPolicy
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }

    process {
        try {
            # Ensure all identity values are SecurityIdentifier type
            $requestedIdentities = foreach ($account in $AccountName) {
                $securityIdentifier = switch ($account.GetType()) {
                    ([String])    { ([NTAccount]$account).Translate([SecurityIdentifier]); break }
                    ([NTAccount]) { $account.Translate([SecurityIdentifier]); break }
                    default       { $account }
                }

                [PSCustomObject]@{
                    Value              = $account
                    SecurityIdentifier = $securityIdentifier
                }
            }

            foreach ($userRight in $Name | Resolve-UserRight) {
                # Get the current value and ensure all returned values are SecurityIdentifier type
                $currentIdentities = foreach ($account in $lsa.EnumerateAccountsWithUserRight($userRight.Name)) {
                    $securityIdentifier = if ($account -is [NTAccount]) {
                        $account.Translate([SecurityIdentifier])
                    } else {
                        $account
                    }

                    [PSCustomObject]@{
                        Value              = $account
                        SecurityIdentifier = $securityIdentifier
                    }
                }

                foreach ($account in Compare-Object @($requestedIdentities) @($currentIdentities) -Property SecurityIdentifier -PassThru) {
                    if ($account.SideIndicator -eq '<=') {
                        if ($pscmdlet.ShouldProcess(('Adding {0} to user right {1}' -f $account.Value, $userRight.Name))) {
                            $lsa.AddAccountRights($account.SecurityIdentifier, $userRight.Name)
                        }
                    } elseif ($account.SideIndicator -eq '=>') {
                        if ($pscmdlet.ShouldProcess(('Removing {0} from user right {1}' -f $account.Value, $userRight.Name))) {
                            $lsa.RemoveAccountRights($account.SecurityIdentifier, [UserRight[]]$userRight.Name)
                        }
                    }
                }
            }
        } catch {
            Write-Error -ErrorRecord $_
        }
    }

    end {
        CloseLsaPolicy $lsa
    }
}

function Test-GroupManagedServiceAccount {
    <#
    .SYNOPSIS
        Test whether or not a Group Managed Service Account is installed in the NetLogon store.
    .DESCRIPTION
        Test whether or not a Group Managed Service Account is installed in the NetLogon store.
    #>


    [CmdletBinding()]
    [OutputType([Boolean])]
    param (
        # The name of the Group Managed Service Account.
        [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [String]$AccountName
    )

    process {
        try {
            return [Indented.SecurityPolicy.ServiceAccount]::IsServiceAccount($AccountName)
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }
}

function Uninstall-GroupManagedServiceAccount {
    <#
    .SYNOPSIS
        Uninstalls a Group Managed Service Account from the local machine.
    .DESCRIPTION
        Uninstalls a Group Managed Service Account from the local machine.
    #>


    [CmdletBinding()]
    param (
        # The name of the Group Managed Service Account.
        [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [String]$AccountName
    )

    process {
        try {
            if (Test-GroupManagedServiceAccount $AccountName) {
                [Indented.SecurityPolicy.ServiceAccount]::RemoveServiceAccount($AccountName)
            } else {
                $errorRecord = [ErrorRecord]::new(
                    [ArgumentException]::new('The specified account, {0}, is not installed' -f $AccountName),
                    'GMSANotInstalled',
                    'InvalidArgument',
                    $AccountName
                )
                $pscmdlet.ThrowTerminatingError($errorRecord)
            }
        } catch {
            $pscmdlet.ThrowTerminatingError($_)
        }
    }
}

function InitializeModule {
    ImportSecurityOptionData
    ImportUserRightData

    $manifest = Join-Path $myinvocation.MyCommand.Module.ModuleBase ('{0}.psd1' -f $myinvocation.MyCommand.Module.Name)
    $commands = (Import-PowerShellDataFile $manifest).FunctionsToExport
    Register-ArgumentCompleter -CommandName ($commands -like '*UserRight') -ParameterName Name -ScriptBlock {
        param (
            [String]$CommandName,

            [String]$ParameterName,

            [String]$WordToComplete
        )

        [Indented.SecurityPolicy.UserRight].GetEnumValues().Where{
            $_ -like "$WordToComplete*"
        }
    }

    Register-ArgumentCompleter -CommandName ($commands -like '*SecurityOption') -ParameterName Name -ScriptBlock {
        param (
            [String]$CommandName,

            [String]$ParameterName,

            [String]$WordToComplete
        )

        (Resolve-SecurityOption | Where-Object Name -like "$WordToComplete*").Name
    }
}

InitializeModule