Search This Blog

Sunday, February 28, 2021

ADFS ServiceAccount.psm1

## Source: https://github.com/microsoft/adfsToolbox/blob/master/serviceAccountModule/AdfsServiceAccountModule.psm1

# Copyright (c) Microsoft Corporation. All rights reserved.

# Licensed under the MIT License.

#####################################################################

####Helper functions related to rule parsing logic###################

#####################################################################

<#

.SYNOPSIS

    Class to encapsulate parsing of the ADFS Issuances/Auth rules.

#>


class AdfsRules

{

    [System.Collections.ArrayList] hidden $rules


    <#

    .SYNOPSIS

        Constructor

    #>

    AdfsRules([string]$rawRules) 

    {

        $rulesArray = $this.ParseRules($rawRules)

        $this.rules = New-Object "System.Collections.ArrayList"

        $this.rules.AddRange($rulesArray)

    }


    <#

    .SYNOPSIS

        Utility function to parse the rules and return them as a string[].

    #>

    [string[]] hidden ParseRules([string]$rawRules)

    {

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : BEGIN"


        $allRules = @()

        $singleRule = [string]::Empty


        $rawRules.Split("`n") | %{

            

            $line = $_.ToString().Trim()


            if (-not ([string]::IsNullOrWhiteSpace($line)) ) 

            {

                $singleRule += $_ + "`n"


                if ($line.StartsWith("=>"))

                {

                    Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Parsed rule:`n$singleRule"

                    $allRules += $singleRule

                    $singleRule = [string]::Empty

                }

            }

        }


        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : END"


        return $allRules

    }


    <#

    .SYNOPSIS

        Finds the rule by name in the format: @RuleName = "$ruleName". Returns $null if not found.

    #>

    [string] FindByRuleName([string]$ruleName)

    {

        $ruleNameSearchString = '@RuleName = "' + $ruleName + '"'

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Search string: $ruleNameSearchString"


        foreach ($rule in $this.rules)

        {

            if ($rule.Contains($ruleNameSearchString))

            {

                Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Found.`n$rule"

                return $rule

            }

        }


        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : NOT FOUND. Returning $null"

        return $null;

    }


    <#

    .SYNOPSIS

        Replaces the specified old rule with the new one. Returns $true if the old one was found and replaced; $false otherwise.

    #>

    [bool] ReplaceRule([string]$oldRule, [string]$newRule)

    {

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Trying to replace old rule with new.`n Old Rule:`n$oldRule`n New Rule:`n$newRule"

        $idx = $this.FindIndexForRule($oldRule)


        if ($idx -ge 0)

        {

            Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Replacing old rule with new."

            $this.rules[$idx] = $newRule

            return $true

        }


        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Old rule is not found so NOT replacing it."

        return $false

    }


    <#

    .SYNOPSIS

        Removes the specified if found. Returns $true if found; $false otherwise.

    #>

    [bool] RemoveRule([string]$ruleToRemove)

    {

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Trying to remove rule.`n Rule:`n$ruleToRemove"


        $idx = $this.FindIndexForRule($ruleToRemove)


        if ($idx -ge 0)

        {

            Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Removing rule at index: $idx."

            $this.rules.RemoveAt($idx)

            return $true

        }


        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Rule is not found so NOT removing it."

        return $false

    }


    <#

    .SYNOPSIS

        Helper function to find the index of the rule. Returns index if found; -1 otherwise.

    #>

    [int] FindIndexForRule([string]$ruleToFind)

    {

        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Trying to find rule.`n Rule:`n$ruleToFind"


        for ($i = 0; $i -lt $this.rules.Count; $i++)

        {

            $rule = $this.rules[$i]


            if ($rule.Replace(' ','').trim() -eq $ruleToFind.Replace(' ','').trim())

            {

                Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : Found at index: $i."

                return $i

            }

        }


        Write-Verbose "$($PSCmdlet.MyInvocation.MyCommand) : NOT FOUND. Returning -1"

        return -1

    }

    

    <#

    .SYNOPSIS

        Returns all the rules as string.

    #>

    [string] ToString()

    {

        return [string]::Join("`n", $this.rules.ToArray())

    }

}


# Helper function - serializes any DataContract object to an XML string

function Get-DataContractSerializedString()

{

    [CmdletBinding()]

    Param

    (

        [Parameter(Mandatory = $true, HelpMessage="Any object serializable with the DataContractSerializer")]

        [ValidateNotNull()]

        $object

    )


    $serializer = New-Object System.Runtime.Serialization.DataContractSerializer($object.GetType())

    $serializedData = $null


    try

    {

        # No simple write to string option, so we have to write to a memory stream

        # then read back the bytes...

        $stream = New-Object System.IO.MemoryStream

        $writer = New-Object System.Xml.XmlTextWriter($stream,[System.Text.Encoding]::UTF8)


        $null = $serializer.WriteObject($writer, $object);

        $null = $writer.Flush();

                

        # Read back the text we wrote to the memory stream

        $reader = New-Object System.IO.StreamReader($stream,[System.Text.Encoding]::UTF8)

        $null = $stream.Seek(0, [System.IO.SeekOrigin]::Begin)

        $serializedData = $reader.ReadToEnd()

    }

    finally

    {

        if ($reader -ne $null)

        {

            try

            {

                $reader.Dispose()

            }

            catch [System.ObjectDisposedException] { }

        }


        if ($writer -ne $null)

        {

            try

            {

                $writer.Dispose()

            }

            catch [System.ObjectDisposedException] { }

        }


        if ($stream -ne $null)

        {

            try

            {

                $stream.Dispose()

            }

            catch [System.ObjectDisposedException] { }

        }

    }


    return $serializedData

}



# Gets internal ADFS settings by extracting them Get-AdfsProperties

function Get-AdfsInternalSettings()

{

    $settings = Get-AdfsProperties

    $settingsType = $settings.GetType()

    $propInfo = $settingsType.GetProperty("ServiceSettingsData", [System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic)

    $internalSettings = $propInfo.GetValue($settings, $null)

    

    return $internalSettings

}


function IsWID()

{

    param

    (

        [Parameter(Mandatory=$true)]

        [string]$ConnectionString

    )


    if($ConnectionString -match "##wid" -or $ConnectionString -match "##ssee")

    {

        return $true

    }

    return $false

}



function Set-AdfsInternalSettings()

{

    [CmdletBinding()]

    Param

    (

        [Parameter(Mandatory=$true)]

        [string]$SerializedData

    )


    $doc = new-object Xml

    $doc.Load("$env:windir\ADFS\Microsoft.IdentityServer.Servicehost.exe.config")

    $connString = $doc.configuration.'microsoft.identityServer.service'.policystore.connectionString

    $cli = new-object System.Data.SqlClient.SqlConnection

    $cli.ConnectionString = $connString

    $cli.Open()

    try

    {    

        $cmd = new-object System.Data.SqlClient.SqlCommand

        $cmd.CommandText = "update [IdentityServerPolicy].[ServiceSettings] SET ServiceSettingsData=@content,[ServiceSettingsVersion] = [ServiceSettingsVersion] + 1,[LastUpdateTime] = GETDATE()"

        $cmd.Parameters.AddWithValue("@content", $SerializedData) | out-null

        $cmd.Connection = $cli

        $cmd.ExecuteNonQuery() 


        # Update service state table for WID sync if required

        if (IsWid -ConnectionString $connString)

        {

            $cmd = new-object System.Data.SqlClient.SqlCommand

            $cmd.CommandText = "UPDATE [IdentityServerPolicy].[ServiceStateSummary] SET [SerialNumber] = [SerialNumber] + 1,[LastUpdateTime] = GETDATE() WHERE ServiceObjectType='ServiceSettings'"


            $cmd.Connection = $cli

            $cmd.ExecuteNonQuery() 

        }

    }

    finally

    {

        $cli.CLose()

    }



Function AddUserRights 

{   

    $RightsFailed =  $false 

    NTRights.Exe -u $NewName +r SeServiceLogonRight | Out-File $LogPath -Append 

     

    If (!$?) 

    { 

        $RightsFailed =  $true 

        Write-Host "`tFailed to add user rights for $NewName`n`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+ "[WARN]      Failed to add user rights for ${NewName}: 'Log on as a service', 'Generate security audits'" | Out-File $LogPath -Append 

        Return $RightsFailed 

    } 

     

    NTRights.Exe -u $NewName +r SeAuditPrivilege | Out-File $LogPath -Append 

    If (!$?) 

    { 

        $RightsFailed =  $true 

        Write-Host "`tFailed to add user rights for $NewName`n`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+ "[WARN]      Failed to add user rights for ${NewName}: 'Log on as a service', 'Generate security audits'" | Out-File $LogPath -Append 

        Return $RightsFailed 

    }  

    Else 

    { 

        GPUpdate /Force | Out-File $LogPath -Append 

        $RightsFailed = $false 

        Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      User rights 'Log on as a service', 'Generate security audits' added for $NewName" | Out-File $LogPath -Append 

    } 

             

    Return $RightsFailed 

 

# Converts account name to SID 

Function ConvertTo-Sid ($Account) 

    $SID = (New-Object system.security.principal.NtAccount($Account)).translate([system.security.principal.securityidentifier]) 

    Return $SID 

 

 

# ACLs a certificate private key 

Function Set-CertificateSecurity 

 

    param([String]$certThumbprint,[String]$NewAccount) 

    $FailedCertPerms = $false 

    $certKeyPath = $env:ProgramData + "\Microsoft\Crypto\RSA\MachineKeys\" 

    $certsCollection = @(dir cert:\ -recurse | ? { $_.Thumbprint -eq $certThumbprint }) 

    $certToSecure = $certsCollection[0] 

    $uniqueKeyName = $certToSecure.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName 

     

    If ($uniqueKeyname -is [Object]) 

    { 

        $Acl = Get-Acl $certKeyPath$uniqueKeyName 

        $Arguments = $NewAccount,"Read","Allow" 

        $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $Arguments 

        $Acl.SetAccessRule($AccessRule) 

        $Acl | Set-Acl $certKeyPath$uniqueKeyName 

         

        If (!$?) 

        { 

            Write-Host "`t`tFailed to set private key permissions.`n`t`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" -NoNewline 

            ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Failed setting permissions on key for thumbprint $certThumbprint - Setting the ACL did not succeed" | Out-File $LogPath -Append 

            $CertPerms = $false 

        } 

        Else 

        { 

            Write-Host "`t`tSuccess" -ForegroundColor "green" -NoNewline 

            ($ElapsedTime.Elapsed.ToString())+" [INFO]      Set permissions on key for thumbprint $certThumbprint" | Out-File $LogPath -Append 

            $CertPerms = $true 

        } 

    }

 

    Else 

    { 

        Write-Host "`t`tFailed to set private key permissions.`n`t`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Failed setting permissions on key for thumbprint $certThumbprint - Unique key container did not exist" | Out-File $LogPath -Append 

        $CertPerms = $false 

    } 

    Return $CertPerms 

 

 

# ACLs the CertificateSharingContainer 

Function Set-CertificateSharingContainerSecurity 

    param([String]$NewSID) 


    $FailedLdap = $false 

     

    # Get the new SID as a SID object and create AD Access Rules 

    $objNewSID = [System.Security.Principal.SecurityIdentifier]$NewSID 

 

    $nullGUID = [guid]'00000000-0000-0000-0000-000000000000' 

    $RuleCreateChild = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($objNewSID,'CreateChild','Allow','All',$nullGUID)  

    $RuleSelf = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($objNewSID,'Self','Allow','All',$nullGUID)  

    $RuleWriteProperty = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($objNewSID,'WriteProperty','Allow','All',$nullGUID)  

    $RuleGenericRead = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($objNewSID,'GenericRead','Allow','All',$nullGUID)  

 

    # Get the LDAP object based on the certificate sharing container and add the AD Access Rules to the object 

    $DN = ($ADFSProperties.CertificateSharingContainer).ToString() 

    $objLDAP = [ADSI] "LDAP://$DN" 

    $objLDAP.get_ObjectSecurity().AddAccessRule($RuleCreateChild) 

    $objLDAP.get_ObjectSecurity().AddAccessRule($RuleSelf) 

    $objLDAP.get_ObjectSecurity().AddAccessRule($RuleWriteProperty) 

    $objLDAP.get_ObjectSecurity().AddAccessRule($RuleGenericRead) 

 

 

    # Commit the AD Access rule changes to the LDAP object 

    $objLDAP.CommitChanges() 

     

    If (!$?) 

    { 

        Write-Host "`tFailed to set permissions on the Certificate Sharing Container.`n`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Failed setting permissions on AD cert sharing container: $DN. $NewName needs 'Create Child', 'Write', 'Read'." | Out-File $LogPath -Append 

        $FailedLdap = $true 

    } 

    Else 

    { 

        Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      Set permissions on cert sharing container: $DN" | Out-File $LogPath -Append 

    } 

 

 

# Generates SQL scripts for database and service permissions 

Function GenerateSQLScripts 

    # Generate SetPermissions.sql 

    If (!(Test-Path $env:Temp\ADFSSQLScripts)) { New-Item $env:Temp\ADFSSQLScripts -type directory | Out-Null } 

    If (Test-Path $env:Temp\ADFSSQLScripts) { Remove-Item $env:Temp\ADFSSQLScripts\* | Out-Null } 

    Write-Host "`n Generating SQL scripts" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Generating SQL scripts ($env:Temp\ADFSSQLScripts)" | Out-File $LogPath -Append 

     

    $WinDir = (Get-ChildItem Env:WinDir).Value 

    Export-AdfsDeploymentSQLScript -DestinationFolder $env:Temp\ADFSSQLScripts -ServiceAccountName $NewName 

         

    If (!$?) 

    { 

        Write-Host "`tFailed to generate SQL scripts. Exiting" -ForegroundColor "red" 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Failed to generate SQL scripts" | Out-File $LogPath -Append 

        Return $false 

    } 

 

    # Generate UpdateServiceSettings.sql, but not for secondary WID. Secondary SQL never gets to this function 

     

    If (!(($Role -eq "SecondaryComputer") -and ($DBMode -eq "WID"))) 

    { 

        "USE AdfsConfiguration" | Out-File "$env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql" 

        "SELECT ServiceSettingsData from IdentityServerPolicy.ServiceSettings" | Out-File "$env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql" -append 

        "UPDATE IdentityServerPolicy.ServiceSettings" | Out-File "$env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql" -append 

        "SET ServiceSettingsData=REPLACE((SELECT ServiceSettingsData from IdentityServerPolicy.ServiceSettings),'$OldSID','$NewSID')" | Out-File "$env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql" -append 

        "SELECT ServiceSettingsData from IdentityServerPolicy.ServiceSettings" | Out-File "$env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql" -append 

     

        If (!$?) 

        { 

            Write-Host "`tFailed to generate UpdateServiceSettings.sql. Exiting" -ForegroundColor "red" 

            ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Failed to generate UpdateServiceSettings.sql" | Out-File $LogPath -Append 

            Return $false 

        } 

    } 

     

    # Clean up the CreateDB.sql file 

    If (Test-Path "$env:Temp\ADFSSQLScripts\CreateDB.sql") 

    { 

        Remove-Item "$env:Temp\ADFSSQLScripts\CreateDB.sql" 

    } 

     

    Return $true 

     

     

     

# Executes the SQL scripts generated by GenerateSQLScripts 

Function ExecuteSQLScripts 

    Start sqlcmd.exe -ArgumentList "-S $SQLHost -i $env:Temp\ADFSSQLScripts\SetPermissions.sql -o $env:Temp\ADFSSQLScripts\SetPermissions.log" -Wait -WindowStyle Hidden | Out-File $LogPath -Append 

      

    If (!$?) 

    { 

        Write-Host "`tFailed to execute SetPermissions.sql. Exiting" -ForegroundColor "red" 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Failed to execute SetPermissions.sql" | Out-File $LogPath -Append 

        Return $false 

    } 

      

     # Execute UpdateServiceSettings.sql, but not for secondary WID. Secondary SQL never gets to this function. 

      

    If (!(($Role -eq "SecondaryComputer") -and ($DBMode -eq "WID"))) 

    { 

        Start sqlcmd.exe -ArgumentList "-S $SQLHost -i $env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql -o $env:Temp\ADFSSQLScripts\UpdateServiceSettings.log" -Wait -WindowStyle Hidden | Out-File $LogPath -Append 

      

        If (!$?) 

        { 

            Write-Host "`tFailed to execute UpdateServiceSettings.sql. Exiting...." -ForegroundColor "red" 

            ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Failed to execute UpdateServiceSettings.sql" | Out-File $LogPath -Append 

            Return $false 

        } 

    } 

    Return $true 

}


function Update-AdfsServiceAccountRule

{

    param(

        [parameter(Mandatory=$true, Position=1)]

        [string]$ServiceAccount,


        [parameter(ValueFromPipeline=$True)]

        [string[]]$SecondaryServers,


        [parameter()]

        [switch]$RemoveRule    

    )



    #Validate provided account exists

    $User = $ServiceAccount

    if($ServiceAccount -match '\\')

    {

        $Account = $ServiceAccount.Split('\') #Input given in the format domain\user 

        $User = $Account[1]

    }


    $IsGmsaAccount = $User.EndsWith("$") 


    if($IsGmsaAccount -eq $true ) 

    {

        $Lookup = Get-ADServiceAccount -Filter {SamAccountName -eq $User}

    }

    else

    {

        $Lookup = Get-ADUser -Filter {Name -eq $User} 

    }


    if($Lookup -eq $null)

    {

        throw "The specified account $User does not exist"

    }



    #Create rule with new service account

    $SID = ConvertTo-Sid($ServiceAccount)

    $ServiceAccountRule = "@RuleName = `"Permit Service Account`"`nexists([Type == `"http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid`", Value == `"$SID`"])`n=> issue(Type = `"http://schemas.microsoft.com/authorization/claims/permit`", value = `"true`");`n`n"

    $Properties = Get-AdfsInternalSettings


    #Backup service settings prior to adding new rule

    $BackUpPath = ((Convert-Path .) + "\serviceSettingsData" + "-" + (get-date -f yyyy-MM-dd-hh-mm-ss) + ".xml") -replace '\s',''

    Get-DataContractSerializedString -object $Properties | Export-Clixml $BackUpPath

    Write-Host ("Backup of current service settings stored at $BackUpPath")



    if($RemoveRule)

    {

        $AuthorizationPolicyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicy) 

        if($AuthorizationPolicyRules.RemoveRule($ServiceAccountRule))

        {

            Write-Host "Service account $ServiceAccount with SID $SID was removed from the Authorization Policy rule set"

        }

        else

        {

             Write-Host "Service account $ServiceAccount with SID $SID was not found in the Authorization Policy rule set"

        }

        $Properties.PolicyStore.AuthorizationPolicy = $AuthorizationPolicyRules.ToString()


        $AuthorizationPolicyReadOnlyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicyReadOnly) 

        if($AuthorizationPolicyReadOnlyRules.RemoveRule($ServiceAccountRule))

        {

            Write-Host "Service account $ServiceAccount with SID $SID was removed from the Authorization Policy Read Only rule set"

        }

        else

        {

            Write-Host "Service account $ServiceAccount with SID $SID was not found in the Authorization Policy Read Only rule set"

        }

        $Properties.PolicyStore.AuthorizationPolicyReadOnly = $AuthorizationPolicyReadOnlyRules.ToString()


    }

    else

    {

        #Check if rule already exists in auth policy

        $AuthorizationPolicyRules = [AdfsRules]::new($Properties.PolicyStore.AuthorizationPolicy)

        if($AuthorizationPolicyRules.FindIndexForRule($ServiceAccountRule) -ne -1)

        {

            Write-Host "Service account rule already exists."

            return $true

        }

        Write-Host "Adding rule for service account $ServiceAccount with SID $SID to Authorization Policy and Authorization Policy Read Only rule sets"


        $Properties.PolicyStore.AuthorizationPolicy = $Properties.PolicyStore.AuthorizationPolicy + $ServiceAccountRule

        $Properties.PolicyStore.AuthorizationPolicyReadOnly = $Properties.PolicyStore.AuthorizationPolicyReadOnly + $ServiceAccountRule

    }


    try

    {

        Set-AdfsInternalSettings  (Get-DataContractSerializedString -object $Properties) | Out-Null

    }

    catch

    {

        Write-Error "There was an error writing to the configuration database"

        retun $false

    }



    $doc = new-object Xml

    $doc.Load("$env:windir\ADFS\Microsoft.IdentityServer.Servicehost.exe.config")

    $connString = $doc.configuration.'microsoft.identityServer.service'.policystore.connectionString



    if((IsWID -ConnectionString $connString) -eq $true)

    {

        if($SecondaryServers.Count -eq 0)

        {

            Write-Warning("No list of secondary servers was provided. You must ensure a sync has occurred on all machines before proceeding to change the service account.")

        }


        #In the case of WID, sync config among all secondary servers

        foreach($Server in $SecondaryServers)

        {

            Invoke-Command -ComputerName $Server -ScriptBlock {

                $Date = Get-Date 

                $Duration = (Get-AdfsSyncProperties).PollDuration

                Set-AdfsSyncProperties -PollDuration 1

                while((Get-AdfsSyncProperties).LastSyncTime -lt $Date)

                {

                    Start-Sleep 1

                }

                Set-AdfsSyncProperties -PollDuration $Duration

            }

        }

    }

    return $true





#Define functions to export



<#

.SYNOPSIS

Module restores the AD FS service settings from a backup generated by either Add-AdfsServiceAccountRule or Remove-AdfsServiceAccountRule

.EXAMPLE

Restore-AdfsSettingsFromBackUp -BackUpPath C:\Users\Administrator\Documents\serviceSettingsData-2018-04-11-12-04-03.xml

#>


function Restore-AdfsSettingsFromBackup

{

    [cmdletbinding(SupportsShouldProcess, ConfirmImpact='High')]

    param(

        [parameter(Mandatory=$true)]

        [string]$BackupPath

    )


    if(-not (Test-Path $BackupPath))

    {

        Write-Host "The provided path to the backup file was not found."

        return $false

    }


    #Receive user confirmation

    if(-not $PSCmdlet.ShouldProcess("A write to the AD FS configuration database will occur", "This script will write directly to the AD FS configuration database. Are you sure you want to proceed?", "Confrim"))

    {

        Write-Host "Terminating execution of script"

        return $false

    }


    $Properties = Import-Clixml $BackupPath

    try

    {

        Set-AdfsInternalSettings  $Properties | Out-Null

    }

    catch

    {

        Write-Error "There was an error writing to the configuration database"

        return $false

    }

    return $true

}



<#

.SYNOPSIS

Module adds rule permitting the speciifed service account to the AD FS rule set.

For Windows Server 2016 and later this must be done prior to changing the service account.

Failure to do so will render servers non-functional.

.EXAMPLE

Add-AdfsServiceAccountRule -ServiceAccount newAccount

Add-AdfsServiceAccountRule -ServiceAccoount MyDomain\newAccount

Add-AdfsServiceAccountRule -ServiceAccount newAccount -SecondaryServers server1, server2

#>


function Add-AdfsServiceAccountRule

{

    [cmdletbinding(SupportsShouldProcess, ConfirmImpact='High')]

    param

    (

        [parameter(Mandatory=$true, Position=1)]

        [string]$ServiceAccount,


        [parameter(ValueFromPipeline=$True)]

        [string[]]$SecondaryServers

    )


    #Receive user confirmation

    if(-not $PSCmdlet.ShouldProcess("A write to the AD FS configuration database will occur", "This script will write directly to the AD FS configuration database. Are you sure you want to proceed?", "Confrim"))

    {

        Write-Host "Terminating execution of script"

        return $false

    }


    Update-AdfsServiceAccountRule -ServiceAccount $ServiceAccount -SecondaryServers $SecondaryServers

}



<#

.SYNOPSIS

Module deletes rule permitting the speciifed service account from the AD FS rule set.

This can be used to disable the old service account on Windows Server 2016 and later.

This comand should only be run once the service account has been successfully changed.

.EXAMPLE

Remove-AdfsServiceAccountRule -ServiceAccount newAccount

Remove-AdfsServiceAccountRule -ServiceAccoount MyDomain\newAccount

Remove-AdfsServiceAccountRule -ServiceAccount newAccount -SecondaryServers server1, server2

#>

function Remove-AdfsServiceAccountRule

{

    [cmdletbinding(SupportsShouldProcess, ConfirmImpact='High')]

    param

    (

        [parameter(Mandatory=$true, Position=1)]

        [string]$ServiceAccount,


        [parameter(ValueFromPipeline=$True)]

        [string[]]$SecondaryServers

    )


    #Receive user confirmation

    if(-not $PSCmdlet.ShouldProcess("A write to the AD FS configuration database will occur", "This script will write directly to the AD FS configuration database. Are you sure you want to proceed?", "Confrim"))

    {

        Write-Host "Terminating execution of script"

        return $false

    }


    Update-AdfsServiceAccountRule -ServiceAccount $ServiceAccount -SecondaryServers $SecondaryServers -RemoveRule

}


<#

.SYNOPSIS

Module changes the AD FS service account.

The script must be run locally on all seconodary servers first before running on the primary server.

For Windows Server 2016 and later, Add-AdfsServiceAccountRule should be run prior the execution of this command

.EXAMPLE

Update-AdfsServiceAccount

#>

function Update-AdfsServiceAccount 

{

    $ErrorActionPreference = "silentlycontinue" 

    $MachineFQDN = [System.Net.Dns]::GetHostEntry([System.Net.Dns]::GetHostName()).HostName 

    $MachineDomainSlash = ((((($MachineFQDN).ToString()).Split(".",2)[1])+"\"+((($MachineFQDN).ToString()).Split(".",2)[0])).ToUpper()) 

    #check for Vista, 7, or 8 

    $OSVersion = [System.Environment]::OSVersion.Version 

  

    # Show header, show AS-IS statement, detail sample changes made, prompt if ready to continue 

    Write-Host "`n IMPORTANT: This sample is provided AS-IS with no warranties and confers no rights." -ForegroundColor "yellow" 

    Write-Host "`n This sample is intended only for Federation Server farms. If your AD FS 2.x deployment type is Standalone," -ForegroundColor "yellow" 

    Write-Host " this sample does not apply to your Federation Service." -ForegroundColor "yellow" 

    Write-Host "`n The following changes will occur as a result of executing this sample:`n`t1. The AD FS service will be stopped" 

    write-host "`t2. The AD FS database permissions will be altered to allow access for the new account" 

    Write-Host "`t3. A servicePrincipalName registration will be removed from the old account and registered to the new account" 

    Write-Host "`t4. The AD FS service and AdfsAppPool identity will be changed to the new account" 

    Write-Host "`t5. Certificate private key permissions will be modified to allow access for the new account" 

    Write-Host "`t6. The new account will be allowed user rights: `"Log on as a service`" and `"Generate security audits`"" 

    Write-Host "`n PRE-EXECUTION TASKS" -ForegroundColor "yellow" 

    Write-Host " 1. Create the new service account in Active Directory" -ForegroundColor "yellow" 

    Write-Host " 2. Install SQLCmd.exe on each Federation Server in the farm" -ForegroundColor "yellow" 

    Write-Host "`tSQLCmd.exe requires the SQL Native Client to be installed" -ForegroundColor "yellow" 

    Write-Host "`tAfter SQLCmd.exe has been installed, all Powershell windows must be" -ForegroundColor "yellow" 

    Write-Host "`tclosed and re-opened to continue with execution of this sample." -ForegroundColor "yellow" 

    Write-Host "`n`tDownload both installers from the following location`:`n`thttp://www.microsoft.com/download/en/details.aspx?id=15748" -ForegroundColor "yellow" 

 

    Write-Host "`n If you are ready to proceed, type capital C and press Enter to continue: " -NoNewline 

    $Answer = "notready" 

    $LogPath = "$pwd\ADFS_Change_Service_Account.log" 

    $Answer = Read-Host 

 

    If ($Answer -cne "C")  

    {  

        Write-Host "`tExiting`n" -ForegroundColor "red" 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Bad selection at the prompt to continue with sample execution" | Out-File $LogPath 

        exit 

    } 

 

    #write timing info to the log file and start a stopwatch to capture elapsed time 

    "[START TIME] $(Get-Date)" | Out-File $LogPath 

    $ElapsedTime = [System.Diagnostics.Stopwatch]::StartNew() 

    $OpMode1 = "Federation Server" 

    $OpMode2 = "Final Federation Server" 

 

    Write-Host "`n Note: The sample must be executed against each Federation Server in the farm." -ForegroundColor "yellow" 

    Write-Host " Windows Internal Database (WID) and SQL farms are supported. Before execution can" -ForegroundColor "yellow" 

    Write-Host " begin, an operating mode must be selected. Careful consideration of the following" -ForegroundColor "yellow" 

    Write-Host " guidance is necessary to ensure the sample is executed properly on each server." -ForegroundColor "yellow" 

    Write-Host "`n GUIDANCE FOR SELECTING AN OPERATING MODE:" -ForegroundColor "yellow" 

    Write-Host "`n WID FARM:`n The sample must be executed on all Secondary servers before execution should" -ForegroundColor "yellow" 

    Write-Host " occur on the Primary server. The Primary server is the only server with Write access to the" -ForegroundColor "yellow" 

    Write-Host " configuration database. The Primary server must be used as the 'Final Federation Server'" -ForegroundColor "yellow" 

    Write-Host "`n Powershell command to determine whether a server is Primary or Secondary:" -ForegroundColor "yellow" 

 

    #check for Vista, 7, or 8 

    $OSVersion = [System.Environment]::OSVersion.Version 

  

 

    If (($OSVersion.Major -lt 6) -or ( ($OSVersion.Major -eq 6) -and ($OSVersion.Minor -lt 3) )) 

    { 

      Write-Host "`tExiting`n" -ForegroundColor "red" 

      ($ElapsedTime.Elapsed.ToString())+" [ERROR]     This script is only applicable on Windows Server 2012 R2 and later" | Out-File $LogPath 

      exit 

    } 

 

    $feature = Get-WindowsFeature -Name ADFS-Federation 


    If( ($feature -eq $null) -or ($feature.Installed -eq $false) ) 

    { 

      Write-Host "`tExiting`n" -ForegroundColor "red" 

      ($ElapsedTime.Elapsed.ToString())+" [ERROR]     This script is only applicable on a machine where AD FS is already installed" | Out-File $LogPath 

      exit 

    } 

 

    Write-Host "`tImport-Module ADFS" -ForegroundColor "yellow" 

    Import-Module ADFS -ErrorAction Stop 

 

 

    Write-Host "`tGet-AdfsSyncProperties" -ForegroundColor "yellow" 

    Write-Host "`n SQL FARM:`n Any one server in the farm should be selected as the 'Final Federation Server'." -ForegroundColor "yellow" 

    Write-Host " All servers in a SQL farm have Write access to the configuration database. Execute the sample on all other" -ForegroundColor "yellow" 

    Write-Host " servers in the farm before executing the sample on the server selected as the 'Final Federation Server'" -ForegroundColor "yellow" 

 

 

    Write-Host "`n Select operating mode:`n`t1 - $OpMode1`n`t2 - $OpMode2" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Getting operating mode" | Out-File $LogPath -Append 

 

    While (($Mode -ne 1) -and ($Mode -ne 2)) 

    { 

        $Mode = Read-Host "`tSelection" 

     

        If (($Mode -ne 1) -and ($Mode -ne 2)) 

        { 

            Write-Host "`t$Mode is not a valid selection" -ForegroundColor "yellow" 

        } 

    } 

 

    if ($Mode -eq 1) 

    { 

        $SelOpMode = $OpMode1 

    } 

    else 

    { 

        $SelOpMode = $OpMode2 

    } 

 

    Write-Host "`tOperating mode: $SelOpMode" -ForegroundColor "green" 

    ($ElapsedTime.Elapsed.ToString())+" [INFO]      Operating mode: $SelOpMode" | Out-File $LogPath -Append 

 

    # Check for the AD FS service 

 

    Write-Host " Checking the AD FS service" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Checking for service installation (adfssrv)" | Out-File $LogPath -Append 

    $ADFSInstalled = Get-Service adfssrv 

 

    If (!$ADFSInstalled) 

    { 

        Write-Host "`tThe AD FS service was not found. Exiting`n" -ForegroundColor "red" 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     adfssrv is not installed" | Out-File $LogPath -Append 

        Exit 

    } 

    Else 

    { 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      adfssrv is installed" | Out-File $LogPath -Append 

     

        # Check to see if adfssrv is running. If stopped, attempt to start. If start fails, exit. 

        If ($ADFSInstalled.Status -ceq "Stopped") 

        { 

            Write-Host "`tThe AD FS service is stopped. Starting the service`n" -ForegroundColor "yellow" -NoNewline 

            ($ElapsedTime.Elapsed.ToString())+" [WARN]      adfssrv is stopped. Attempting to start" | Out-File $LogPath -Append 

            $ADFSInstalled.Start() 

            $ADFSInstalled.WaitForStatus("Running",[System.TimeSpan]::FromSeconds(25)) 

         

            If (!$?) 

            { 

                Write-Host "`tThe AD FS service could not be started. Exiting" -ForegroundColor "red" 

                ($ElapsedTime.Elapsed.ToString())+" [ERROR]     adfssrv failed to start" | Out-File $LogPath -Append 

                Exit 

            } 

        } 

        Else 

        { 

            ($ElapsedTime.Elapsed.ToString())+" [INFO]      adfssrv is running" | Out-File $LogPath -Append 

        } 

       

        Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

    } 

 

 

   

    # Check if Fed Svc Name equals machine FQDN. This is not supported for farms. Breaks Kerberos. 

    Write-Host "`n Checking the Federation Service Name" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Checking Federation Service Name" | Out-File $LogPath -Append 

 

    $ADFSProperties = Get-ADFSProperties 

    $FederationServiceName = ((($ADFSProperties.HostName).ToString()).ToUpper()) 

 

    If ($FederationServiceName -eq $MachineFQDN) 

    { 

        Write-Host "`tFederation Service Name: $FederationServiceName`n`tFederation Service Name must not equal the qualified`n`tcomputer name in an AD FS farm." -ForegroundColor "red" 

        Write-Host "`thttp://social.technet.microsoft.com/wiki/contents/articles/ad-fs-2-0-how-to-change-the-federation-service-name.aspx" -ForegroundColor "gray" 

        Write-Host "`tExiting`n" -ForegroundColor "red" 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Federation Service Name: $FederationServiceName equals the qualified computer name. This is not supported in a farm deployment" | Out-File $LogPath -Append 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     http://social.technet.microsoft.com/wiki/contents/articles/ad-fs-2-0-how-to-change-the-federation-service-name.aspx" | Out-File $LogPath -Append 

        Exit 

    } 

    Else 

    { 

        Write-Host "`tSuccess" -ForegroundColor "green" 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      Federation Service Name is OK" | Out-File $LogPath -Append 

    } 

 

    $CredsNotValidated = $true 

 

    While ($CredsNotValidated) 

    { 

        # Collect creds for new service account 

        $NewName = "foo" 

        While (($NewName -match " ") -or ($NewName -match "networkservice") -or ($NewName -match "localsystem") -or (($NewName -notmatch "\\") -and ($NewName -notmatch "`@"))) 

        { 

            Write-Host " Collecting credentials for the new account" 

            ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Collecting new credentials" | Out-File $LogPath -Append 

            $NewName = (Read-Host "`tUsername (domain\user)").ToUpper() 

            ($ElapsedTime.Elapsed.ToString())+" [INFO]      New user name: $NewName" | Out-File $LogPath -Append 

     

            If (($NewName -match " ") -or ($NewName -match "networkservice") -or ($NewName -match "localsystem") -or (($NewName -notmatch "\\") -and ($NewName -notmatch "`@"))) 

            { 

                Write-Host "`t$NewName is not supported. AD FS farms require a domain user account (domain\user)" -ForegroundColor "red" 

                ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Unsupported new name entry: $NewName. Service account must be domain user" | Out-File $LogPath -Append 

            } 

        } 

        $IsGmsaAccount = $NewName.EndsWith("$") 

        If($IsGmsaAccount) 

        { 

            $NewPassword = $null 

        } 

        Else 

        { 

            $NewPassword = Read-Host -assecurestring "`tPassword" 

        } 

        $objNewCreds = New-Object Management.Automation.PSCredential $NewName, $NewPassword 

        $NewPassword = $objNewCreds.GetNetworkCredential().Password 

   

        # Check for UPN style new name and convert to domain\username for SPN work items 

        If ($NewName.ToString() -match "`@") 

        { 

            $NewName = ((($NewName.Split("`@")[1]).ToString() + "\" + ($NewName.Split("`@")[0]).ToString()).ToUpper()) 

            Write-Host "`n`tUsing $NewName in order to meet SPN requirements" -ForegroundColor "gray" 

            ($ElapsedTime.Elapsed.ToString())+" [INFO]      Using $NewName in order to meet SPN requirements" | Out-File $LogPath -Append 

        } 

     

        // Do not validate creds for gMSA 

       

        If ($IsGmsaAccount) 

        { 

            Write-Host " gMSA account was specified. Skipping credential validation" 

            $CredsNotValidated = $false 

        } 

        Else 

        {  

   

            # Validating credentials 

            Write-Host " Validating credentials" 

            ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Validating credentials" | Out-File $LogPath -Append 

            $Domain = "LDAP://" + ([ADSI]"").distinguishedName 

            $DomainObject = New-Object System.DirectoryServices.DirectoryEntry($Domain,$NewName,$NewPassword) 

 

            `$DomainObject.Name = `$DomainObject.Name 

            If ($DomainObject.Name -eq $null) 

            { 

                Write-Host "`tFailed credential validation" -ForegroundColor "red" 

                ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Failed credential validation" | Out-File $LogPath -Append 

            } 

            Else 

            { 

                Write-Host "`tSuccess" -ForegroundColor "green" 

                ($ElapsedTime.Elapsed.ToString())+" [INFO]      Credentials validated" | Out-File $LogPath -Append 

                $CredsNotValidated = $false 

            } 

        } 

    } 

 

    # Getting current identity for the AD FS 2.x Windows Service 

 

    Write-Host " Discovering current account name" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Getting old name" | Out-File $LogPath -Append 

    $ADFSSvc = gwmi win32_service -filter "name='adfssrv'" 

 

    If (!$ADFSSvc) 

    { 

        Write-Host "`tFailed to get the current account name. Exiting`n" -ForegroundColor "red" 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Could not get old name from WMI service information for adfssrv" | Out-File $LogPath -Append 

        exit 

    } 

    Else 

    { 

        $OldName = ((($ADFSSvc.StartName).ToString()).ToUpper()) 

        Write-Host "`t$OldName" -ForegroundColor "Green" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      Old name: $OldName" | Out-File $LogPath -Append 

     

        If ($Mode -eq 2) 

        { 

            # Check for network service and local system and set a variable to use the domain\computername for SPN work items 

            If ((($OldName).ToString() -eq "NT AUTHORITY\NETWORK SERVICE") -or (($OldName).ToString() -eq "NT AUTHORITY\LOCAL SYSTEM")) 

            { 

                Write-Host "`tUsing $MachineDomainSlash in order to meet SPN requirements" -ForegroundColor "gray" 

                ($ElapsedTime.Elapsed.ToString())+" [INFO]      Using $MachineDomainSlash in order to meet SPN requirements" | Out-File $LogPath -Append 

                $UseMachineFQDN = $true 

            } 

           

            # Check for UPN style old name and convert to domain\username for SPN work items 

            If ($OldName.ToString() -match "`@") 

            { 

                $OldName = ($OldName.Split("`@")[1]).ToString() + "\" + ($OldName.Split("`@")[0]).ToString() 

                Write-Host "`tUsing $OldName in order to meet SPN requirements" -ForegroundColor "gray" 

                ($ElapsedTime.Elapsed.ToString())+" [INFO]      Using $OldName in order to meet SPN requirements" | Out-File $LogPath -Append 

            } 

        } 

    } 

   

    ####ADD NEEDED MODULES#### 

 

    $ADFSCertificate = Get-ADFSCertificate 

    $ADFSSyncProperties = Get-ADFSSyncProperties 

    $Role = (($ADFSSyncProperties.Role).ToString()) 


    $doc = new-object Xml

    $doc.Load("$env:windir\ADFS\Microsoft.IdentityServer.Servicehost.exe.config")

    $connString = $doc.configuration.'microsoft.identityServer.service'.policystore.connectionString

   

    ####STOP THE AD FS WINDOWS SERVICE#### 

    

    Write-Host "`n Stopping the AD FS service" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Stopping adfssrv" | Out-File $LogPath -Append 

 

    # Stop the AD FS Windows service. No need to check status since Stop-Service does not throw if service is currently stopped. 

    $ADFSInstalled.Stop() 

    $ADFSInstalled.WaitForStatus("Stopped",[System.TimeSpan]::FromSeconds(15)) 

 

    If (!$?) 

    { 

        Write-Host "`tThe AD FS service could not be stopped.`n`tExiting`n" -ForegroundColor "red" 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     adfssrv could not be stopped" | Out-File $LogPath -Append 

        exit 

    } 

    Else 

    { 

        Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      adfssrv is stopped" | Out-File $LogPath -Append 

    } 

 

    ####GETTING THE SQL HOST NAME#### 

 

    # Getting SQL host name 

    Write-Host "`n Discovering SQL host" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Discovering SQL host" | Out-File $LogPath -Append 

    $SQLHost = (($connString.ToString()).split("=")[1]).Split(";")[0] 

    Write-Host "`t$SQLHost" -ForegroundColor "green" -NoNewline 

    ($ElapsedTime.Elapsed.ToString())+" [INFO]      SQL host: $SQLHost" | Out-File $LogPath -Append 

     

    ####DETECT DATABASE TYPE#### 

     

    # Detect WID or SQL 

    Write-Host "`n Detecting database type" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Detecting database type" | Out-File $LogPath -Append 

     

    if((IsWid -ConnectionString $connString) -eq $true)

    {

        $DBMode = "WID" 

    }


    else

    {

        $DBMode = "SQL" 

    }

     

    Write-Host "`t$DBMode" -ForegroundColor "green" -NoNewline 

    ($ElapsedTime.Elapsed.ToString())+" [INFO]      Database type: $DBMode" | Out-File $LogPath -Append 

     

    #check to be sure that the admin isn't attempting a mode that isn't suitable for the current FS's role 

     

    If ($DBMode -eq "WID") 

    { 

        Write-Host "`n Checking operating mode against server role" 

        ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Checking op mode against server role" | Out-File $LogPath -Append 

     

        If ((($Mode -eq 2) -and ($Role -eq "SecondaryComputer")) -or (($Mode -eq 1) -and ($Role -eq "PrimaryComputer"))) 

        { 

            Write-Host "`tError: Operating mode and role mismatch. Operating mode $Mode cannot be executed`n`ton a server with role $Role`n`tAction: Select a valid operating mode for this server.`n`tExiting" -ForegroundColor "Red" 

            ($ElapsedTime.Elapsed.ToString())+" [ERROR]     Op mode does not match server role. Mode: $Mode. Role: $Role" | Out-File $LogPath -Append 

            exit 

        } 

        Write-Host "`tSuccess" -ForegroundColor "Green" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      Op mode matches server role" | Out-File $LogPath -Append 

    } 

     

    # Detect SQLCmd.exe, but not for secondary SQL 

     

    If (!(($Mode -eq 1) -and ($DBMode -eq "SQL"))) 

    { 

        Write-Host "`n Detecting SQLCmd.exe" 

        ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Detecting SQLCMD.exe" | Out-File $LogPath -Append 

        $SQLCmdPresent = $false 

        sqlcmd.exe /? | Out-Null 

     

        If (!$?) 

        { 

            Write-Host "`tSQLCmd.exe was not found`n`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY." -ForegroundColor "yellow" -NoNewline 

            ($ElapsedTime.Elapsed.ToString())+" [WARN]      SQLCMD.exe not found. SQL scripts must be manually executed." | Out-File $LogPath -Append 

        } 

        Else 

        { 

            Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

            ($ElapsedTime.Elapsed.ToString())+" [INFO]      SQLCMD.exe found" | Out-File $LogPath -Append 

            $SQLCmdPresent = $true 

        } 

    } 

 

    ####CONVERTING NAMES TO SIDS#### 

    Write-Host "`n Converting $OldName to SID" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Convert $OldName to SID" | Out-File $LogPath -Append 

     

    # Get SID for the old account into a variable 

    $OldSID = ConvertTo-Sid -Account $OldName 

     

    If (!$OldSID) 

    { 

        Write-Host "`tName to SID translation failed for `"$OldName`".`n`tExiting`n" -ForegroundColor "red" 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     $OldName SID translation failed" | Out-File $LogPath -Append 

        exit 

    } 

    Else 

    { 

        Write-Host "`t$OldSID" -ForegroundColor "green" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      Old SID: $OldSID" | Out-File $LogPath -Append 

    } 

       

    Write-Host "`n Converting $NewName to SID" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Convert $NewName to SID" | Out-File $LogPath -Append 

 

    #Get SID for the new account into a variable 

    $NewSID = ConvertTo-Sid -Account $NewName 

     

    If (!$NewSID) 

    { 

        Write-Host "`tName to SID translation failed for `"$NewName`".`n`tEnsure that the new service account name is typed correctly. Exiting`n" -ForegroundColor "red" 

        ($ElapsedTime.Elapsed.ToString())+" [ERROR]     $NewName SID translation failed" | Out-File $LogPath -Append 

                exit 

    } 

    Else 

    { 

        Write-Host "`t$NewSID" -ForegroundColor "green" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      New SID: $NewSID" | Out-File $LogPath -Append 

    } 

       

    If ($NewSID -eq $OldSID) 

    { 

        Write-Host "`n The old and new accounts are the same, do you wish to proceed?" -ForegroundColor "yellow" 

        $SameAccountAnswer = Read-Host "`t(Y/N)" 

         

        If ($SameAccountAnswer -ne "y") 

        { 

            Write-Host "`tExiting`n" -ForegroundColor "red" 

            Exit 

        } 

    } 

       

    ####GENERATE SQL SCRIPTS, BUT NOT FOR SECONDARY SQL#### 

       

    If (!(($Mode -eq 1) -and ($DBMode -eq "SQL"))) 

    { 

        $GenerateSQLScripts = GenerateSQLScripts 

        If (!$GenerateSQLScripts) 

        { 

            exit 

        } 

        Else 

        { 

            Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

            ($ElapsedTime.Elapsed.ToString())+" [INFO]      SQL scripts generated" | Out-File $LogPath -Append 

        } 

    } 

 

    ####PERFORM ACTIONS FOR SQL DATABASE TYPE#### 

     

    if (($DBMode -eq "SQL") -and ($Mode -eq 2)) 

    { 

        Write-Host "`n Does the currently logged on user have administrative access to the AD FS databases within SQL server`?" 

        ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Discovering if current user is SQL admin" | Out-File $LogPath -Append 

        $SQLAnswser = "foo" 

                 

        while (($SQLAnswer -ne "Y") -and ($SQLAnswer -ne "N")) 

        { $SQLAnswer = Read-Host "`t(Y/N)" } 

             

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      SQL admin answer: $SQLAnswer" | Out-File $LogPath -Append 

         

        # If the user has permissions in SQL and SQLCmd.exe is present, run the scripts, otherwise, explain how they must perform this step manually. 

           if (($SQLAnswer -eq "Y") -and ($SQLCmdPresent)) 

           { 

                Write-Host " Executing SQL scripts" 

                ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Executing SQL scripts using SQLCMD.exe" | Out-File $LogPath -Append 

                $ExecuteSQLScripts = ExecuteSQLScripts 

             

                If (!$ExecuteSQLScripts) 

                { 

                    exit 

                } 

                Else 

                { 

                    Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

                    ($ElapsedTime.Elapsed.ToString())+" [INFO]      SQL scripts executed successfully" | Out-File $LogPath -Append 

                } 

            } 

            else 

            { 

                $NeedsSQLWarning = $true 

                ($ElapsedTime.Elapsed.ToString())+" [WARN]      Admin must execute SQL scripts manually:" | Out-File $LogPath -Append 

                ($ElapsedTime.Elapsed.ToString())+" [WARN]      sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\SetPermissions.sql -o $env:Temp\ADFSSQLScripts\SetPermissions-output.log,0,True" | Out-File $LogPath -Append 

                ($ElapsedTime.Elapsed.ToString())+" [WARN]      sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql -o $env:Temp\ADFSSQLScripts\UpdateServiceSettings-output.log,0,True" | Out-File $LogPath -Append 

 

            } 

        } 

         

        If ($DBMode -eq "WID") 

        { 

     

            ####PERFORM STEPS FOR WID DATABASE TYPE#### 

     

            # We don't care if they are an admin in SQL Server, so only need to check to see if SQLCmd.exe is installed. Run the scripts, otherwise, explain how they must perform steps manually 

            if ($SQLCmdPresent) 

            { 

                Write-Host "`n Executing SQL scripts" 

                ($ElapsedTime.Elapsed.ToString())+" [INFO]      Executing SQL scripts using SQLCMD.exe" | Out-File $LogPath -Append 

                $ExecuteSQLScripts = ExecuteSQLScripts 

             

                If (!$ExecuteSQLScripts) 

                { 

                    exit 

                } 

                Else 

                { 

                    Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

                    ($ElapsedTime.Elapsed.ToString())+" [INFO]      SQL scripts executed successfully" | Out-File $LogPath -Append 

                } 

            } 

            else 

            { 

                $NeedsSQLWarning = $true 

            } 

        } 

   

     

        If ($Mode -eq 2) 

        { 

            ####REMOVE THE SPN FROM THE OLD SERVICE ACCOUNT#### 

       

            If ($UseMachineFQDN) 

            { 

                Write-Host "`n Removing SPN HOST/$FederationServiceName from $MachineDomainSlash" 

                ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Removing SPN HOST/$FederationServiceName from $MachineDomainSlash" | Out-File $LogPath -Append 

               setspn.exe -D HOST/$FederationServiceName $MachineDomainSlash | Out-File $LogPath -Append 

         

                If (!$?) 

                { 

                    Write-Host "`tRemoving SPN failed`n`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY." -ForegroundColor "yellow" -NoNewline 

                    ($ElapsedTime.Elapsed.ToString())+" [WARN]      Removing SPN failed: HOST/$FederationServiceName from $MachineDomainSlash" | Out-File $LogPath -Append 

                    ($ElapsedTime.Elapsed.ToString())+" [WARN]      setspn.exe -D HOST/$FederationServiceName $MachineDomainSlash" | Out-File $LogPath -Append 

                    $FailedSpn = $true 

                } 

                Else 

                { 

                    Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

                    ($ElapsedTime.Elapsed.ToString())+" [INFO]      SPN removed: HOST/$FederationServiceName from $MachineDomainSlash" | Out-File $LogPath -Append 

                } 

            } 

            Else 

            { 

                Write-Host "`n Removing SPN HOST/$FederationServiceName from $OldName" 

                ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Removing SPN HOST/$FederationServiceName from $OldName" | Out-File $LogPath -Append 

                setspn.exe -D HOST/$FederationServiceName $OldName | Out-File $LogPath -Append 

         

                If (!$?) 

                { 

                    Write-Host "`tRemoving SPN failed`n`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" -NoNewline 

                    ($ElapsedTime.Elapsed.ToString())+" [WARN]      Removing SPN failed: HOST/$FederationServiceName from $OldName" | Out-File $LogPath -Append 

                    ($ElapsedTime.Elapsed.ToString())+" [WARN]      setspn.exe -D HOST/$FederationServiceName $OldName" | Out-File $LogPath -Append 

                    $FailedSpn = $true 

                } 

                Else 

                { 

                    Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

                    ($ElapsedTime.Elapsed.ToString())+" [INFO]      SPN removed: HOST/$FederationServiceName from $OldName" | Out-File $LogPath -Append 

                } 

            } 

 

            ####ADD THE SPN TO THE NEW SERVICE ACCOUNT#### 

     

            Write-Host "`n Registering SPN HOST/$FederationServiceName to $NewName" 

            ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Registering SPN HOST/$FederationServiceName to $NewName" | Out-File $LogPath -Append 

            setspn.exe -S HOST/$FederationServiceName $NewName | Out-File $LogPath -Append 

 

            If (!$?) 

            { 

                Write-Host "`tRegistering SPN failed`n`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" -NoNewline 

                ($ElapsedTime.Elapsed.ToString())+" [WARN]      Registering SPN failed: HOST/$FederationServiceName to $NewName" | Out-File $LogPath -Append 

                ($ElapsedTime.Elapsed.ToString())+" [WARN]      setspn.exe -S HOST/$FederationServiceName $NewName" | Out-File $LogPath -Append 

                $FailedSpn = $true 

            } 

            Else 

            { 

                Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

                ($ElapsedTime.Elapsed.ToString())+" [INFO]      SPN registered: HOST/$FederationServiceName to $NewName" | Out-File $LogPath -Append 

            } 

        } 

 

    ####SET THE IDENTITY OF THE AD FS WINDOWS SERVICE TO THE NEW SERVICE ACCOUNT#### 

 

    # Setting identity for the AD FS Windows Service to the new service account 

    Write-Host "`n Setting the AD FS service identity to $NewName" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Setting new service identity for adfssrv to $NewName" | Out-File $LogPath -Append 

 

    $ADFSSvc = gwmi win32_service -filter "name='adfssrv'" 

 

    If (!$ADFSSvc) 

    { 

        Write-Host "`tFailed to get information about the AD FS service." -ForegroundColor "yellow" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [WARN]      Failed to get WMI information for adfssrv from WMI" | Out-File $LogPath -Append 

    } 

 

    $ADFSSvc.Change($null,$null,$null,$null,$null,$null,$NewName,$NewPassword,$null,$null,$null) | Out-Null 

 

    If (!$?) 

    { 

        Write-Host "`tFailed to set the identity of the AD FS service`n`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [WARN]      Failed to set identity for adfssrv to $NewName" | Out-File $LogPath -Append 

        $FailedServiceIdentity = $true 

    } 

    Else 

    { 

        Write-Host "`tSuccess" -ForegroundColor "green" -NoNewline 

        ($ElapsedTime.Elapsed.ToString())+" [INFO]      Set identity of adfssrv to $NewName" | Out-File $LogPath -Append 

    } 

 

    If ( !$FailedServiceIdentity ) 

    { 

        # If the service account was gMSA, and you are running on a DC, add service dependency on kdssvc, otherwise remove the dependency on kdssvc 

        $kdssvc = Get-Service -Name "kdssvc" 

        If( ( $kdssvc -ne $null ) -and $IsGmsaAccount ) 

        { 

            Write-Host "`n Setting HTTP/KdsSvc as a service dependency for ADFS Service" 

            ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Setting HTTP/KdsSvc as a service dependency for adfssrv" | Out-File $LogPath -Append 

            Start sc.exe -ArgumentList "config adfssrv depend=HTTP/KdsSvc" -Wait -WindowStyle Hidden | Out-File $LogPath -Append 

        } 

        Else 

        { 

            Write-Host "`n Adding HTTP as a service dependency for ADFS Service" 

            ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Setting HTTP as a service dependency for adfssrv" | Out-File $LogPath -Append 

            Start sc.exe -ArgumentList "config adfssrv depend=HTTP" -Wait -WindowStyle Hidden | Out-File $LogPath -Append 

        } 

    } 

 

    ####ACL THE CERTIFICATE SHARING CONTAINER FOR THE NEW SERVICE ACCOUNT#### 

 

    # Only execute if this is the first federation server 

    if ($Mode -eq 2) 

    { 

        # Check if CertificateSharingContainer has a value. If it does, ACL the container for the new service account. 

        If ($ADFSProperties.CertificateSharingContainer -ne $null) 

        { 

            Write-Host "`n Providing $NewName access to the Certificate Sharing Container" 

            ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Providing $NewName access to ($ADFSProperties.CertificateSharingContainer).ToString()" | Out-File $LogPath -Append 

            Set-CertificateSharingContainerSecurity -NewSID $NewSID 

        } 

    } 

   

    ####ADD USER RIGHTS#### 

 

    Write-Host "`n Adding user rights for $NewName" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Adding user rights for $NewName" | Out-File $LogPath -Append 

 

    # Execute for all opmodes 

    $FailedUserRights = AddUserRights 

 

    ####START THE AD FS WINDOWS SERVICE#### 

    

    Write-Host "`n Starting the AD FS service" 

    ($ElapsedTime.Elapsed.ToString())+" [WORK ITEM] Starting adfssrv" | Out-File $LogPath -Append 

 

    #check to see if SQL scripts need run. If yes, skip this step 

    If (($Mode -eq 1) -or $NeedsSQLWarning -or $FailedLdap -or $FailedServiceIdentity -or $FailedServiceStart -or $FailedSpn -or $FailedUserRights) 

    { 

        Write-Host "`tSkipped`n`tSee: POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" 

        ($ElapsedTime.Elapsed.ToString())+" [WARN]      Skipped starting adfssrv due to post-sample needs" | Out-File $LogPath -Append 

        $SkipServiceStart = $true 

    } 

    Else 

    { 

        # Start the AD FS Windows service. No need to check status since Start-Service does not throw if service is currently started. 

        $ADFSInstalled.Start() 

        $ADFSInstalled.WaitForStatus("Running",[System.TimeSpan]::FromSeconds(25)) 

 

        If (!$?) 

        { 

            Write-Host "`tFailed: The AD FS service could not be started.`n`tExamine the AD FS 2.0/Admin and AD FS 2.0 Tracing/Debug event logs for details." -ForegroundColor "red" 

            ($ElapsedTime.Elapsed.ToString())+" [ERROR]     adfssrv service failed to start. See Admin and Debug logs for details." | Out-File $LogPath -Append 

            $FailedServiceStart = $true 

        } 

        Else 

        { 

            Write-Host "`tSuccess" -ForegroundColor "green" 

            ($ElapsedTime.Elapsed.ToString())+" [INFO]      adfssrv started" | Out-File $LogPath -Append 

        } 

    } 

 

    ####NOTIFY ABOUT MANUALLY SETTING ITEMS 

 

    $NotifyCount = 1 

    Write-Host "`n`n`n POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" -ForegroundColor "yellow" 

    "`n`n`n POST-SAMPLE ITEMS THAT MUST BE EXECUTED MANUALLY" | Out-File $LogPath -Append 

 

    If ($FailedUserRights) 

    { 

        Write-Host "`n`n $NotifyCount. You must manually set User Rights Assigment for $NewName" -ForegroundColor "yellow" 

        Write-Host "    to allow `"Generate Security Audits`" and `"Log On As a Service`"." -ForegroundColor "yellow" 

        Write-Host "`n    Steps:`n    Start -> Run -> GPEdit.msc -> Computer Configuration -> Windows Settings ->" -ForegroundColor "yellow" 

        Write-Host "    Security Settings -> Local Policies -> User Rights Assignment" -ForegroundColor "yellow" 

        "`n`n $NotifyCount. You must manually set User Rights Assigment for $NewName" | Out-File $LogPath -Append 

        "    to allow `"Generate Security Audits`" and `"Log On As a Service`"." | Out-File $LogPath -Append 

        "`n    Steps:`n    Start -> Run -> GPEdit.msc -> Computer Configuration -> Windows Settings ->" | Out-File $LogPath -Append 

        "    Security Settings -> Local Policies -> User Rights Assignment" | Out-File $LogPath -Append 

        $NotifyCount += 1 

    } 

 

    If ($FailedLdap) 

    { 

        Write-Host "`n`n $NotifyCount. $NewName must have Read, Write, and Create Child permissions to the certificate" -ForegroundColor "yellow" 

        Write-Host "    sharing container in AD. These permissions were not set during execution and must be set manually." -ForegroundColor "yellow" 

        Write-Host "    LDAP path: $DN" -ForegroundColor "yellow" 

     

        "`n`n $NotifyCount. $NewName must have Read, Write, and Create Child permissions to the certificate" | Out-File $LogPath -Append 

        "    sharing container in AD. These permissions were not set during execution and must be set manually." | Out-File $LogPath -Append 

        "    LDAP path: $DN" | Out-File $LogPath -Append 

        $NotifyCount += 1 

    } 

 

    If ($NeedsSQLWarning) 

    { 

        If ($DBMode -eq "SQL") 

        { 

            Write-Host "`n`n $NotifyCount. Either the currently logged on user does not have appropriate permissions on the SQL Server," -ForegroundColor "yellow" 

            Write-Host "    or SQLCmd.exe was not found on this system. You must provide your SQL DBA with the SetPermissions.sql" -ForegroundColor "yellow" 

            Write-Host "    and UpdateServiceSettings.sql fileslocated in $env:Temp\ADFSSQLScripts." -ForegroundColor "yellow" 

            Write-Host "    The DBA should execute these scripts on the SQL Server where the AD FS" -ForegroundColor "yellow" 

            Write-Host "    Configuration and Artifact databases reside." -ForegroundColor "yellow" 

            Write-Host "`n    Syntax:" -ForegroundColor "yellow"  

            Write-Host "    sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\SetPermissions.sql" -ForegroundColor "yellow" 

            Write-Host "    -o $env:Temp\ADFSSQLScripts\SetPermissions-output.log" -ForegroundColor "yellow" 

            Write-Host "`n    sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql" -ForegroundColor "yellow" 

            Write-Host "    -o $env:Temp\ADFSSQLScripts\UpdateServiceSettings-output.log" -ForegroundColor "yellow" 

     

            "`n`n $NotifyCount. Either the currently logged on user does not have appropriate permissions on the SQL Server," | Out-File $LogPath -Append 

            "    or SQLCmd.exe was not found on this system. You must provide your SQL DBA with the SetPermissions.sql" | Out-File $LogPath -Append 

            "    and UpdateServiceSettings.sql fileslocated in $env:Temp\ADFSSQLScripts. The DBA should execute these" | Out-File $LogPath -Append 

            "    scripts on the SQL Server where the AD FS Configuration and Artifact databases reside." | Out-File $LogPath -Append 

            "`n    Syntax:" | Out-File $LogPath -Append 

            "    sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\SetPermissions.sql -o" | Out-File $LogPath -Append 

            "    $env:Temp\ADFSSQLScripts\SetPermissions-output.log" | Out-File $LogPath -Append 

            "`n    sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql -o" | Out-File $LogPath -Append 

            "    $env:Temp\ADFSSQLScripts\UpdateServiceSettings-output.log" | Out-File $LogPath -Append 

        } 

        Else 

        { 

            Write-Host "`n`n $NotifyCount. SQLCmd.exe was not found on this system. The SQL scripts must be executed" -ForegroundColor "yellow" 

            Write-Host "    manually using either SQL Management Studio or SQLCmd.exe. The scripts currently reside" -ForegroundColor "yellow" 

            Write-Host "    in $env:Temp\ADFSSQLScripts." -ForegroundColor "yellow" 

            Write-Host "`n    Syntax:" -ForegroundColor "yellow"  

            Write-Host "    sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\SetPermissions.sql" -ForegroundColor "yellow" 

            Write-Host "    -o $env:Temp\ADFSSQLScripts\SetPermissions-output.log" -ForegroundColor "yellow" 

            Write-Host "`n    sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql" -ForegroundColor "yellow" 

            Write-Host "    -o $env:Temp\ADFSSQLScripts\UpdateServiceSettings-output.log" -ForegroundColor "yellow" 

     

            "`n`n $NotifyCount. Either the currently logged on user does not have appropriate permissions on the SQL Server," | Out-File $LogPath -Append 

            "    or SQLCmd.exe was not found on this system. You must provide your SQL DBA with the SetPermissions.sql" | Out-File $LogPath -Append 

            "    and UpdateServiceSettings.sql fileslocated in $env:Temp\ADFSSQLScripts. The DBA should execute these" | Out-File $LogPath -Append 

            "    scripts on the SQL Server where the AD FS Configuration and Artifact databases reside." | Out-File $LogPath -Append 

            "`n    Syntax:" | Out-File $LogPath -Append 

            "    sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\SetPermissions.sql -o" | Out-File $LogPath -Append 

            "    $env:Temp\ADFSSQLScripts\SetPermissions-output.log" | Out-File $LogPath -Append 

            "`n    sqlcmd.exe -S $SQLHost -i $env:Temp\ADFSSQLScripts\UpdateServiceSettings.sql -o" | Out-File $LogPath -Append 

            "    $env:Temp\ADFSSQLScripts\UpdateServiceSettings-output.log" | Out-File $LogPath -Append 

        } 

         

        $NotifyCount += 1 

    } 

   

    If ($FailedSpn) 

    { 

        Write-Host "`n`n $NotifyCount. $NewName must have the SPN HOST/$FederationServiceName registered.`n    SPN registration failed during execution and must be handled manually.`n" -ForegroundColor "yellow" 

        Write-Host "    Syntax:`n    setspn -S HOST/$FederationServiceName $NewName" -ForegroundColor "yellow" 

     

        "`n`n $NotifyCount. $NewName must have the SPN HOST/$FederationServiceName registered.`n    SPN registration failed during execution and must be handled manually.`n" | Out-File $LogPath -Append 

        "    Syntax:`n    setspn -S HOST/$FederationServiceName $NewName" | Out-File $LogPath -Append 

        $NotifyCount += 1 

    } 

   

    If ($FailedServiceIdentity) 

    { 

        Write-Host "`n`n $NotifyCount. Failed setting the AD FS service identity to $NewName during execution.`n    This must be set manually in the Services console." -ForegroundColor "yellow" 

     

        "`n`n $NotifyCount. Failed setting the AD FS service identity to $NewName during execution.`n    This must be set manually in the Services console." | Out-File $LogPath -Append 

        $NotifyCount += 1 

    } 

   

    If ($Mode -eq 1) 

    { 

        Write-Host "`n`n $NotifyCount. Operating Mode $Mode was selected for this server, which means this sample must be executed`n    in Operating Mode 2 on the final server before the AD FS service is started on this server.`n    Once the sample has been run on the final server in Operating Mode 2, return to this server`n    to start the AD FS service." -ForegroundColor "yellow" 

        "`n`n $NotifyCount. Operating Mode $Mode was selected for this server, which means this sample must be executed`n    in Operating Mode 2 on the final server before the AD FS service is started on this server.`n    Once the sample has been run on the final server in Operating Mode 2, return to this server`n    to start the AD FS service." | Out-File $LogPath -Append 

        $NotifyCount += 1 

    } 

   

    If ($SkipServiceStart) 

    { 

        Write-Host "`n`n $NotifyCount. Service start was skipped during execution due to post-sample needs. The service must be manually started.`n`n    Syntax:`n    net start adfssrv" -ForegroundColor "yellow" 

     

        "`n`n $NotifyCount. Service start was skipped during execution due to post-sample needs.`n    The service must be manually started." | Out-File $LogPath -Append 

        $NotifyCount += 1 

    } 

   

    If ($FailedServiceStart) 

    { 

        Write-Host "`n`n $NotifyCount. Failed service start during execution.`n    The service must be manually started." -ForegroundColor "yellow" 

        Write-Host "    Syntax: net start adfssrv" -ForegroundColor "yellow" 

     

        "`n`n $NotifyCount. Failed service start during execution.`n    The service must be manually started." | Out-File $LogPath -Append 

        "    Syntax: net start adfssrv" | Out-File $LogPath -Append 

        $NotifyCount += 1 

    } 

   

    If ($NotifyCount -eq 1) 

    { 

        Write-Host "`n No post-sample items" -ForegroundColor "green" 

        "No post-sample items" | Out-File $LogPath -Append 

    } 


    Write-Host "`n`n It is recommended the old service account $OldName be deletd once the service account has been changed on all servers.`n" -ForegroundColor "yellow"

 

    Write-Host "`n`n Sample completed successfully. See ADFS_Change_Service_Account.log in the current directory for detail`n" -ForegroundColor "green" 

    "[END TIME] $(Get-Date)" | Out-File $LogPath -Append 

 

    $ErrorActionPreference = "continue" 

}


Export-ModuleMember -Function Add-AdfsServiceAccountRule

Export-ModuleMember -Function Remove-AdfsServiceAccountRule

Export-ModuleMember -Function Update-AdfsServiceAccount

Export-ModuleMember -Function Restore-AdfsSettingsFromBackup

How to Change ADFS Service Account

Description:

You have existing ADFS running with regular user account. To increase security you want to change the ADFS service account to use group managed service account or gMSA account.

Resolution:

Source of Information: https://github.com/microsoft/adfsToolbox/tree/master/serviceAccountModule

  1. Download the ServiceAccount.psm1 module to all of your AD FS servers (primary and secondary)

  2. Import the PowerShell Module on all servers

    In a PowerShell window, run the following: ipmo ServiceAccount.psm1

  3. For Windows Server 2016 and later, add a rule granting the new service account necessary permissions.

    In a PowerShell window on the primary AD FS server, run the following:

    Add-AdfsServiceAccountRule -ServiceAccount <ServiceAccount> -SecondaryServers <ListOfSecondaryServers>

    Note that <ServiceAccount> should be the service account you want to grant permissions to and can be provided either in the format Domain\User or merely User.

    <ListOfSecondaryServers> should be replaced with a list of secondary servers if the environment is a WID farm so that the configuration database can be synced across all machines.

  4. Change the service account on each machine in the farm.

    Beginning with the secondary servers, run the following:

    Update-AdfsServiceAccount

    Once the function has been executed on all secondary servers, proceed to run it on the primary server.

  5. If Device Registration Services (DRS) is set up in your AD FS environment, you must also use the Set-AdfsDeviceRegistration cmdlet (an internal command exposed by the service) to add the proper permissions to the new service account.

  6. For Windows Server 2016 and later, remove the rule granting permissions to the old service account.

    In a PowerShell window on the primary AD FS server, run the following:

    Remove-AdfsServiceAccountRule -ServiceAccount <ServiceAccount> -SecondaryServers <ListOfSecondaryServers>

    Note that <ServiceAccount> should be the service account you want to revoke permissions for and can be provided either in the format Domain\User or merely User.

    <ListOfSecondaryServers> should be replaced with a list of secondary servers if the environment is a WID farm so that the configuration database can be synced across all machines.


Upgrading Active Directory Federation Service (ADFS) Server

Description:

You want to upgrade existing ADFS and ADFS Proxy (WAP) to new version in new machines.

Resolution:

Upgrade ADFS Server
  • Export the existing SSL Certificate with Private Key
  • Import the SSL Certificate with Private Key on new ADFS and ADFS Proxy machines
  • Install the AD FS Role using Server Manager on the new machines
  • Configure AD FS using Server Manager > Will need Domain Admin Account and Service Account for ADFS
  • Move the Primary ADFS Role to the new ADFS machine
    • To check the role, please run: Get-AdfsSyncProperties
    • On the new primary machine, please run:
Set-AdfsSyncProperties -Role PrimaryComputer
    • On the old ADFS primary machine, please run:
Set-AdfsSyncProperties -Role SecondaryComputer -PrimaryComputerName FQDNnewADFS

  • Demote the old ADFS machines 
  • Raise the ADFS Farm Behavior Level (FBL)

    • Please run the following
      • Get-AdfsFarmInformation
      • Invoke-adfsfarmbehaviorlevelraise

  • Change DNS Record to point to the new AD FS Server
  • Test access and authentication to new ADFS

https://adfs.fabrikam.com/adfs/ls/idpinitiatedsignon.aspx

Upgrade WAP Server
  • Install Web Application Proxy Role using Server Manager on the new machines
  • Configure WAP using Server Manager > Will need local administrator account on the federation servers
  • Verifying the trust with the AD FS farm
    • Navigate to Applications and Services Logs > AD FS > Admin
    • You should be able to see an event with ID 245

Search Google