SPSWakeUP.ps1
|
<#PSScriptInfo .VERSION 4.2.4 .GUID 1fc873b1-5854-46cb-8632-29cee879bb55 .AUTHOR luigilink (Jean-Cyril DROUHIN) .COPYRIGHT .TAGS script powershell sharepoint warmup .LICENSEURI https://github.com/luigilink/SPSWakeUp/blob/main/LICENSE .PROJECTURI https://github.com/luigilink/SPSWakeUp .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .SYNOPSIS SPSWakeUP script for SharePoint OnPremises .DESCRIPTION SPSWakeUp is a PowerShell script tool to warm up all site collection in your SharePoint environment. It's compatible with all supported versions for SharePoint (2016 to Subscription Edition). Use WebRequest object in multi-thread to download JS, CSS and Pictures files. Log script results in log file, Configure automatically prerequisites for a best warm-up. .PARAMETER Action Use the Action parameter equal to Install if you want to add the warmup script in taskscheduler InstallAccount parameter need to be set PS D:\> E:\SCRIPT\SPSWakeUP.ps1 -Action Install -InstallAccount (Get-Credential) Use the Action parameter equal to Uninstall if you want to remove the warmup script from taskscheduler PS D:\> E:\SCRIPT\SPSWakeUP.ps1 -Action Uninstall Use the Action parameter equal to AdminSitesOnly if you want to warmup the Central Administration Site collection PS D:\> E:\SCRIPT\SPSWakeUP.ps1 -Action AdminSitesOnly .PARAMETER InstallAccount Need parameter InstallAccount whent you use the Action parameter equal to Install PS D:\> E:\SCRIPT\SPSWakeUP.ps1 -Install -InstallAccount (Get-Credential) .PARAMETER Transcript Use the boolean Transcript parameter if you want to start Transcrit PowerShell Feature. PS D:\> E:\SCRIPT\SPSWakeUP.ps1 -Transcript:$True .EXAMPLE SPSWakeUP.ps1 -Action Install -InstallAccount (Get-Credential) SPSWakeUP.ps1 -Action Uninstall SPSWakeUP.ps1 -Action AdminSitesOnly SPSWakeUP.ps1 -Transcript:$True .NOTES FileName: SPSWakeUP.ps1 Authors: luigilink (Jean-Cyril DROUHIN) Nutsoft (Des Finkenzeller) bed428 (Brian D.) Date: August 17, 2026 Version: 4.2.4 Licence: MIT License .LINK https://spjc.fr/ https://github.com/luigilink/spswakeup #> param ( [Parameter(Position = 1)] [validateSet('Install', 'Uninstall', 'Default', 'AdminSitesOnly', IgnoreCase = $true)] [System.String] $Action = 'Default', [Parameter(Position = 2)] [System.Management.Automation.PSCredential] $InstallAccount, [Parameter(Position = 3)] [System.Boolean] $Transcript = $false ) #region Initialization # Define variables $spsWakeupVersion = '4.2.4' $currentUser = ([Security.Principal.WindowsIdentity]::GetCurrent()).Name # Clear the host console Clear-Host # 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." } # Setting power management plan to High Performance" Start-Process -FilePath "$env:SystemRoot\system32\powercfg.exe" -ArgumentList '/s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' -NoNewWindow # Start Transcript parameter is equal to True if ($Transcript) { # Initialize the log file full path $pathLogFile = Join-Path -Path $PSScriptRoot -ChildPath ('SPSWakeUP_script_' + (Get-Date -Format yyyy-MM-dd_H-mm) + '.log') # Start Transcript with the log file Start-Transcript -Path $pathLogFile -IncludeInvocationHeader } #endregion #region functions function Add-SPSWakeUpEvent { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $Message, [Parameter(Mandatory = $true)] [System.String] $Source, [Parameter()] [ValidateSet('Error', 'Information', 'FailureAudit', 'SuccessAudit', 'Warning')] [System.String] $EntryType = 'Information', [Parameter()] [System.UInt32] $EventID = 1 ) $LogName = 'SPSWakeUp' if ([System.Diagnostics.EventLog]::SourceExists($Source)) { $sourceLogName = [System.Diagnostics.EventLog]::LogNameFromSourceName($Source, ".") if ($LogName -ne $sourceLogName) { Write-Verbose -Message "[ERROR] Specified source {$Source} already exists on log {$sourceLogName}" return } } else { if ([System.Diagnostics.EventLog]::Exists($LogName) -eq $false) { #Create event log $null = New-EventLog -LogName $LogName -Source $Source } else { [System.Diagnostics.EventLog]::CreateEventSource($Source, $LogName) } } try { $headerMessage = @" SPSWakeUp Script Version: $spsWakeupVersion User: $currentUser ComputerName: $($env:COMPUTERNAME) -------------------------------------------------------------- "@ Write-EventLog -LogName $LogName -Source $Source -EventId $EventID -Message ($headerMessage + "`r`n" + $Message) -EntryType $EntryType } catch { Write-Error -Message @" SPSWakeUp Script Version: $spsWakeupVersion An error occurred while adding Event Log in Source: $Source User: $currentUser ComputerName: $($env:COMPUTERNAME) Exception: $_ "@ } } function Get-SPSInstalledProductVersion { [OutputType([System.Diagnostics.FileVersionInfo])] param () $pathToSearch = 'C:\Program Files\Common Files\microsoft shared\Web Server Extensions\*\ISAPI\Microsoft.SharePoint.dll' $fullPath = Get-Item $pathToSearch -ErrorAction SilentlyContinue | Sort-Object { $_.Directory } -Descending | Select-Object -First 1 if ($null -eq $fullPath) { Write-Error -Message 'SharePoint path {C:\Program Files\Common Files\microsoft shared\Web Server Extensions} does not exist' } else { return [System.Diagnostics.FileVersionInfo]::GetVersionInfo($fullPath.FullName) } } function Add-SPSSheduledTask { param ( [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $ExecuteAsCredential, # Credentials for Task Schedule [Parameter(Mandatory = $true)] [System.String] $ActionArguments, # Arguments for the task action [Parameter()] [System.String] $TaskName = 'SPSWakeUP', # Name of the scheduled task to be added [Parameter()] [System.String] $TaskPath = 'SharePoint' # Path of the task folder ) # Initialize variables $TaskDate = Get-Date -Format yyyy-MM-dd # Current date in yyyy-MM-dd format $TaskCmd = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' # Path to PowerShell executable $UserName = $ExecuteAsCredential.UserName $Password = $ExecuteAsCredential.GetNetworkCredential().Password # Connect to the local TaskScheduler Service $TaskSvc = New-Object -ComObject ('Schedule.service') $TaskSvc.Connect($env:COMPUTERNAME) # Check if the folder exists, if not, create it try { $TaskFolder = $TaskSvc.GetFolder($TaskPath) # Attempt to get the task folder } catch { Write-Output "Task folder '$TaskPath' does not exist. Creating folder..." $RootFolder = $TaskSvc.GetFolder('\') # Get the root folder $RootFolder.CreateFolder($TaskPath) # Create the missing task folder $TaskFolder = $TaskSvc.GetFolder($TaskPath) # Get the newly created folder Write-Output "Successfully created task folder '$TaskPath'" } # Retrieve the scheduled task $getScheduledTask = $TaskFolder.GetTasks(0) | Where-Object -FilterScript { $_.Name -eq $TaskName } if ($getScheduledTask) { Write-Warning -Message 'Scheduled Task already exists - skipping.' # Task already exists [System.Runtime.InteropServices.Marshal]::ReleaseComObject($TaskSvc) | Out-Null Remove-Variable UserName, Password -ErrorAction SilentlyContinue } else { Write-Output '--------------------------------------------------------------' Write-Output "Adding '$TaskName' script in Task Scheduler Service ..." # Get credentials for Task Schedule $TaskAuthor = ([Security.Principal.WindowsIdentity]::GetCurrent()).Name # Author of the task $TaskUser = $UserName # Username for task registration $TaskUserPwd = $Password # Password for task registration # Add a new Task Schedule $TaskSchd = $TaskSvc.NewTask(0) $TaskSchd.RegistrationInfo.Description = "$($TaskName) Task - Start at 6:00 daily" # Task description $TaskSchd.RegistrationInfo.Author = $TaskAuthor # Task author $TaskSchd.Principal.RunLevel = 1 # Task run level (1 = Highest) # Task Schedule - Modify Settings Section $TaskSettings = $TaskSchd.Settings $TaskSettings.AllowDemandStart = $true $TaskSettings.Enabled = $true $TaskSettings.Hidden = $false $TaskSettings.StartWhenAvailable = $true # Task Schedule - Trigger Section $TaskTriggers = $TaskSchd.Triggers # Add Trigger Type 2 OnSchedule Daily Start at 6:00 AM $TaskTrigger1 = $TaskTriggers.Create(2) $TaskTrigger1.StartBoundary = $TaskDate + 'T06:00:00' $TaskTrigger1.DaysInterval = 1 $TaskTrigger1.Enabled = $true # Add Trigger Type 8 At StartUp Delay 10M $TaskTrigger2 = $TaskTriggers.Create(8) $TaskTrigger2.Delay = 'PT10M' $TaskTrigger2.Enabled = $true # Add Trigger Type 0 OnEvent IISReset $TrigSubscription = @" <QueryList><Query Id="0" Path="System"><Select Path="System">*[System[Provider[@Name='Microsoft-Windows-IIS-IISReset'] and EventID=3201]]</Select></Query></QueryList> "@ $TaskTrigger3 = $TaskTriggers.Create(0) $TaskTrigger3.Delay = 'PT20S' $TaskTrigger3.Subscription = $TrigSubscription $TaskTrigger3.Enabled = $true # Define the task action $TaskAction = $TaskSchd.Actions.Create(0) # 0 = Executable action $TaskAction.Path = $TaskCmd # Path to the executable $TaskAction.Arguments = $ActionArguments # Arguments for the executable try { # Register the task $TaskFolder.RegisterTaskDefinition($TaskName, $TaskSchd, 6, $TaskUser, $TaskUserPwd, 1) Write-Output "Successfully added '$TaskName' script in Task Scheduler Service" Add-SPSWakeUpEvent -Message "Successfully added '$TaskName' script in Task Scheduler Service" -Source 'Add-SPSSheduledTask' -EntryType 'Information' } catch { $catchMessage = @" An error occurred while adding the script in scheduled task: $($TaskName) ActionArguments: $($ActionArguments) Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage # Handle any errors during task registration Add-SPSWakeUpEvent -Message $catchMessage -Source 'Add-SPSSheduledTask' -EntryType 'Error' } finally { # Release COM objects [System.Runtime.InteropServices.Marshal]::ReleaseComObject($TaskSvc) | Out-Null # Remove variables Remove-Variable TaskSchd, TaskSettings, TaskTriggers, TaskTrigger1, TaskTrigger2, TaskTrigger3, TaskAction, TaskAuthor, TaskUser, TaskUserPwd -ErrorAction SilentlyContinue } } } function Remove-SPSSheduledTask { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [System.String] $TaskName, # Name of the scheduled task to be removed [Parameter()] [System.String] $TaskPath = 'SharePoint' # Path of the task folder ) # Connect to the local TaskScheduler Service $TaskSvc = New-Object -ComObject ('Schedule.service') $TaskSvc.Connect($env:COMPUTERNAME) # Check if the folder exists try { $TaskFolder = $TaskSvc.GetFolder($TaskPath) # Attempt to get the task folder } catch { Write-Output "Task folder '$TaskPath' does not exist." return } # Retrieve the scheduled task $getScheduledTask = $TaskFolder.GetTasks(0) | Where-Object -FilterScript { $_.Name -eq $TaskName } if ($null -eq $getScheduledTask) { Write-Warning -Message 'Scheduled Task already removed - skipping.' # Task not found } else { Write-Output '--------------------------------------------------------------' Write-Output "Removing $($TaskName) script in Task Scheduler Service ..." if ($PSCmdlet.ShouldProcess($TaskName, 'Remove scheduled task')) { try { $TaskFolder.DeleteTask($TaskName, $null) # Remove the task Write-Output "Successfully removed $($TaskName) script from Task Scheduler Service" Add-SPSWakeUpEvent -Message "Successfully removed '$TaskName' script from Task Scheduler Service" -Source 'Remove-SPSSheduledTask' -EntryType 'Information' } catch { $catchMessage = @" An error occurred while removing the script in scheduled task: $($TaskName) Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage # Handle any errors during task removal Add-SPSWakeUpEvent -Message $catchMessage -Source 'Remove-SPSSheduledTask' -EntryType 'Error' } finally { # Release COM objects [System.Runtime.InteropServices.Marshal]::ReleaseComObject($TaskSvc) | Out-Null } } } } function Install-SPSWakeUP { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Position = 2)] [System.Management.Automation.PSCredential] $InstallAccount ) # Initialize variables # Quote script path to support scheduled task execution when path contains spaces. $escapedScriptPath = $Path.Replace('"', '""') $ActionArguments = "-ExecutionPolicy Bypass -File `"$escapedScriptPath`"" $UserName = $InstallAccount.UserName $Password = $InstallAccount.GetNetworkCredential().Password $currentDomain = 'LDAP://' + ([ADSI]'').distinguishedName Write-Output "Checking Account `"$UserName`" ..." $dom = New-Object System.DirectoryServices.DirectoryEntry($currentDomain, $UserName, $Password) if ($null -eq $dom.Path) { Write-Warning -Message "Password Invalid for user:`"$UserName`"" Add-SPSWakeUpEvent -Message "Password Invalid for user:`"$UserName`"" -Source 'SPSWakeUP' -EntryType 'Error' Exit } else { Write-Output "Account `"$UserName`" is valid. Adding SPSWakeUp script in Task Scheduler Service ..." # 1. Add SPSWakeup script in a new scheduled Task Add-SPSSheduledTask -ExecuteAsCredential $InstallAccount -ActionArguments $ActionArguments # 2. Get All Web Applications Urls $getSPWebApps = Get-SPSWebAppUrl # 3. Add read access for Warmup User account in User Policies settings if ($null -ne $getSPWebApps) { Add-SPSUserPolicy -Urls $getSPWebApps -UserName $UserName } } # Remove variables Remove-Variable ActionArguments, escapedScriptPath, UserName, Password, currentDomain, dom } function Clear-SPSLog { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $path, [Parameter()] [System.UInt32] $days = 30 ) if (Test-Path $path) { # Define LastWriteTime parameter based on $days $LastWrite = (Get-Date).AddDays(-$days) # Get files based on lastwrite filter and specified folder $files = Get-ChildItem -Path $path -Filter '*.log' | Where-Object -FilterScript { $_.LastWriteTime -le "$LastWrite" } if ($files) { Write-Output "Cleaning log files in $path ..." foreach ($file in $files) { Write-Output "Deleting file $file ..." Remove-Item $file.FullName | Out-Null } } } } function Get-SPSThrottleLimit { # Get Number Of Throttle Limit process { try { $cimInstanceProc = @(Get-CimInstance -ClassName Win32_Processor) $cimInstanceSocket = $cimInstanceProc.count $numLogicalCpu = $cimInstanceProc[0].NumberOfLogicalProcessors * $cimInstanceSocket $NumThrottle = @{ $true = 10; $false = 2 * $numLogicalCpu }[$numLogicalCpu -ge 8] return $NumThrottle } catch { Write-Warning -Message $_ } } } function Add-SPSHostEntry { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $url ) $url = $url -replace 'https://', '' $url = $url -replace 'http://', '' $hostNameEntry = $url.split('/')[0] [void]$hostEntries.Add($hostNameEntry) } function Get-SPSAdminUrl { try { # Initialize ArrayList Object $tbCASitesURL = New-Object -TypeName System.Collections.ArrayList # Get Central Administration Url and add it in ArrayList Object $webAppADM = Get-SPWebApplication -IncludeCentralAdministration | Where-Object -FilterScript { $_.IsAdministrationWebApplication } [void]$tbCASitesURL.Add("$($webAppADM.Url)") # List of the most useful administration pages and Quick launch top links $urlsAdmin = @('Lists/HealthReports/AllItems.aspx', ` '_admin/FarmServers.aspx', ` '_admin/Server.aspx', ` '_admin/WebApplicationList.aspx', ` '_admin/ServiceApplications.aspx', ` 'applications.aspx', ` 'systemsettings.aspx', ` 'monitoring.aspx', ` 'backups.aspx', ` 'security.aspx', ` 'upgradeandmigration.aspx', ` 'apps.aspx', ` 'generalapplicationsettings.aspx') foreach ($urlAdmin in $urlsAdmin) { [void]$tbCASitesURL.Add("$($webAppADM.Url)$($urlAdmin)") } # Get Service Application Urls and then in ArrayList Object $sa = Get-SPServiceApplication $linkUrls = $sa | ForEach-Object { $_.ManageLink.Url } | Select-Object -Unique foreach ($linkUrl in $linkUrls) { $siteADMSA = $linkUrl.TrimStart('/') [void]$tbCASitesURL.Add("$($webAppADM.Url)$($siteADMSA)") } return $tbCASitesURL } catch { Write-Warning -Message $_ } } function Get-SPSPreferredUrl { <# .SYNOPSIS Returns the URL to use for local warm-up, choosing the scheme from the zone's real IIS bindings instead of the (possibly offloaded) public URL. .DESCRIPTION In an SSL offloading architecture, the Web Front End listens in HTTP only (no 443 binding) while the Default zone public URL is HTTPS. SPSWakeUp warms up the site collections locally (HOSTS entries pointing to 127.0.0.1), so it must target the scheme that IIS actually listens on locally, not the public (offloaded) scheme. Rule: if the zone exposes an HTTP ServerBindings entry, warm up in HTTP; otherwise (SecureBindings only) use HTTPS. The host header and path of the original URL are preserved, which also handles web applications exposing several bindings / host headers on the same zone. When the IIS bindings cannot be read (e.g. object model unavailable), the original URL is returned unchanged, preserving the previous behaviour. .PARAMETER WebApplication The SPWebApplication object owning the zone to inspect. .PARAMETER Zone The SPUrlZone to inspect. Defaults to 'Default'. .PARAMETER OriginalUrl The public/AAM URL whose host header and path must be preserved. When omitted, the zone response URI of the web application is used. #> [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] $WebApplication, [Parameter(Mandatory = $false)] $Zone = 'Default', [Parameter(Mandatory = $false)] [System.String] $OriginalUrl ) # Determine the reference URL whose host header + path must be preserved. $referenceUrl = $OriginalUrl if ([string]::IsNullOrEmpty($referenceUrl)) { try { $referenceUrl = $WebApplication.GetResponseUri($Zone).AbsoluteUri } catch { $referenceUrl = $null } } if ([string]::IsNullOrEmpty($referenceUrl)) { return $referenceUrl } try { $refUri = [System.Uri]$referenceUrl } catch { return $referenceUrl } # Read the real IIS bindings for the zone. Be defensive: the object model may be # unavailable (unit tests) or the zone may not be extended. $httpBindings = @() $httpsBindings = @() try { if ($null -ne $WebApplication -and $null -ne $WebApplication.IisSettings) { $iis = $WebApplication.IisSettings[$Zone] if ($null -ne $iis) { $httpBindings = @($iis.ServerBindings | Where-Object { $null -ne $_ }) $httpsBindings = @($iis.SecureBindings | Where-Object { $null -ne $_ }) } } } catch { return $referenceUrl } # No binding information available -> keep the original URL untouched. if ($httpBindings.Count -eq 0 -and $httpsBindings.Count -eq 0) { return $referenceUrl } # Prefer HTTP when the zone exposes an HTTP ServerBinding (covers SSL offloading); # otherwise use HTTPS (SecureBindings only). if ($httpBindings.Count -gt 0) { $scheme = 'http' $defaultPort = 80 $selectedBindings = $httpBindings } else { $scheme = 'https' $defaultPort = 443 $selectedBindings = $httpsBindings } # Preserve the host header from the reference URL (handles multiple host headers). $hostHeader = $refUri.Host # Keep a non-standard port when the matching binding uses one. $port = $defaultPort foreach ($binding in $selectedBindings) { $bindingHost = $binding.HostHeader $bindingPort = $binding.Port if (-not [string]::IsNullOrEmpty($bindingHost)) { if ($bindingHost -eq $hostHeader -and $null -ne $bindingPort) { $port = [int]$bindingPort break } } elseif ($null -ne $bindingPort) { # Catch-all binding (no host header) -> use its port. $port = [int]$bindingPort } } $pathAndQuery = $refUri.PathAndQuery if ([string]::IsNullOrEmpty($pathAndQuery)) { $pathAndQuery = '/' } if ($port -eq $defaultPort) { return ('{0}://{1}{2}' -f $scheme, $hostHeader, $pathAndQuery) } else { return ('{0}://{1}:{2}{3}' -f $scheme, $hostHeader, $port, $pathAndQuery) } } function Get-SPSSitesUrl { try { # Initialize ArrayList Object $tbSitesURL = New-Object -TypeName System.Collections.ArrayList # Get Url of all site collection $webApps = Get-SPWebApplication -ErrorAction SilentlyContinue if ($null -ne $webApps) { foreach ($webApp in $webApps) { foreach ($site in $webApp.sites) { if ($site.RootWeb.Url -notmatch 'sitemaster-') { # Use the scheme that IIS actually listens on locally (SSL offloading aware) $preferredUrl = Get-SPSPreferredUrl -WebApplication $webApp -Zone 'Default' -OriginalUrl $site.RootWeb.Url [void]$tbSitesURL.Add("$($preferredUrl)") } $site.Dispose() } } } return $tbSitesURL } catch { Write-Warning -Message $_ } } function Get-SPSWebAppUrl { try { # Initialize ArrayList Object $webAppURL = New-Object -TypeName System.Collections.ArrayList # hostEntries is provided by Invoke-SPSAllSite; initialize locally when # Get-SPSWebAppUrl is called from other flows (e.g. Install). if (-not (Get-Variable -Name hostEntries -Scope 1 -ErrorAction SilentlyContinue)) { Set-Variable -Name hostEntries -Scope Local -Value (New-Object -TypeName System.Collections.Generic.List[string]) } # Get SPwebApplication Object $webApps = Get-SPWebApplication -ErrorAction SilentlyContinue if ($null -ne $webApps) { foreach ($webapp in $webApps) { # Use the scheme that IIS actually listens on locally (SSL offloading aware) $responseUri = Get-SPSPreferredUrl -WebApplication $webapp -Zone 'Default' -OriginalUrl $webapp.GetResponseUri('Default').AbsoluteUri [void]$webAppURL.Add($responseUri) if ($null -eq (Get-SPServer | Where-Object { $responseUri -match $_.Name })) { Add-SPSHostEntry -Url $responseUri } } $sites = $webApps | ForEach-Object -Process { $_.sites } $HSNCs = $sites | Where-Object -FilterScript { $_.HostHeaderIsSiteName -eq $true } foreach ($HSNC in $HSNCs) { if ($HSNC.Url -notmatch 'sitemaster-') { # Resolve the preferred scheme from the HSNC's own web application $preferredHSNCUrl = Get-SPSPreferredUrl -WebApplication $HSNC.WebApplication -Zone 'Default' -OriginalUrl $HSNC.Url [void]$webAppURL.Add($preferredHSNCUrl) Add-SPSHostEntry -Url $preferredHSNCUrl } $HSNC.Dispose() } } else { $webAppURL = $null } return $webAppURL } catch { Write-Warning -Message $_ } } function Invoke-SPSWebRequest { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String[]] $Urls, [Parameter(Mandatory = $true)] $throttleLimit ) $ScriptBlock = { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter()] $Useragent = [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome, [Parameter()] $SessionWeb ) Process { try { $startProcess = Get-Date if ($null -ne $SessionWeb) { $webResponse = Invoke-WebRequest -Uri $Uri ` -WebSession $SessionWeb ` -TimeoutSec 90 ` -UserAgent $Useragent ` -UseBasicParsing } else { $webResponse = Invoke-WebRequest -Uri $Uri ` -UseDefaultCredentials ` -TimeoutSec 90 ` -UserAgent $Useragent ` -UseBasicParsing } $timeExec = '{0:N2}' -f (((Get-Date) - $startProcess).TotalSeconds) $Response = "$([System.int32]$webResponse.StatusCode) - $($webResponse.StatusDescription)" } catch { $Response = $_.Exception.Message } finally { if ($webResponse) { $webResponse.Close() Remove-Variable webResponse } } $RunResult = New-Object PSObject $RunResult | Add-Member -MemberType NoteProperty -Name Url -Value $Uri $RunResult | Add-Member -MemberType NoteProperty -Name 'Time(s)' -Value $TimeExec $RunResult | Add-Member -MemberType NoteProperty -Name Status -Value $Response $RunResult } } try { # Initialize variables and runpsace for Multi-Threading Request $psUserAgent = [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome $Jobs = [System.Collections.Generic.List[object]]::new() $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault() $Pool = [runspacefactory]::CreateRunspacePool(1, $throttleLimit, $iss, $Host) $Pool.Open() } catch { $catchMessage = @" An error occurred invoking CreateRunspacePool Method Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage # Handle any errors during task removal Add-SPSWakeUpEvent -Message $catchMessage -Source 'CreateRunspacePool' -EntryType 'Error' } try { # Initialize WebSession from First SPWebApplication object $webApp = Get-SpWebApplication | Select-Object -first 1 # Use the scheme that IIS actually listens on locally (SSL offloading aware) $authentBaseUrl = Get-SPSPreferredUrl -WebApplication $webApp -Zone 'Default' -OriginalUrl $webApp.GetResponseUri('Default').AbsoluteUri $authentUrl = ("$($authentBaseUrl)" + '_windows/default.aspx?ReturnUrl=/_layouts/15/Authenticate.aspx?Source=%2f') Write-Output "Getting webSession by opening $($authentUrl) with Invoke-WebRequest" Invoke-WebRequest -Uri $authentUrl ` -SessionVariable webSession ` -UseDefaultCredentials ` -TimeoutSec 90 ` -UserAgent $psUserAgent ` -UseBasicParsing } catch { $catchMessage = @" An error occurred invoking Invoke-WebRequest Function Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage # Handle any errors during task removal Add-SPSWakeUpEvent -Message $catchMessage -Source 'Invoke-WebRequest' -EntryType 'Error' } foreach ($Url in $Urls) { try { if (-not([string]::IsNullOrEmpty($Url))) { $Job = [powershell]::Create().AddScript($ScriptBlock).AddParameter('Uri', $Url).AddParameter('UserAgent', $psUserAgent).AddParameter('SessionWeb', $webSession) $Job.RunspacePool = $Pool $Jobs.Add([PSCustomObject]@{ Url = $Url Pipe = $Job Result = $Job.BeginInvoke() }) } } catch { $catchMessage = @" An error occurred invoking Create Method for Url: $($Url) Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage # Handle any errors during task removal Add-SPSWakeUpEvent -Message $catchMessage -Source 'Invoke-SPSWebRequest' -EntryType 'Error' } } While ($Jobs.Result.IsCompleted -contains $false) { Start-Sleep -S 1 } $Results = [System.Collections.Generic.List[object]]::new() foreach ($Job in $Jobs) { try { $Results.Add($Job.Pipe.EndInvoke($Job.Result)) } catch { $catchMessage = @" An error occurred invoking Job Pipe EndInvoke Method for Url: $($Job.Url) Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage # Handle any errors during task removal Add-SPSWakeUpEvent -Message $catchMessage -Source 'Invoke-SPSWebRequest' -EntryType 'Error' } finally { $Job.Pipe.Dispose() } } $Pool.Dispose() $Results } function Invoke-SPSAdminSite { # Disable LoopBack Check Disable-LoopbackCheck # Disable IE First Run Disable-IEFirstRun # Get Central Administration Service Instance $serviceInstance = Get-SPServiceInstance -Server $env:COMPUTERNAME if ($null -eq $serviceInstance) { $domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain $fqdn = "$($env:COMPUTERNAME).$domain" $serviceInstance = Get-SPServiceInstance -Server $fqdn } if ($null -ne $serviceInstance) { $serviceInstance = $serviceInstance | Where-Object -FilterScript { $_.GetType().Name -eq "SPWebServiceInstance" -and $_.Name -eq "WSS_Administration" } } if ($null -ne $serviceInstance) { Write-Output 'Opening All Central Admin Urls with Invoke-WebRequest, Please Wait...' $getSPADMSites = Get-SPSAdminUrl foreach ($spADMUrl in $getSPADMSites) { try { $startInvoke = Get-Date $argsInvokeWebReq = @{ Uri = $spADMUrl UseDefaultCredentials = $true TimeoutSec = 90 UserAgent = [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome UseBasicParsing = $true } $webResponse = Invoke-WebRequest @argsInvokeWebReq $TimeExec = '{0:N2}' -f (((Get-Date) - $startInvoke).TotalSeconds) Write-Output '-----------------------------------' Write-Output "| Url : $spADMUrl" Write-Output "| Time : $TimeExec" Write-Output "| Status : $($webResponse.StatusCode) - $($webResponse.StatusDescription)" } catch { $catchMessage = @" An error occurred with Invoke-WebRequest CMDLet Url: $($spADMUrl) Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage Add-SPSWakeUpEvent -Message $catchMessage -Source 'Invoke-SPSAdminSite' -EntryType 'Error' } } } else { Write-Warning -Message "No Central Admin Service Instance running on $env:COMPUTERNAME" Add-SPSWakeUpEvent -Message "No Central Admin Service Instance running on $env:COMPUTERNAME" -Source 'Invoke-SPSAdminSite' -EntryType 'Warning' } } function Invoke-SPSAllSite { # Initialize variables $DateStarted = Get-Date $hostEntries = New-Object -TypeName System.Collections.Generic.List[string] $hostsFile = "$env:windir\System32\drivers\etc\HOSTS" $hostsFileCopy = $hostsFile + '.' + (Get-Date -UFormat "%y%m%d%H%M%S").ToString() + '.copy' # Get All Web Applications Urls, Host Named Site Collection and Site Collections Write-Output '--------------------------------------------------------------' Write-Output 'Get URLs of All Web Applications ...' $getSPWebApps = Get-SPSWebAppUrl # Filter out null or empty URLs (orphaned objects) if ($null -ne $getSPWebApps) { $originalWebAppCount = $getSPWebApps.Count $getSPWebApps = $getSPWebApps | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } $filteredWebAppCount = $getSPWebApps.Count if ($filteredWebAppCount -lt $originalWebAppCount) { Write-Warning "Filtered out $($originalWebAppCount - $filteredWebAppCount) null or empty Web Application URL(s)" } } Write-Output '--------------------------------------------------------------' Write-Output 'Get URLs of All Site Collection ...' $getSPSites = Get-SPSSitesUrl # Filter out null or empty URLs (orphaned SPSite objects) if ($null -ne $getSPSites) { $originalSiteCount = $getSPSites.Count $getSPSites = $getSPSites | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } $filteredSiteCount = $getSPSites.Count if ($filteredSiteCount -lt $originalSiteCount) { Write-Warning "Filtered out $($originalSiteCount - $filteredSiteCount) null or empty Site Collection URL(s) - possible orphaned SPSite objects" } } if (($null -ne $getSPWebApps -and $getSPWebApps.Count -gt 0) -and ($null -ne $getSPSites -and $getSPSites.Count -gt 0)) { if ($hostEntries) { # Disable LoopBack Check Disable-LoopbackCheck # Disable IE First Run Disable-IEFirstRun # Remove Duplicate Entries $hostEntries = $hostEntries | Get-Unique # Initialize variables $hostFileNeedsBackup = $true $hostIPV4Addr = '127.0.0.1' # Make backup copy of the Hosts file with today's date Add Web Application and Host Named Site Collection Urls in HOSTS system File Write-Output '--------------------------------------------------------------' Write-Output 'Add Urls of All Web Applications or HSNC in HOSTS File ...' foreach ($hostEntry in $hostEntries) { $hostEntryIsPresent = Select-String -Path $hostsFile -Pattern $hostEntry if ($null -eq $hostEntryIsPresent) { if ($hostFileNeedsBackup) { Write-Verbose -Message "Backing up $hostsFile file to: $hostsFileCopy" Copy-Item -Path $hostsFile -Destination $hostsFileCopy -Force $hostFileNeedsBackup = $false } # Remove http or https information to keep only HostName or FQDN if ($hostEntry.Contains(':')) { Write-Warning -Message "$hostEntry cannot be added in HOSTS File, only web applications with 80 or 443 port are added." } else { Write-Output "Adding $($hostEntry) in HOSTS file" Add-Content -Path $hostsFile -value "$hostIPV4Addr `t $hostEntry" } } } } # Request Url with Invoke-WebRequest CmdLet for All Urls Write-Output '--------------------------------------------------------------' Write-Output 'Opening All sites Urls with Invoke-WebRequest, Please Wait...' # Additional validation before calling Invoke-SPSWebRequest if ($null -ne $getSPSites -and $getSPSites.Count -gt 0) { $InvokeResults = Invoke-SPSWebRequest -Urls $getSPSites -throttleLimit (Get-SPSThrottleLimit) # Show the results foreach ($InvokeResult in $InvokeResults) { if ($null -ne $InvokeResult.Url) { Write-Output '-----------------------------------' Write-Output "| Url : $($InvokeResult.Url)" Write-Output "| Time : $($InvokeResult.'Time(s)') seconds" Write-Output "| Status : $($InvokeResult.Status)" } } $DateEnded = Get-Date $totalUrls = $getSPSites.Count $totalDuration = ($DateEnded - $DateStarted).TotalSeconds $outputMessage = @" ------------------------------------- | SPSWakeUp Script - Invoke-SPSAllSite | PowerShell Version : $($PSVersionTable.PSVersion) | Started on : $DateStarted | Completed on : $DateEnded | SPSWakeUp waked up $totalUrls urls in $totalDuration seconds -------------------------------------------------------------- | REPORTING: Memory Usage for each worker process (W3WP.EXE) | Process Creation Date | Memory | Application Pool Name -------------------------------------------------------------- "@ $w3wpProcess = Get-CimInstance Win32_Process -Filter "name = 'w3wp.exe'" | Select-Object WorkingSetSize, CommandLine, CreationDate | Sort-Object CommandLine foreach ($w3wpProc in $w3wpProcess) { $w3wpProcCmdLine = $w3wpProc.CommandLine.Replace('c:\windows\system32\inetsrv\w3wp.exe -ap "', '') $pos = $w3wpProcCmdLine.IndexOf('"') $appPoolName = $w3wpProcCmdLine.Substring(0, $pos) $w3wpMemoryUsage = [Math]::Round($w3wpProc.WorkingSetSize / 1MB) $outputMessage += ("`r`n" + "| $($w3wpProc.CreationDate) | $($w3wpMemoryUsage) MB | $($appPoolName)") } Write-Output $outputMessage Add-SPSWakeUpEvent -Message $outputMessage -Source 'Invoke-SPSAllSite'-EntryType Information } else { Write-Warning 'No site URLs found to process. $getSPSites is null or empty.' $warningMessage = "SPSWakeUp Script - No site URLs found to wake up. Please verify that Get-SPSSitesUrl returned valid URLs." Write-Output $warningMessage Add-SPSWakeUpEvent -Message $warningMessage -Source 'Invoke-SPSAllSite' -EntryType Warning } # Clean the copy files of system HOSTS folder Clear-HostsFileCopy -hostsFilePath $hostsFile } else { Write-Warning 'Cannot proceed: Web Applications or Site Collections not found or are empty.' if ($null -eq $getSPWebApps -or $getSPWebApps.Count -eq 0) { Write-Warning 'No Web Applications found. Please verify that Get-SPWebApplication returned valid URLs.' } if ($null -eq $getSPSites -or $getSPSites.Count -eq 0) { Write-Warning 'No Site Collections found. Please verify that Get-SPSite returned valid URLs.' } $errorMessage = "SPSWakeUp Script - Cannot proceed: insufficient data (Web Applications count: $($getSPWebApps.Count), Site Collections count: $($getSPSites.Count))" Add-SPSWakeUpEvent -Message $errorMessage -Source 'Invoke-SPSAllSite' -EntryType Warning } } function Disable-LoopbackCheck { $lsaPath = 'HKLM:\System\CurrentControlSet\Control\Lsa' $lsaPathValue = Get-ItemProperty -path $lsaPath if (-not ($lsaPathValue.DisableLoopbackCheck -eq '1')) { Write-Output 'Disabling Loopback Check...' New-ItemProperty -Path HKLM:\System\CurrentControlSet\Control\Lsa -Name 'DisableLoopbackCheck' -value '1' -PropertyType dword -Force | Out-Null } else { Write-Output 'Loopback Check already Disabled - skipping.' } } function Disable-IEFirstRun { $iefirstrunPath = 'HKCU:\Software\Microsoft\Internet Explorer\Main' $iefirstrunPathValue = Get-ItemProperty -path $iefirstrunPath if (-not ($iefirstrunPathValue.DisableFirstRunCustomize -eq '1')) { Write-Output 'Disabling IE First Run...' New-ItemProperty -Path $iefirstrunPath -Name 'DisableFirstRunCustomize' -value '1' -PropertyType dword -Force | Out-Null } else { Write-Output 'IE First Run already Disabled - skipping.' } } function Clear-HostsFileCopy { Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $hostsFilePath, [Parameter()] [System.UInt32] $numberFiles = 10 ) $hostsFolderPath = Split-Path $hostsFilePath if (Test-Path $hostsFolderPath) { # Get files with .copy extension, sort them by name, from most recent to oldest and skip the first numberFiles variable $copyFiles = Get-ChildItem -Path $hostsFolderPath -Filter '*.copy' | Sort-Object -Descending -Property Name | Select-Object -Skip $numberFiles if ($copyFiles) { Write-Output '--------------------------------------------------------------' Write-Output "Cleaning backup HOSTS files in $hostsFolderPath ..." foreach ($copyFile in $copyFiles) { if ($null -ne $copyFile) { Write-Output " * Deleting File $copyFile ..." Remove-Item $copyFile.FullName | Out-Null } } } } } function Add-SPSUserPolicy { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] $Urls, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $UserName ) Write-Output '--------------------------------------------------------------' Write-Output "Add Read Access to $UserName for All Web Applications ..." foreach ($url in $Urls) { try { $webapp = [Microsoft.SharePoint.Administration.SPWebApplication]::Lookup("$url") $displayName = 'SPSWakeUP Account' # If the web app is not Central Administration if ($webapp.IsAdministrationWebApplication -eq $false) { # If the web app is using Claims auth, change the user accounts to the proper syntax if ($webapp.UseClaimsAuthentication -eq $true) { $user = (New-SPClaimsPrincipal -identity $UserName -identitytype 1).ToEncodedString() } else { $user = $UserName } Write-Output "Checking Read access for $user account to $url..." [Microsoft.SharePoint.Administration.SPPolicyCollection]$policies = $webapp.Policies $policyExist = $policies | Where-Object -FilterScript { $_.Displayname -eq 'SPSWakeUP Account' } if (-not ($policyExist)) { Write-Output "Applying Read access for $user account to $url..." [Microsoft.SharePoint.Administration.SPPolicy]$policy = $policies.Add($user, $displayName) $policyRole = $webApp.PolicyRoles.GetSpecialRole([Microsoft.SharePoint.Administration.SPPolicyRoleType]::FullRead) if ($null -ne $policyRole) { $policy.PolicyRoleBindings.Add($policyRole) } $webapp.Update() Write-Output "Done Applying Read access for `"$user`" account to `"$url`"" } } } catch { $catchMessage = @" An error occurred while adding SPWebApp Policy for UserName: $UserName Url: $($url) Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage Add-SPSWakeUpEvent -Message $catchMessage -Source 'SPSWakeUP' -EntryType 'Error' } } } function Set-SPSProxySetting { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateSet('Backup', 'Disable', 'Restore', IgnoreCase = $true)] [System.String] $Action, [Parameter(Position = 1)] [System.String] $BackupFile = "$PSScriptRoot\SPSWakeUP_proxy_backup.json" ) $regPath = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' switch ($Action) { 'Backup' { if ($PSCmdlet.ShouldProcess($BackupFile, 'Back up proxy settings')) { try { $proxySettings = Get-ItemProperty -Path $regPath | Select-Object AutoConfigURL, ProxyEnable, ProxyServer, ProxyOverride $proxySettings | ConvertTo-Json | Out-File -FilePath $BackupFile -Encoding UTF8 Write-Output "Proxy settings backed up to $BackupFile" } catch { $catchMessage = @" An error occurred while saving proxy settings File : $BackupFile Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage } } } 'Disable' { if ($PSCmdlet.ShouldProcess('Proxy settings', 'Disable')) { try { $itemProperties = @('AutoConfigURL', 'ProxyServer', 'ProxyOverride') Set-ItemProperty -Path $regPath -Name ProxyEnable -Value 0 foreach ($itemProperty in $itemProperties) { if (Get-ItemProperty -Path $regPath -Name $itemProperty -ErrorAction SilentlyContinue) { Remove-ItemProperty -Path $regPath -Name $itemProperty -ErrorAction SilentlyContinue } } Write-Output 'All proxy settings disabled.' } catch { $catchMessage = @" An error occurred while disabling proxy settings Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage } } } 'Restore' { if (Test-Path $BackupFile) { if ($PSCmdlet.ShouldProcess($BackupFile, 'Restore proxy settings')) { try { $proxySettings = Get-Content $BackupFile | ConvertFrom-Json Set-ItemProperty -Path $regPath -Name ProxyEnable -Value $proxySettings.ProxyEnable if ($proxySettings.ProxyServer) { Set-ItemProperty -Path $regPath -Name ProxyServer -Value $proxySettings.ProxyServer } else { Remove-ItemProperty -Path $regPath -Name ProxyServer -ErrorAction SilentlyContinue } if ($proxySettings.AutoConfigURL) { Set-ItemProperty -Path $regPath -Name AutoConfigURL -Value $proxySettings.AutoConfigURL } else { Remove-ItemProperty -Path $regPath -Name AutoConfigURL -ErrorAction SilentlyContinue } if ($proxySettings.ProxyOverride) { Set-ItemProperty -Path $regPath -Name ProxyOverride -Value $proxySettings.ProxyOverride } else { Remove-ItemProperty -Path $regPath -Name ProxyOverride -ErrorAction SilentlyContinue } Write-Output "Proxy settings restored from $BackupFile" } catch { $catchMessage = @" An error occurred while restoring proxy settings File : $BackupFile Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage } } } else { Write-Output "Backup file not found at $BackupFile" } } } } #endregion #region initialize SharePoint Context # Load SharePoint snap-in/module for all actions that need SharePoint cmdlets. if ($Action -in @('Install', 'Uninstall', 'Default', 'AdminSitesOnly')) { try { $installedVersion = Get-SPSInstalledProductVersion if ($installedVersion.ProductMajorPart -eq 15 -or $installedVersion.ProductBuildPart -le 12999) { if ($null -eq (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue)) { Add-PSSnapin Microsoft.SharePoint.PowerShell } } else { if (-not (Get-Module SharePointServer)) { Import-Module SharePointServer -Verbose:$false -WarningAction SilentlyContinue -DisableNameChecking } } } catch { $catchMessage = @" Failed to load SharePoint module on $($env:COMPUTERNAME) Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage Add-SPSWakeUpEvent -Message $catchMessage -Source 'Initialize Module' -EntryType 'Error' } try { $currentSPServer = Get-SPServer | Where-Object -FilterScript { $_.Address -eq $env:COMPUTERNAME } if ($null -ne $currentSPServer -and (Get-SPFarm).buildversion.major -ge 16) { if ($currentSPServer.Role -eq 'Search') { Write-Warning -Message 'You run this script on server with Search MinRole' Add-SPSWakeUpEvent -Message 'Search MinRole is not supported in SPSWakeUp' -Source 'Server MinRole' -EntryType 'Warning' Exit } } } catch { Write-Error -Message @" An error occurred while checking the SharePoint Server Role Exception: $($_.Exception.Message) "@ } } #endregion #region main switch ($Action) { 'Uninstall' { # Remove SPSWakeup script from scheduled Task Remove-SPSSheduledTask -TaskName 'SPSWakeUP' } 'Install' { if ($null -eq $InstallAccount) { Write-Warning -Message ('SPSWakeUp: Install parameter is set. Please set also InstallAccount ' + ` "parameter. `nSee https://github.com/luigilink/SPSWakeUp/wiki for details.") Exit } else { # Initialize variables $scriptFullPath = Join-Path -Path $PSScriptRoot -ChildPath 'SPSWakeUP.ps1' # Add SPSWakeup script in a new scheduled Task Install-SPSWakeUP -Path $scriptFullPath -InstallAccount $InstallAccount } } 'AdminSitesOnly' { # Phase 1: collect SharePoint URLs in current process (PS 5.1 compatible) $jsonPath = Join-Path -Path $PSScriptRoot -ChildPath 'SPSWakeUp_urls.json' $warmupScript = Join-Path -Path $PSScriptRoot -ChildPath 'SPSWakeUp-pwsh.ps1' Write-Output '--------------------------------------------------------------' Write-Output 'Phase 1: Collecting URLs in current PowerShell process ...' $adminUrls = Get-SPSAdminUrl $payload = [ordered]@{ Action = 'AdminSitesOnly' GeneratedAt = (Get-Date -Format 'o') ThrottleLimit = (Get-SPSThrottleLimit) AdminUrls = @($adminUrls | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) SiteUrls = @() WebAppUrls = @() HostEntries = @() AuthUrl = '' } $payload | ConvertTo-Json -Depth 5 | Out-File -FilePath $jsonPath -Encoding UTF8 -Force # Phase 2: warm up via PS 7.x if available, otherwise fallback to PS 5.1. if (Test-Path -Path $jsonPath) { $pwshCmd = Get-Command pwsh -ErrorAction SilentlyContinue $pwshPath = if ($null -ne $pwshCmd) { $pwshCmd.Source } else { $null } if ([string]::IsNullOrEmpty($pwshPath)) { $pwshPath = 'C:\Program Files\PowerShell\7\pwsh.exe' } if ((Test-Path -Path $pwshPath) -and (Test-Path -Path $warmupScript)) { Write-Output '--------------------------------------------------------------' Write-Output 'Phase 2: Warming up with PowerShell 7.x ...' $warmupArgs = "-ExecutionPolicy Bypass -File `"$warmupScript`" -Action AdminSitesOnly -InputJsonPath `"$jsonPath`"" Start-Process -FilePath $pwshPath -ArgumentList $warmupArgs -Wait -NoNewWindow } else { Write-Warning 'PowerShell 7.x not found. Falling back to Windows PowerShell 5.1 warm-up flow.' Set-SPSProxySetting -Action 'Backup' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" Set-SPSProxySetting -Action 'Disable' Invoke-SPSAdminSite Set-SPSProxySetting -Action 'Restore' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" } } else { Write-Warning "JSON file not found after collect phase: $jsonPath" } } Default { # Phase 1: collect SharePoint URLs in current process (PS 5.1 compatible) $jsonPath = Join-Path -Path $PSScriptRoot -ChildPath 'SPSWakeUp_urls.json' $warmupScript = Join-Path -Path $PSScriptRoot -ChildPath 'SPSWakeUp-pwsh.ps1' Write-Output '--------------------------------------------------------------' Write-Output 'Phase 1: Collecting URLs in current PowerShell process ...' $hostEntries = New-Object -TypeName System.Collections.Generic.List[string] $adminUrls = Get-SPSAdminUrl $webAppUrls = Get-SPSWebAppUrl $siteUrls = Get-SPSSitesUrl $authUrl = '' try { $firstWebApp = Get-SPWebApplication | Select-Object -First 1 if ($null -ne $firstWebApp) { # Use the scheme that IIS actually listens on locally (SSL offloading aware) $authBaseUrl = Get-SPSPreferredUrl -WebApplication $firstWebApp -Zone 'Default' -OriginalUrl $firstWebApp.GetResponseUri('Default').AbsoluteUri $authUrl = "$($authBaseUrl)_windows/default.aspx?ReturnUrl=/_layouts/15/Authenticate.aspx?Source=%2f" } } catch { Write-Warning "Could not determine authentication URL: $($_.Exception.Message)" } $payload = [ordered]@{ Action = 'Default' GeneratedAt = (Get-Date -Format 'o') ThrottleLimit = (Get-SPSThrottleLimit) AdminUrls = @($adminUrls | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) SiteUrls = @($siteUrls | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) WebAppUrls = @($webAppUrls | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) HostEntries = @($hostEntries | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique) AuthUrl = $authUrl } $payload | ConvertTo-Json -Depth 5 | Out-File -FilePath $jsonPath -Encoding UTF8 -Force # Phase 2: warm up via PS 7.x if available, otherwise fallback to PS 5.1. if (Test-Path -Path $jsonPath) { $pwshCmd = Get-Command pwsh -ErrorAction SilentlyContinue $pwshPath = if ($null -ne $pwshCmd) { $pwshCmd.Source } else { $null } if ([string]::IsNullOrEmpty($pwshPath)) { $pwshPath = 'C:\Program Files\PowerShell\7\pwsh.exe' } if ((Test-Path -Path $pwshPath) -and (Test-Path -Path $warmupScript)) { Write-Output '--------------------------------------------------------------' Write-Output 'Phase 2: Warming up with PowerShell 7.x ...' $warmupArgs = "-ExecutionPolicy Bypass -File `"$warmupScript`" -Action Default -InputJsonPath `"$jsonPath`"" Start-Process -FilePath $pwshPath -ArgumentList $warmupArgs -Wait -NoNewWindow } else { Write-Warning 'PowerShell 7.x not found. Falling back to Windows PowerShell 5.1 warm-up flow.' Set-SPSProxySetting -Action 'Backup' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" Set-SPSProxySetting -Action 'Disable' Invoke-SPSAdminSite Invoke-SPSAllSite Set-SPSProxySetting -Action 'Restore' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" } } else { Write-Warning "JSON file not found after collect phase: $jsonPath" } } } # Stop Transcript parameter is equal to True if ($Transcript) { Stop-Transcript # Clean the folder of log files Clear-SPSLog -path $PSScriptRoot } Exit #endregion |