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) } #> |