public/add-rbacserviceaccount.ps1

function Add-RBACServiceAccount {
    [CmdletBinding(DefaultParameterSetName = "Standard")]
    param (
        # Name of service account function.
        # If the name is shorter than 5 characters, the org and component names will be integrated.
        # If shorter than 9 characters, the org name will be integrated
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateLength(3, 13)]
        [validatePattern("[a-zA-Z][a-zA-Z0-9#()+&]+")]
        [String]
        $Name,

        # Plaintext description for documentation purposes
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [String]
        $Description,

        # Org owning the component that the Service Account lives in
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateScript({ [bool](get-rbacOrg -org $_) })]
        [ArgumentCompleter( {
                param( $commandName, $parameterName, $wordToComplete, $commandAST, $fakeBoundParameters)
                if ($fakeBoundParameters.containsKey('Component')) {
                (get-rbacComponent -org "$wordToComplete*" -component $fakeBoundParameters.Component | Sort-Object -Unique Org).org
                }
                else {
                (get-rbacOrg -org "$wordToComplete*").org
                }
            })]
        [String]$Org,

        # Component that the Service Account lives in
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateScript({ $(get-rbacComponent).component.contains($_) })]
        [ArgumentCompleter( {
                param( $commandName, $parameterName, $wordToComplete, $commandAST, $fakeBoundParameters)
                if ($fakeBoundParameters.containsKey('Org')) {
                (get-rbacComponent -org $fakeBoundParameters.Org -component "$wordToComplete*" | Sort-Object -Unique Component).Component
                }
                else {
                (get-rbacComponent -component "$wordToComplete*").Component
                }
            })]
        [String]$Component,

        # Indicates Managed Service Account
        [Alias("MSA", "M")]
        [Parameter(ParameterSetName = "MSA", ValueFromPipelineByPropertyName)]
        [Switch]
        $ManagedServiceAccount,

        # Indicates Group Managed Service Account
        [Alias("GMSA", "G")]
        [Parameter(ParameterSetName = "GMSA", ValueFromPipelineByPropertyName)]
        [Switch]
        $GroupManagedServiceAccount,

        # DNS host that will be accessing the MSA
        [Parameter(ParameterSetName = "MSA", Mandatory)]
        [Parameter(ParameterSetName = "GMSA", Mandatory)]
        [ValidateScript({ [bool](get-adcomputer -server $server $_ ) })]
        [ArgumentCompleter( {
                param ( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters )
                if ($fakeBoundParameters.containsKey('Org') -and $fakeBoundParameters.containsKey('Component')) {
                    $searchBase = (get-rbacComponent -org $fakeBoundParameters.org -component $fakeBoundParameters.Component -detailed).distinguishedname
                }
                elseif ($fakeBoundParameters.containsKey('Component')) {
                    $searchBase = (get-rbacComponent -component $fakeBoundParameters.Component -detailed).distinguishedname
                }
                elseif ($fakeBoundParameters.containsKey('Org')) {
                    $searchBase = (get-rbacOrg -org $fakeBoundParameters.org -detailed).distinguishedname
                }
                else {
                    write-loghandler -level "warning" -message " Autocomplete: Be more specific"
                    return
                }
                $results = (get-adcomputer -server $server -filter "name -like '$wordToComplete*'" -searchBase $SearchBase -SearchScope subTree).name
                if ($results.count -gt 0) {
                    # Clear out prior error messages
                    Write-Host " "
                    $results
                }
                else {
                    write-loghandler -level "warning" -message " AutoComplete: No matching hosts"
                }
            })]
        [String]
        $TrustedHost,

        # DNS hostname for the MSA / GMSA
        [Parameter(ParameterSetName = "MSA")]
        [Parameter(ParameterSetName = "GMSA")]
        [String]
        $DNSHostName,

        # Show plaintext password
        [Parameter()]
        [Switch]
        $DisplayPassword,

        # Flavor of service account to set some sane defaults
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('ADFS')]
        [String]
        $Personality,
        [Microsoft.ActiveDirectory.Management.ADDirectoryServer]$Server = (get-addomainController -Writable -Discover)

    )
    Begin {
        $CreatedAccounts = [System.Collections.Generic.List[PSCustomObject]]::new()
        $SvcPrefixLookup = @{
            GMSA = @{
                prefix          = "G"
                Type            = "GMSA"
                namelengthlimit = 15

            }
            MSA  = @{
                prefix          = "M"
                Type            = "MSA"
                namelengthlimit = 15
            }
            SVC  = @{
                Prefix          = "S"
                Type            = "Legacy"
                namelengthlimit = 15
            }
        }
    }
    Process {
        $ComponentObject = get-rbacComponent -org $org -component $component -detailed
        $RightsPrefix = "{0}-{1}" -f $Settings.Names.RightsName, $ComponentObject.objectMidName
        $TypeParams = If ($GroupManagedServiceAccount -or $ManagedServiceAccount) {
            if ($GroupManagedServiceAccount) {
                $SvcPrefixLookup["GMSA"]
            } else {
                $SvcPrefixLookup["MSA"]
            }
            $TrustedHostComputerObjects = $TrustedHost | ForEach-Object -parallel {
                get-adcomputer -server $server $PSItem -properties DNSHostName
            }
            $MSAGroup = (get-adgroup -server $server -filter "name -like '*$RightsPrefix-ServiceAcct-MSA'" -searchBase $ComponentObject.distinguishedName).name

            if (-not $DNSHostName) {
                if ($TrustedHostComputerObjects.count -ne 1) {
                    write-loghandler -level "warning" -message "Did not find exactly 1 computer account matching trustedHost (results: $($TrustedHostComputerAccount.count))"
                    write-loghandler -level "warning" -message "This is which is required to derive DNSHostName. Either specify a DNSHostname or check your TrustedHost parameter".
                    Throw "Could not derive DNSHostname from TrustedHost"
                }
                $DNSHostName = $TrustedHostComputerObjects.DNSHostName
            }
        } else {
            $SvcPrefixLookup["SVC"]
        }

        switch ($name.length) {
            { $_ -gt 9 } {
                $charsAvailable = 0
            }
            { $_ -ge 7 -and $_ -le 9 } {
                $charsAvailable = $TypeParams.namelengthlimit - $_ - 1
                $orgChars = $charsAvailable
                $ShortOrgName = $ComponentObject.org[0..($orgChars - 1)] -join ""
                $ShortCompName = ""
                write-loghandler -level "Verbose" -message "length: $_; adding $charsAvailable org chars"
            }
            { $_ -le 6 } {
                $charsAvailable = $TypeParams.namelengthlimit - $_ - 1
                $orgChars = [math]::Ceiling($charsAvailable / 2)
                $componentChars = [math]::floor($charsAvailable / 2)
                $ShortOrgName = $ComponentObject.org[0..($orgChars - 1)] -join ""
                $ShortCompName = $ComponentObject.Component[0..($componentChars - 1)] -join ""
                write-loghandler -level "Verbose" -message "Length: $_; org: $orgChars; component: $componentChars"
            }
        }

        if ($charsAvailable -gt 0 ) {
            $middleBit = "{0}{1}-" -f $ShortOrgName, $ShortCompName
            write-loghandler -level "warning" -message "Padding out service account name with Org / Component: $Middlebit"
        }
        else {
            $middlebit = ""

        }
        $AccountName = "{0}_{1}{2}" -f $TypeParams.Prefix, $middleBit, $name


        $Path = "OU={0},{1}" -f "ServiceAccounts", $ComponentObject.DistinguishedName

        $LogonServiceRight = (get-adgroup -server $server -filter "name -like '*$RightsPrefix-LogonService'" -searchBase $ComponentObject.distinguishedName).name
        write-loghandler -level "Verbose" -message "Creating $($typeParams.Type) : $AccountName @ $path"
        $ServiceAccount = try {
            switch ($TypeParams.Type) {
                "Legacy" {
                    $Password = get-randomPassword -passwordLength 16 -forceComplex
                    $SecurePassword = $Password | ConvertTo-SecureString -AsPlainText -Force
                    $UserParams = @{
                        Name              = $accountName
                        SAMAccountName    = $accountName
                        UserprincipalName = "$accountName@$((get-addomain).dnsRoot)"
                        Path              = $path
                        Description       = $Description
                    }
                    $EnableParams = @{
                        ChangePasswordAtLogon = $False
                        Enabled               = $true
                    }
                    $Account = try {
                        new-ADuser -server $server @UserParams -passthru
                        write-loghandler -level "Verbose" -message "Account did not exist, creating"
                    }
                    catch [Microsoft.ActiveDirectory.Management.ADIdentityAlreadyExistsException] {
                        get-aduser -server $server $accountName
                    }
                    catch {
                        Write-Warning $_.exception.getType().fullname
                        write-loghandler -level "warning" -message "Ran into an issue creating new user account."
                        throw $_
                    }

                    $account | set-adaccountPassword -server $server -reset -newPassword $Securepassword -passthru | enable-adaccount -server $server -passthru | set-aduser -server $server @EnableParams -passthru | add-adprincipalGroupMembership -server $server -memberOf "$LogonServiceRight"
                    write-loghandler -level "Verbose" -message "...Set attributes, reset password, and added account to $LogonServiceRight.`r`n"
                    if (-not $DisplayPassword) {
                        write-loghandler -level "warning" -message "Created Account, but password is obscured. use '-DisplayPassword' switch to display password."
                        $password = "********"
                    }

                    [pscustomobject]@{
                        Name     = $AccountName
                        Type     = $TypeParams.Type
                        Password = $Password
                        Path     = $account.distinguishedName
                    }


                }
                "MSA" {
                    write-loghandler -level "warning" -message "Not implemented yet."
                }
                "GMSA" {
                    $GMSAIdentity = @{
                        Name = $Accountname
                        Path = $Path
                    }
                    $GMSASettings = @{
                        KerberosEncryptionType                     = "AES128,AES256"
                        DNSHostname                                = $DNSHostName
                        PrincipalsAllowedToRetrieveManagedPassword = $TrustedHostComputerObjects.distinguishedName
                    }
                    $GMSAaccount = try {
                        new-adserviceAccount -server $server @GMSAIdentity @GMSASettings -PassThru
                    }
                    catch [Microsoft.ActiveDirectory.Management.ADIdentityAlreadyExistsException] {
                        get-adserviceaccount -server $server $GMSAIdentity.name | Set-ADServiceAccount -server $server @GMSASettings -PassThru
                    }
                    $GMSAAccount | add-adprincipalGroupMembership -server $server -memberOf "$LogonServiceRight"
                    $GMSAAccount = get-adserviceaccount -server $server $GMSAIdentity.name -properties Enabled, DNSHostName, PrincipalsAllowedToRetrieveManagedPassword
                    $gmsaObject = [pscustomobject] @{
                        Name              = $GMSAaccount.name
                        SAMAccountName    = $GMSAaccount.samaccountname
                        TrustedPrincipals = $($GMSAaccount.PrincipalsAllowedToRetrieveManagedPassword | ForEach-Object -parallel {
                                (get-adobject -server $server $_).name
                            }) -join ", "
                        DistinguishedName = $GMSAAccount.DistinguishedName
                    }
                    $CreatedAccounts.add($GMSAObject)

                    $GMSAObject
                }
                default {
                    write-loghandler -level "warning" -message "Not implemented / something went wrong"
                }
            }
        }
        catch [System.UnauthorizedAccessException] {
            Write-Error "Access was denied. Please check your permissions and try again."
            $_ | Format-List * -Force
            break
        }
        catch {
            Write-Warning $_.exception.getType().fullname
            write-loghandler -level "warning" -message "Something went wrong."
            Write-Warning $_.exception.getType().fullname
            $_ | Format-List * -Force
            break
        }
        $ServiceAccount

    }
    end {
        if ($createdAccounts.count -gt 0) {
            $nbt = (get-addomain).netbiosname
            Write-Host "Created $($CreatedAccounts.count) GMSA / MSAs."
            Write-Host "You will need to install these accounts on the windows system, e.g.:"
            Write-Host " install-adserviceAccount -server $server -identity $($CreatedAccounts[0].name)`n"
            Write-Host "To use the GMSA with services, use the GUI with username '$nbt\$($CreatedAccounts[0].SAMAccountName)' and no password, or use the following script:`n"
            Write-Host "gwmi Win32_service -filter `"DisplayName like 'ServiceName%'`"| foreach { `$_.Change(`$null,`$null,`$null,`$null,`$null,`$null,'$nbt\$($CreatedAccounts[0].SAMAccountName)',`$null,`$null,`$null,`$null) }`n`n"
        }
    }
}

<#
$nbt=$(get-addomain).netbiosName
$SvcName="msa-DellWMS-01"
 
new-adserviceAccount -server $server -name "$svcName" `
    -dnsHostName "wms-w01.domain" `
    -KerberosEncryptionType AES128,AES256 `
    -PrincipalsAllowedToDelegateToAccount "Right-Infrastructure-VDI-ServiceAcct-MSA","wms-w01$"`
    -PrincipalsAllowedToRetrieveManagedPassword "Right-Infrastructure-VDI-ServiceAcct-MSA","wms-w01$" `
 -path "OU=ServiceAccounts,OU=VDI,OU=Components,OU=Infrastructure,OU=Orgs,dc=contoso,dc=net"
 
 # Change relevant services to gmsa
 $account = $nbt+ "\" + $(get-adserviceAccount -server $server -identity $svcName).samaccountname
 gwmi Win32_service -filter "DisplayName like 'Dell%'"| foreach {
    $_.Change($null,$null,$null,$null,$null,$null,$account,$null,$null,$null,$null)
}
#>