diff --git a/CHANGELOG.md b/CHANGELOG.md index f359d62..08b1bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - 2023-10-18 +## [Unreleased] - 2023-10-29 ### Added @@ -24,4 +24,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Wiki Documentation in repository - Add : - wiki/Home.md - wiki/Getting-Started.md + - wiki/Configuration.md + - wiki/Usage.md - .github/workflows/wiki.yml + +### Changed + +- SPSTrust.ps1: + - Update parameter description + - Add [ValidateScript({ Test-Path $_ -and $_ -like '*.json' })] in ConfigFile parameter + - Add missing comments diff --git a/README.md b/README.md index 2162884..0f38e51 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Description -SPSTrust is a PowerShell script tool to configure trust Farm in your SharePoint environment. +SPSTrust is a PowerShell script tool to configure trust Farm in your SharePoint environment. The script follows the documentation: [Share service applications across farms in SharePoint Server](https://learn.microsoft.com/en-us/sharepoint/administration/share-service-applications-across-farms). It's compatible with all supported versions for SharePoint OnPremises (2016 to Subscription Edition). diff --git a/scripts/Modules/util.psm1 b/scripts/Modules/util.psm1 index ebd4453..d39f4fd 100644 --- a/scripts/Modules/util.psm1 +++ b/scripts/Modules/util.psm1 @@ -1,98 +1,94 @@ #region Import Modules +# Import the custom module 'sps.util.psm1' from the script's directory Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'sps.util.psm1') -Force #endregion function Invoke-SPSCommand { [CmdletBinding()] - param - ( + param ( [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] - $Credential, - + $Credential, # Credential to be used for executing the command [Parameter()] [Object[]] - $Arguments, - + $Arguments, # Optional arguments for the script block [Parameter(Mandatory = $true)] [ScriptBlock] - $ScriptBlock, - + $ScriptBlock, # Script block containing the commands to execute [Parameter(Mandatory = $true)] [System.String] - $Server + $Server # Target server where the commands will be executed ) - $VerbosePreference = 'Continue' + # Base script to ensure the SharePoint snap-in is loaded $baseScript = @" - if (`$null -eq (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue)) - { - Add-PSSnapin Microsoft.SharePoint.PowerShell - } - + if (`$null -eq (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue)) + { + Add-PSSnapin Microsoft.SharePoint.PowerShell + } "@ - + $invokeArgs = @{ ScriptBlock = [ScriptBlock]::Create($baseScript + $ScriptBlock.ToString()) } + # Add arguments if provided if ($null -ne $Arguments) { $invokeArgs.Add("ArgumentList", $Arguments) } + # Ensure a credential is provided if ($null -eq $Credential) { throw 'You need to specify a Credential' } else { - Write-Verbose -Message ("Executing using a provided credential and local PSSession " + ` - "as user $($Credential.UserName)") - - # Running garbage collection to resolve issues related to Azure DSC extention use + Write-Verbose -Message ("Executing using a provided credential and local PSSession " + "as user $($Credential.UserName)") + # Running garbage collection to resolve issues related to Azure DSC extension use [GC]::Collect() - + # Create a new PowerShell session on the target server using the provided credentials $session = New-PSSession -ComputerName $Server ` -Credential $Credential ` -Authentication CredSSP ` -Name "Microsoft.SharePoint.PSSession" ` - -SessionOption (New-PSSessionOption -OperationTimeout 0 ` - -IdleTimeout 60000) ` + -SessionOption (New-PSSessionOption -OperationTimeout 0 -IdleTimeout 60000) ` -ErrorAction Continue - + + # Add the session to the invocation arguments if the session is created successfully if ($session) { $invokeArgs.Add("Session", $session) } - try { + # Invoke the command on the target server return Invoke-Command @invokeArgs -Verbose } catch { - throw $_ + throw $_ # Throw any caught exceptions } finally { + # Remove the session to clean up if ($session) { Remove-PSSession -Session $session } } } } + function Get-SPSServer { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] - param - ( + param ( [Parameter(Mandatory = $true)] [System.String] - $Server, - + $Server, # Name of the SharePoint server [Parameter()] [System.Management.Automation.PSCredential] - $InstallAccount + $InstallAccount # Credential for accessing the SharePoint server ) - Write-Verbose "Getting SharePoint Servers of Farm '$Server'" + # Use the Invoke-SPSCommand function to get SharePoint servers $result = Invoke-SPSCommand -Credential $InstallAccount ` -Arguments $PSBoundParameters ` -Server $Server ` -ScriptBlock { - (Get-SPServer | Where-Object -FilterScript { $_.Role -ne 'Invalid' }).Name + (Get-SPServer | Where-Object -FilterScript { $_.Role -ne 'Invalid' }).Name } return $result } @@ -101,29 +97,29 @@ function Clear-SPSLog { param ( [Parameter(Mandatory = $true)] [System.String] - $path, - + $path, # Path to the log files [Parameter()] [System.UInt32] - $Retention = 180 + $Retention = 180 # Number of days to retain log files ) - + # Check if the log file path exists if (Test-Path $path) { # Get the current date $Now = Get-Date - # Define LastWriteTime parameter based on $days + # Define LastWriteTime parameter based on $Retention $LastWrite = $Now.AddDays(-$Retention) - # Get files based on lastwrite filter and specified folder - $files = Get-Childitem -Path $path -Filter "$($logFileName)*" | Where-Object -FilterScript { + # Get files based on last write filter and specified folder + $files = Get-ChildItem -Path $path -Filter "$($logFileName)*" | Where-Object -FilterScript { $_.LastWriteTime -le "$LastWrite" } + # If files are found, proceed to delete them if ($files) { Write-Output '--------------------------------------------------------------' Write-Output "Cleaning log files in $path ..." foreach ($file in $files) { if ($null -ne $file) { Write-Output "Deleting file $file ..." - Remove-Item $file.FullName | out-null + Remove-Item $file.FullName | Out-Null } else { Write-Output 'No more log files to delete' diff --git a/scripts/SPSTrust.ps1 b/scripts/SPSTrust.ps1 index 1c9493a..1478de3 100644 --- a/scripts/SPSTrust.ps1 +++ b/scripts/SPSTrust.ps1 @@ -1,32 +1,29 @@ <# .SYNOPSIS - SPSTrust is a PowerShell script tool to configure trust Farm in your SharePoint environment. + SPSTrust is a PowerShell script tool to configure trusted farms in your SharePoint environment. .DESCRIPTION - SPSTrust.ps1 is a PowerShell script tool to configure SharePoint Trust. - It's compatible with PowerShell version 5.0 and later. + SPSTrust.ps1 is a PowerShell script that configures SharePoint trust relationships between farms. + Compatible with PowerShell version 5.0 and later. .PARAMETER ConfigFile - Need parameter ConfigFile, example: - PS D:\> E:\SCRIPT\SPSTrust.ps1 -ConfigFile 'contoso-PROD.json' + Specifies the path to the JSON configuration file, containing details about the application, environment, and certificate paths. .PARAMETER FarmAccount - Need parameter FarmAccount, example: - PS D:\> E:\SCRIPT\SPSTrust.ps1 -ConfigFile 'contoso-PROD.json' -FarmAccount (Get-Credential) + Specifies the credential for the service account that runs the script. .PARAMETER CleanServices - Need parameter CleanServices to remove connected service application, example: - PS D:\> E:\SCRIPT\SPSTrust.ps1 -ConfigFile 'contoso-PROD.json' -CleanServices + Optional switch to remove published services on each trusted farm. .EXAMPLE - SPSTrust.ps1 -ConfigFile 'contoso-PROD.json' - SPSTrust.ps1 -ConfigFile 'contoso-PROD.json' -CleanServices + SPSTrust.ps1 -ConfigFile 'contoso-PROD.json' -FarmAccount (Get-Credential) + SPSTrust.ps1 -ConfigFile 'contoso-PROD.json' -FarmAccount (Get-Credential) -CleanServices .NOTES FileName: SPSTrust.ps1 Author: luigilink (Jean-Cyril DROUHIN) - Date: Ocotober 17, 2024 - Version: 1.0.0 + Date: October 17, 2024 + Version: 1.1.0 .LINK https://spjc.fr/ @@ -34,241 +31,260 @@ #> param( [Parameter(Position = 1, Mandatory = $true)] + [ValidateScript({ Test-Path $_ -and $_ -like '*.json' })] [System.String] - $ConfigFile, + $ConfigFile, # Path to the configuration file - [Parameter(Position = 2)] + [Parameter(Position = 2, Mandatory = $true)] [System.Management.Automation.PSCredential] - $FarmAccount, + $FarmAccount, # Credential for the FarmAccount [Parameter(Position = 3)] [switch] - $CleanServices + $CleanServices # Switch parameter to clean services ) -#region Main +#region Initialization +# Clear the host console Clear-Host + +# Set the window title $Host.UI.RawUI.WindowTitle = "SPSTrust script running on $env:COMPUTERNAME" + +# Define the path to the helper module $script:HelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules' + +# Import the helper module Import-Module -Name (Join-Path -Path $script:HelperModulePath -ChildPath 'util.psm1') -Force -if (Test-Path $ConfigFile) { - $jsonEnvCfg = get-content $ConfigFile | ConvertFrom-Json - $Application = $jsonEnvCfg.ApplicationName - $Environment = $jsonEnvCfg.ConfigurationName - $certFolder = $jsonEnvCfg.CertFileShared - $scriptFQDN = $jsonEnvCfg.Domain - $spFarmsObj = $jsonEnvCfg.Farms - $spTrustsObj = $jsonEnvCfg.Trusts +# Ensure the script is running with administrator privileges +if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { + Throw "Administrator rights are required. Please re-run this script as an Administrator." } -else { - Throw "Missing $ConfigFile" + +# Load the configuration file +try { + if (Test-Path $ConfigFile) { + $jsonEnvCfg = Get-Content $ConfigFile | ConvertFrom-Json + $Application = $jsonEnvCfg.ApplicationName + $Environment = $jsonEnvCfg.ConfigurationName + $certFolder = $jsonEnvCfg.CertFileShared + $scriptFQDN = $jsonEnvCfg.Domain + $spFarmsObj = $jsonEnvCfg.Farms + $spTrustsObj = $jsonEnvCfg.Trusts + } + else { + Throw "Configuration file '$ConfigFile' not found." + } +} +catch { + Write-Error "Failed to load configuration file: $_" + Exit } -# Define variable +# Define variables $SPSTrustVersion = '1.0.0' $getDateFormatted = Get-Date -Format yyyy-MM-dd $spsTrustFileName = "$($Application)-$($Environment)-$($getDateFormatted)" $currentUser = ([Security.Principal.WindowsIdentity]::GetCurrent()).Name -$scriptRootPath = Split-Path -parent $MyInvocation.MyCommand.Definition +$scriptRootPath = Split-Path -Parent $MyInvocation.MyCommand.Definition $pathLogsFolder = Join-Path -Path $scriptRootPath -ChildPath 'Logs' -# Initialize required folders -# Check if the path exists +# Initialize logs if (-Not (Test-Path -Path $pathLogsFolder)) { - # If the path does not exist, create the directory - New-Item -ItemType Directory -Path $pathLogsFolder + New-Item -ItemType Directory -Path $pathLogsFolder -Force } -# Initialize Start-Transcript $pathLogFile = Join-Path -Path $pathLogsFolder -ChildPath ($spsTrustFileName + '.log') -$DateStarted = Get-date -$psVersion = ($host).Version.ToString() +$DateStarted = Get-Date +$psVersion = ($Host).Version.ToString() +# Start transcript to log the output Start-Transcript -Path $pathLogFile -IncludeInvocationHeader + +# Output the script information Write-Output '-----------------------------------------------' -Write-Output "| Automated Script - Configuration Trust $SPSTrustVersion |" -Write-Output "| Started on - $DateStarted by $currentUser|" -Write-Output "| PowerShell Version - $psVersion |" +Write-Output "| SPSTrust Configuration Script v$SPSTrustVersion |" +Write-Output "| Started on - $DateStarted by $currentUser |" +Write-Output "| PowerShell Version - $psVersion |" Write-Output '-----------------------------------------------' +#endregion -# Check Permission Level -if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { - Write-Warning -Message 'You do not have Administrator rights to run this script!`nPlease re-run this script as an Administrator!' - Break -} -else { - Write-Verbose -Message "Setting power management plan to `"High Performance`"..." - Start-Process -FilePath "$env:SystemRoot\system32\powercfg.exe" ` - -ArgumentList '/s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' ` - -NoNewWindow - - # Export STS and ROOT certificates for each Farm - foreach ($spFarm in $spFarmsObj) { - $spRootCertPath = "$($certFolder)\$($spFarm.Name)_ROOT.cer" - $spTargetServer = "$($spFarm.Server).$($scriptFQDN)" - if (Test-Path $spRootCertPath ) { - Write-Verbose -Message "$($spFarm.Name)_ROOT.cer already exists in file shared" +#region Main Process + +# Set power management plan to "High Performance" +Write-Verbose -Message "Setting power management plan to 'High Performance'..." +Start-Process -FilePath "$env:SystemRoot\system32\powercfg.exe" -ArgumentList '/s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' -NoNewWindow + +# 1. Exchange trust certificates between the farms +# Export STS and ROOT certificates for each Farm +foreach ($spFarm in $spFarmsObj) { + $spRootCertPath = "$($certFolder)\$($spFarm.Name)_ROOT.cer" + $spTargetServer = "$($spFarm.Server).$scriptFQDN" + + try { + # Check if the ROOT certificate already exists; if not, export it + if (-Not (Test-Path $spRootCertPath)) { + Export-SPSTrustedRootAuthority -Name $spFarm.Name -Server $spTargetServer -InstallAccount $FarmAccount -CertificateFilePath $certFolder + Write-Output "Exported ROOT certificate for $($spFarm.Name)." } else { - Export-SPSTrustedRootAuthority -Name $spFarm.Name ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount ` - -CertificateFilePath $certFolder + Write-Output "ROOT certificate for $($spFarm.Name) already exists." } + } + catch { + Write-Error "Failed to export ROOT certificate for $($spFarm.Name): $_" + } - $spSTSCertPath = "$($certFolder)\$($spFarm.Name)_STS.cer" - if (Test-Path $spSTSCertPath ) { - Write-Verbose -Message "$($spFarm.Name)_STS.cer already exists in file shared" + $spSTSCertPath = "$($certFolder)\$($spFarm.Name)_STS.cer" + + try { + # Check if the STS certificate already exists; if not, export it + if (-Not (Test-Path $spSTSCertPath)) { + Export-SPSSecurityTokenCertificate -Name $spFarm.Name -Server $spTargetServer -InstallAccount $FarmAccount -CertificateFilePath $certFolder + Write-Output "Exported STS certificate for $($spFarm.Name)." } else { - Export-SPSSecurityTokenCertificate -Name $spFarm.Name ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount ` - -CertificateFilePath $certFolder + Write-Output "STS certificate for $($spFarm.Name) already exists." } - - $getFarmId = Get-SPSFarmId -Server $spTargetServer -InstallAccount $FarmAccount - New-Variable -Name "$($spFarm.Name)_FarmId" -Value $getFarmId -Force } - # Establishing trust on the publishing farm - Import STS and ROOT certificates - foreach ($spTrust in $spTrustsObj) { - $spServer = $jsonEnvCfg.Farms | Where-Object -FilterScript { $_.Name -eq $spTrust.LocalFarm } - $spTargetServer = "$($spServer.Server).$($scriptFQDN)" - $spRemoteServers = $spTrust.RemoteFarms - $spServices = $spTrust.Services - $AppCode = $spTrust.AppCode - foreach ($spRemoteServer in $spRemoteServers) { - $spRootCertPath = "$($certFolder)\$($spRemoteServer)_ROOT.cer" - $currentValues = Get-SPSTrustedRootAuthority -Name "$($spRemoteServer)_ROOT" ` - -CertificateFilePath $spRootCertPath ` - -InstallAccount $FarmAccount ` - -Server $spTargetServer + catch { + Write-Error "Failed to export STS certificate for $($spFarm.Name): $_" + } +} - if ($currentValues.Ensure -eq 'Absent') { - Set-SPSTrustedRootAuthority -Name "$($spRemoteServer)_ROOT" ` - -CertificateFilePath $spRootCertPath ` - -InstallAccount $FarmAccount ` - -Server $spTargetServer +# Establish trust on the publishing farm - Import STS and ROOT certificates +foreach ($spTrust in $spTrustsObj) { + $spServer = $jsonEnvCfg.Farms | Where-Object -FilterScript { $_.Name -eq $spTrust.LocalFarm } + $spTargetServer = "$($spServer.Server).$($scriptFQDN)" + $spRemoteServers = $spTrust.RemoteFarms + $spServices = $spTrust.Services + + foreach ($spRemoteServer in $spRemoteServers) { + $spRootCertPath = "$($certFolder)\$($spRemoteServer)_ROOT.cer" + $currentValues = Get-SPSTrustedRootAuthority -Name "$($spRemoteServer)_ROOT" -CertificateFilePath $spRootCertPath -InstallAccount $FarmAccount -Server $spTargetServer + + # Check and establish ROOT trust + if ($currentValues.Ensure -eq 'Absent') { + try { + Set-SPSTrustedRootAuthority -Name "$($spRemoteServer)_ROOT" -CertificateFilePath $spRootCertPath -InstallAccount $FarmAccount -Server $spTargetServer + Write-Output "Trust established with ROOT for $($spRemoteServer)." } - else { - Write-Verbose -Message "$($spRemoteServer)_ROOT already exists in TrustedRootAuthority" + catch { + Write-Error "Failed to establish ROOT trust for $($spRemoteServer): $_" } - if ($spServices -notcontains 'Content' ) { - $spSTSCertPath = "$($certFolder)\$($spRemoteServer)_STS.cer" - $currentValues = Get-SPSTrustedServiceTokenIssuer -Name "$($spRemoteServer)_STS" ` - -CertificateFilePath $spSTSCertPath ` - -InstallAccount $FarmAccount ` - -Server $spTargetServer - - if ($currentValues.Ensure -eq 'Absent') { - Set-SPSTrustedServiceTokenIssuer -Name "$($spRemoteServer)_STS" ` - -CertificateFilePath $spSTSCertPath ` - -InstallAccount $FarmAccount ` - -Server $spTargetServer + } + else { + Write-Verbose -Message "$($spRemoteServer)_ROOT already exists in TrustedRootAuthority" + } + + # Check and establish STS trust if not a 'Content' service + if ($spServices -notcontains 'Content') { + $spSTSCertPath = "$($certFolder)\$($spRemoteServer)_STS.cer" + $currentValues = Get-SPSTrustedServiceTokenIssuer -Name "$($spRemoteServer)_STS" -CertificateFilePath $spSTSCertPath -InstallAccount $FarmAccount -Server $spTargetServer + + if ($currentValues.Ensure -eq 'Absent') { + try { + Set-SPSTrustedServiceTokenIssuer -Name "$($spRemoteServer)_STS" -CertificateFilePath $spSTSCertPath -InstallAccount $FarmAccount -Server $spTargetServer + Write-Output "Trust established with STS for $($spRemoteServer)." } - else { - Write-Verbose -Message "$($spRemoteServer)_STS already exists in TrustedServiceTokenIssuer" + catch { + Write-Error "Failed to establish STS trust for $($spRemoteServer): $_" } } + else { + Write-Verbose -Message "$($spRemoteServer)_STS already exists in TrustedServiceTokenIssuer" + } } + } - # Publish Service Application - foreach ($spService in $spServices) { - if ($spService -ne 'Content') { - $currentValues = Get-SPSPublishedServiceApplication -Name $spService ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount - Write-Verbose -Message "Getting uri of service $spService" - New-Variable -Name "$($spService)_URI" -Value $currentValues.Uri -Force - if ($currentValues.Ensure -eq 'Absent') { - Publish-SPSServiceApplication -Name $spService ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount - } - else { - Write-Verbose -Message "The service $($spService) is already Published" - } - # Set permissions to Published Service Application - foreach ($spRemoteServer in $spRemoteServers) { - $spFarmID = Get-Variable -Name "$($spRemoteServer)_FarmId" - $currentValues = Get-SPSPublishedServiceAppPermission -FarmId "$($spFarmID.Value)" ` - -Name $spService ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount - if ($currentValues.Ensure -eq 'Absent') { - Set-SPSPublishedServiceAppPermission -FarmId "$($spFarmID.Value)" ` - -Name $spService ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount - } - else { - Write-Verbose -Message "The Farm $($spRemoteServer) is already added in $($spService)" - } - } + # 2. On the publishing farm, publish the service application + foreach ($spService in $spServices) { + if ($spService -ne 'Content') { + $currentValues = Get-SPSPublishedServiceApplication -Name $spService -Server $spTargetServer -InstallAccount $FarmAccount + Write-Verbose -Message "Getting URI of service $spService" + + # Store the URI of the service in a variable + New-Variable -Name "$($spService)_URI" -Value $currentValues.Uri -Force + + if ($currentValues.Ensure -eq 'Absent') { + Publish-SPSServiceApplication -Name $spService -Server $spTargetServer -InstallAccount $FarmAccount + } + else { + Write-Verbose -Message "The service $($spService) is already Published" } } + } - # Set permissions to Application Discovery and Load Balancing Service Application + # 3. On the publishing farm, set the permission to the appropriate service applications for the consuming farm. + foreach ($spService in $spServices) { + # Set permissions to Published Service Application foreach ($spRemoteServer in $spRemoteServers) { - $spFarmID = Get-Variable -Name "$($spRemoteServer)_FarmId" - $currentValues = Get-SPSTopologyServiceAppPermission -FarmId "$($spFarmID.Value)" ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount + $spFarmID = Get-SPSFarmId -Server $spRemoteServer -InstallAccount $FarmAccount + $currentValues = Get-SPSPublishedServiceAppPermission -FarmId "$($spFarmID.Value)" -Name $spService -Server $spTargetServer -InstallAccount $FarmAccount + + # If permissions are not set, set them if ($currentValues.Ensure -eq 'Absent') { - Set-SPSTopologyServiceAppPermission -FarmId "$($spFarmID.Value)" ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount + Set-SPSPublishedServiceAppPermission -FarmId "$($spFarmID.Value)" -Name $spService -Server $spTargetServer -InstallAccount $FarmAccount } else { - Write-Verbose -Message "The Farm $($spRemoteServer) is already added in Application Discovery Permissions" + Write-Verbose -Message "The Farm $($spRemoteServer) is already added in $($spService)" } } + } + + # Set permissions to Application Discovery and Load Balancing Service Application + foreach ($spRemoteServer in $spRemoteServers) { + $spFarmID = Get-SPSFarmId -Server $spRemoteServer -InstallAccount $FarmAccount + $currentValues = Get-SPSTopologyServiceAppPermission -FarmId "$($spFarmID.Value)" -Server $spTargetServer -InstallAccount $FarmAccount + + # If permissions are not set, set them + if ($currentValues.Ensure -eq 'Absent') { + Set-SPSTopologyServiceAppPermission -FarmId "$($spFarmID.Value)" -Server $spTargetServer -InstallAccount $FarmAccount + } + else { + Write-Verbose -Message "The Farm $($spRemoteServer) is already added in Application Discovery Permissions" + } + } - # Connect each published service application on remote farm - foreach ($spService in $spServices) { - if ($spService -ne 'Content') { - $spServicePublishedUri = Get-Variable -Name "$($spService)_URI" + # Connect each published service application on remote farm + foreach ($spService in $spServices) { + if ($spService -ne 'Content') { + $spServicePublishedUri = Get-Variable -Name "$($spService)_URI" + foreach ($spRemoteServer in $spRemoteServers) { + $spServer = $jsonEnvCfg.Farms | Where-Object -FilterScript { $_.Name -eq $spRemoteServer } + $spTargetServer = ($spServer.Server) + '.' + "$($scriptFQDN)" - foreach ($spRemoteServer in $spRemoteServers) { - $spServer = $jsonEnvCfg.Farms | Where-Object -FilterScript { $_.Name -eq $spRemoteServer } - $spTargetServer = ($spServer.Server) + '.' + "$($scriptFQDN)" + # If CleanServices switch is enabled, remove existing service app proxy + if ($CleanServices) { + Remove-SPSPublishedServiceAppProxy -Name $spService -Server $spTargetServer -InstallAccount $FarmAccount + } + else { + $currentValues = Get-SPSPublishedServiceAppProxy -Name $spService -Server $spTargetServer -InstallAccount $FarmAccount - if ($CleanServices) { - Remove-SPSPublishedServiceAppProxy -Name $spService ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount + # If the service app proxy is not present, create a new one + if ($currentValues.Ensure -eq 'Absent') { + New-SPSPublishedServiceAppProxy -Name $spService -Server $spTargetServer -ServiceUri $spServicePublishedUri.value -InstallAccount $FarmAccount } else { - $currentValues = Get-SPSPublishedServiceAppProxy -Name $spService ` - -Server $spTargetServer ` - -InstallAccount $FarmAccount - - if ($currentValues.Ensure -eq 'Absent') { - New-SPSPublishedServiceAppProxy -Name $spService ` - -Server $spTargetServer ` - -ServiceUri $spServicePublishedUri.value ` - -InstallAccount $FarmAccount - } - else { - Write-Verbose -Message "The Service Application Proxy $($spService) is already added in Farm $($spRemoteServer)" - } + Write-Verbose -Message "The Service Application Proxy $($spService) is already added in Farm $($spRemoteServer)" } } } } } - Trap { Continue } - - $DateEnded = Get-date - Write-Output '-----------------------------------------------' - Write-Output "| Automated Script - Configuration Trust |" - Write-Output "| Started on - $DateStarted |" - Write-Output "| Completed on - $DateEnded |" - Write-Output '-----------------------------------------------' - Stop-Transcript - Remove-Variable * -ErrorAction SilentlyContinue; - Remove-Module *; - $error.Clear(); - Exit - #endregion } +#endregion + +# Clean-Up +Trap { Continue } +$DateEnded = Get-Date +Write-Output '-----------------------------------------------' +Write-Output "| SPSTrust Script Completed |" +Write-Output "| Started on - $DateStarted |" +Write-Output "| Ended on - $DateEnded |" +Write-Output '-----------------------------------------------' +Stop-Transcript +Remove-Variable * -ErrorAction SilentlyContinue +Remove-Module * -ErrorAction SilentlyContinue +$error.Clear() +Exit diff --git a/wiki/Configuration.md b/wiki/Configuration.md new file mode 100644 index 0000000..0a339cd --- /dev/null +++ b/wiki/Configuration.md @@ -0,0 +1,109 @@ +# Configuration + +To customize the script for your environment, you need to prepare a JSON configuration file. Below is a sample structure for the file: + +```json +{ + "$schema": "http://json-schema.org/schema#", + "contentVersion": "1.0.0.0", + "ConfigurationName": "PROD", + "ApplicationName": "contoso", + "Domain": "contoso.com", + "CertFileShared": "\\\\srvfileshared.contoso.com\\certsfolder", + "Trusts": [ + { + "LocalFarm": "SEARCH", + "RemoteFarms": ["CONTENT", "SERVICES"], + "Services": ["CONTOSOPRODSCH"] + }, + { + "LocalFarm": "SERVICES", + "RemoteFarms": ["CONTENT", "SEARCH"], + "Services": ["CONTOSOPRODUPS", "CONTOSOPRODMMS", "CONTOSOPRODSSA"] + }, + { + "LocalFarm": "CONTENT", + "RemoteFarms": ["SEARCH", "SERVICES"], + "Services": ["Content"] + } + ], + "Farms": [ + { + "Name": "SEARCH", + "Server": "srvcontososearch" + }, + { + "Name": "SERVICES", + "Server": "srvcontososervices" + }, + { + "Name": "CONTENT", + "Server": "srvcontosocontent" + } + ] +} +``` + +## Configuration and Application + +`ConfigurationName` is used to populate the content of `Environment` PowerShell Variable. +`ApplicationName` is used to populate the content of `Application` PowerShell Variable. + +## Certificate Configuration + +Certificate File Shared Path: `\\srvfileshared.contoso.com\certsfolder` + +The certificate file is stored in a shared folder, accessible to all relevant servers, ensuring secure communication across the server farms. + +> [!IMPORTANT] +> The credential used in the FarmAccount parameter needs write permission on this file share + +## Trust Relationships + +This configuration defines specific trust relationships between different server farms in the PROD environment. Each trust relationship specifies a local farm, the remote farms it trusts, and the services available to those farms. + +### Trust Definitions + +1. **SEARCH Farm** + + - **Trusted Remote Farms**: `CONTENT`, `SERVICES` + - **Exposed Services**: `CONTOSOPRODSCH` + +2. **SERVICES Farm** + + - **Trusted Remote Farms**: `CONTENT`, `SEARCH` + - **Exposed Services**: + - `CONTOSOPRODUPS` (User Profile Service) + - `CONTOSOPRODMMS` (Managed Metadata Service) + - `CONTOSOPRODSSA` (Search Service Application) + +3. **CONTENT Farm** + - **Trusted Remote Farms**: `SEARCH`, `SERVICES` + - **Exposed Service**: `Content` + +These trust relationships allow each farm to communicate securely with one another, sharing specific services as needed. + +> [!IMPORTANT] +> You need to use the same service account to configure trust between farms + +## Farm Server Details + +Each farm is associated with a dedicated server, as outlined below: + +| Farm Name | Server Name | +| --------- | ------------------ | +| SEARCH | srvcontososearch | +| SERVICES | srvcontososervices | +| CONTENT | srvcontosocontent | + +These servers are configured to handle specific workloads as per their assigned farm roles. + +## Notes + +- File Path Format: Ensure that the file path syntax (`\\`) is correctly configured for shared network access. +- Farm Names: The `LocalFarm` and `RemoteFarms` properties should match the Farms names exactly to maintain trust relationships. +- Service Availability: Each farm's services are restricted to only trusted farms as specified, ensuring a secure, segmented setup for production use. + +## Next Step + +For the next steps, go to the [Usage](./Usage) page. diff --git a/wiki/Getting-Started.md b/wiki/Getting-Started.md index 6be98b7..7db09ad 100644 --- a/wiki/Getting-Started.md +++ b/wiki/Getting-Started.md @@ -30,13 +30,9 @@ In the above example, `$CredSSPDelegates` can be a wildcard name (such as "\*.co 3. Add the script in task scheduler by running the following command: ```powershell -.\SPSTrust.ps1 -ConfigFile 'contoso-PROD.json' -Install -InstallAccount (Get-Credential) +.\SPSTrust.ps1 -ConfigFile 'contoso-PROD.json' -FarmAccount (Get-Credential) ``` -> [!IMPORTANT] -> Configure the StoredCredential parameter in JSON before running the script in installation mode. -> Run the Install mode with the same account than you used the in InstallAccount parameter - ## Next Step For the next steps, go to the [Configuration](./Configuration) page. diff --git a/wiki/Usage.md b/wiki/Usage.md new file mode 100644 index 0000000..9bf7a4c --- /dev/null +++ b/wiki/Usage.md @@ -0,0 +1,25 @@ +# Usage + +## Parameters + +| Parameter | Description | +| ---------------- | ------------------------------------------------- | +| `-ConfigFile` | Specifies the path to the configuration file. | +| `-FarmAccount` | Specifies the service account who runs the script | +| `-CleanServices` | Remove published services on each trusted farm | + +### Basic Usage Example + +Run the script with a specified configuration and farm account: + +```powershell +.\SPSWeather.ps1 -ConfigFile 'contoso-PROD.json' -FarmAccount (Get-Credential) +``` + +### Clean Services Usage Example + +Remove published services on each trusted farm: + +```powershell +.\SPSWeather.ps1 -ConfigFile 'contoso-PROD.json' -CleanServices +```