From bb8e87a6793dec3b174c2cd008463927e7060441 Mon Sep 17 00:00:00 2001 From: LuigiLink Date: Wed, 6 May 2026 11:03:05 +0200 Subject: [PATCH 1/4] Update release notes and changelog for version 4.1.3; implement Pester tests and fix various bugs in SPSWakeUP script --- CHANGELOG.md | 14 +++- RELEASE-NOTES.md | 37 ++++++++- scripts/SPSWakeUP.ps1 | 164 ++++++++++++++++++++------------------ tests/SPSWakeUP.Tests.ps1 | 44 +++++----- 4 files changed, 158 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcd4618..feabcd6 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] +## [4.1.3] - 2026-05-06 ### Added @@ -17,10 +17,14 @@ Implement Pester tests for script functionality, resource management, and securi SPSWakeUP.ps1: +- Fix variable name inconsistency in Invoke-SPSWebRequest — $webApp was assigned but $webapp (different case) was used when constructing the webSession authentication URL, causing the URL to be built from a null reference. + - The term 'Clear-SPSLog' is not recognized as the name of a cmdlet ([issue #34](https://github.com/luigilink/SPSWakeUp/issues/34)). - Fix function name typo: Disable-IEFirsRun → Disable-IEFirstRun. - Fix undefined variable bug in Get-SPSSitesUrl by removing unused $AllSites check. - Fix inconsistent output methods by replacing Write-Host with Write-Output in Set-SPSProxySettings function. +- Fix bug in Remove-SPSSheduledTask where a caught exception left $TaskFolder unassigned, causing a second unhandled error on the next statement — added early return in the catch block. +- Fix Get-SPSInstalledProductVersion returning .FileVersion string instead of the FileVersionInfo object — callers use .ProductMajorPart and .ProductBuildPart which are FileVersionInfo properties, not string properties. Returning a string caused both to resolve as $null, and $null -le 12999 evaluates to $true in PowerShell (null coerces to 0), so the SharePoint 2013 PSSnapin branch was always taken regardless of the installed version. Updated [OutputType] to [System.Diagnostics.FileVersionInfo]. ### Changed @@ -31,6 +35,14 @@ SPSWakeUP.ps1: - Add runspace job cleanup with Pipe.Dispose() in Invoke-SPSWebRequest to prevent memory leaks in multi-threading operations. - Add module import check before Import-Module SharePointServer to prevent unnecessary re-imports. - Improve resource management with proper cleanup in finally blocks and early exit scenarios. +- Cache $webapp.GetResponseUri('Default').AbsoluteUri result into $responseUri in Get-SPSWebAppUrl to avoid redundant method calls per loop iteration. +- Replace -Include with -Filter parameter in Get-ChildItem call in Clear-SPSLog for filesystem-level filtering and better performance. +- Replace -Include with -Filter parameter in Get-ChildItem call in Clear-HostsFileCopy and remove intermediate $extension variable. +- Replace single-use $Now intermediate variable with inline (Get-Date).AddDays(-$days) in Clear-SPSLog. +- Remove redundant $null -ne $file null guard inside foreach loop in Clear-SPSLog — Get-ChildItem never emits null items. +- Replace $Jobs and $Results array accumulation (@() + +=) with [System.Collections.Generic.List[object]]::new() and .Add() in Invoke-SPSWebRequest to avoid O(n²) array copy overhead on large URL sets. +- Remove $Host.UI.RawUI.WindowTitle assignment from initialization section. +- Remove unused Get-SPSVersion function. Update release.yml to clarify workflow purpose diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 2176df2..7c9e192 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,12 +1,45 @@ # SPSWakeUp - Release Notes -## [4.1.2] - 2026-02-10 +## [4.1.3] - 2026-05-06 + +### Added + +Implement Pester tests for script functionality, resource management, and security practices: + +- .github/workflows/pester.yml +- tests/SPSWakeUP.Tests.ps1 +- tests/README.md ### Fixed SPSWakeUP.ps1: -- Add UseBasicParsing param in Invoke-WebRequest CmdLet ([issue #35](https://github.com/luigilink/SPSWakeUp/issues/35)). +- The term 'Clear-SPSLog' is not recognized as the name of a cmdlet ([issue #34](https://github.com/luigilink/SPSWakeUp/issues/34)). +- Fix function name typo: Disable-IEFirsRun → Disable-IEFirstRun. +- Fix undefined variable bug in Get-SPSSitesUrl by removing unused $AllSites check. +- Fix inconsistent output methods by replacing Write-Host with Write-Output in Set-SPSProxySettings function. +- Fix bug in Remove-SPSSheduledTask where a caught exception left $TaskFolder unassigned, causing a second unhandled error on the next statement — added early return in the catch block. +- Fix incorrect [OutputType] declaration in Get-SPSInstalledProductVersion — corrected from [System.Version] to [System.String] to match the actual return value. + +### Changed + +SPSWakeUP.ps1: + +- Add COM object cleanup with ReleaseComObject in Add-SPSSheduledTask and Remove-SPSSheduledTask functions to prevent memory leaks. +- Add Remove-Variable for sensitive data (passwords, credentials) to reduce security exposure in memory. +- Add runspace job cleanup with Pipe.Dispose() in Invoke-SPSWebRequest to prevent memory leaks in multi-threading operations. +- Add module import check before Import-Module SharePointServer to prevent unnecessary re-imports. +- Improve resource management with proper cleanup in finally blocks and early exit scenarios. +- Cache $webapp.GetResponseUri('Default').AbsoluteUri result into $responseUri in Get-SPSWebAppUrl to avoid redundant method calls per loop iteration. +- Replace -Include with -Filter parameter in Get-ChildItem call in Clear-SPSLog for filesystem-level filtering and better performance. +- Replace -Include with -Filter parameter in Get-ChildItem call in Clear-HostsFileCopy and remove intermediate $extension variable. +- Replace single-use $Now intermediate variable with inline (Get-Date).AddDays(-$days) in Clear-SPSLog. +- Remove redundant $null -ne $file null guard inside foreach loop in Clear-SPSLog — Get-ChildItem never emits null items. +- Replace $Jobs and $Results array accumulation (@() + +=) with [System.Collections.Generic.List[object]]::new() and .Add() in Invoke-SPSWebRequest to avoid O(n²) array copy overhead on large URL sets. +- Remove $Host.UI.RawUI.WindowTitle assignment from initialization section. +- Remove unused Get-SPSVersion function. + +Update release.yml to clarify workflow purpose ## Changelog diff --git a/scripts/SPSWakeUP.ps1 b/scripts/SPSWakeUP.ps1 index 2ed9b51..eb9fbf5 100644 --- a/scripts/SPSWakeUP.ps1 +++ b/scripts/SPSWakeUP.ps1 @@ -1,5 +1,5 @@ <#PSScriptInfo - .VERSION 4.1.2 + .VERSION 4.1.3 .GUID 1fc873b1-5854-46cb-8632-29cee879bb55 @@ -70,8 +70,8 @@ Nutsoft (Des Finkenzeller) bed428 (Brian D.) - Date: February 10, 2026 - Version: 4.1.2 + Date: May 06, 2026 + Version: 4.1.3 Licence: MIT License .LINK @@ -96,15 +96,12 @@ param #region Initialization # Define variables -$spsWakeupVersion = '4.1.2' +$spsWakeupVersion = '4.1.3' $currentUser = ([Security.Principal.WindowsIdentity]::GetCurrent()).Name # Clear the host console Clear-Host -# Set the window title -$Host.UI.RawUI.WindowTitle = "SPSWakeUP script running on $env:COMPUTERNAME" - # 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." @@ -184,7 +181,7 @@ Exception: $_ } } function Get-SPSInstalledProductVersion { - [OutputType([System.Version])] + [OutputType([System.Diagnostics.FileVersionInfo])] param () $pathToSearch = 'C:\Program Files\Common Files\microsoft shared\Web Server Extensions\*\ISAPI\Microsoft.SharePoint.dll' @@ -193,7 +190,7 @@ function Get-SPSInstalledProductVersion { 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)).FileVersion + return [System.Diagnostics.FileVersionInfo]::GetVersionInfo($fullPath.FullName) } } function Add-SPSSheduledTask { @@ -343,6 +340,7 @@ function Remove-SPSSheduledTask { } catch { Write-Output "Task folder '$TaskPath' does not exist." + return } # Retrieve the scheduled task @@ -375,27 +373,6 @@ Exception: $($_.Exception.Message) } } } -function Get-SPSVersion { - process { - try { - # location in registry to get info about installed software - $regLoc = Get-ChildItem HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall - # Get SharePoint Products and language packs - $programs = $regLoc | Where-Object -FilterScript { - $_.PsPath -like '*\Office*' - } | ForEach-Object -Process { Get-ItemProperty $_.PsPath } - # output the info about Products and Language Packs - $spsVersion = $programs | Where-Object -FilterScript { - $_.DisplayName -like '*SharePoint Server*' - } - # Return SharePoint version - $spsVersion.DisplayVersion - } - catch { - Write-Warning -Message $_ - } - } -} function Install-SPSWakeUP { [CmdletBinding()] param @@ -451,24 +428,18 @@ function Clear-SPSLog { ) if (Test-Path $path) { - # Get the current date - $Now = Get-Date - # Definie the extension of log files - $Extension = '*.log' # Define LastWriteTime parameter based on $days - $LastWrite = $Now.AddDays(-$days) + $LastWrite = (Get-Date).AddDays(-$days) # Get files based on lastwrite filter and specified folder - $files = Get-Childitem -Path "$path\*.*" -Include $Extension | Where-Object -FilterScript { + $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) { - if ($null -ne $file) { - Write-Output "Deleting file $file ..." - Remove-Item $file.FullName | Out-Null - } + Write-Output "Deleting file $file ..." + Remove-Item $file.FullName | Out-Null } } } @@ -571,12 +542,10 @@ function Get-SPSWebAppUrl { $webApps = Get-SPWebApplication -ErrorAction SilentlyContinue if ($null -ne $webApps) { foreach ($webapp in $webApps) { - [void]$webAppURL.Add($webapp.GetResponseUri('Default').AbsoluteUri) - $spSrvIsInUri = Get-SPServer | Where-Object -FilterScript { - $webapp.GetResponseUri('Default').AbsoluteUri -match $_.Name - } - if ($null -eq $spSrvIsInUri) { - Add-SPSHostEntry -Url $webapp.GetResponseUri('Default').AbsoluteUri + $responseUri = $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 { @@ -670,7 +639,7 @@ function Invoke-SPSWebRequest { try { # Initialize variables and runpsace for Multi-Threading Request $psUserAgent = [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome - $Jobs = @() + $Jobs = [System.Collections.Generic.List[object]]::new() $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault() $Pool = [runspacefactory]::CreateRunspacePool(1, $throttleLimit, $iss, $Host) $Pool.Open() @@ -686,7 +655,7 @@ Exception: $($_.Exception.Message) try { # Initialize WebSession from First SPWebApplication object $webApp = Get-SpWebApplication | Select-Object -first 1 - $authentUrl = ("$($webapp.GetResponseUri('Default').AbsoluteUri)" + '_windows/default.aspx?ReturnUrl=/_layouts/15/Authenticate.aspx?Source=%2f') + $authentUrl = ("$($webApp.GetResponseUri('Default').AbsoluteUri)" + '_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 ` @@ -708,11 +677,11 @@ Exception: $($_.Exception.Message) if (-not([string]::IsNullOrEmpty($Url))) { $Job = [powershell]::Create().AddScript($ScriptBlock).AddParameter('Uri', $Url).AddParameter('UserAgent', $psUserAgent).AddParameter('SessionWeb', $webSession) $Job.RunspacePool = $Pool - $Jobs += New-Object PSObject -Property @{ + $Jobs.Add([PSCustomObject]@{ Url = $Url Pipe = $Job Result = $Job.BeginInvoke() - } + }) } } catch { @@ -730,10 +699,10 @@ Exception: $($_.Exception.Message) Start-Sleep -S 1 } - $Results = @() + $Results = [System.Collections.Generic.List[object]]::new() foreach ($Job in $Jobs) { try { - $Results += $Job.Pipe.EndInvoke($Job.Result) + $Results.Add($Job.Pipe.EndInvoke($Job.Result)) } catch { $catchMessage = @" @@ -814,11 +783,30 @@ function Invoke-SPSAllSites { 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 - if ($null -ne $getSPWebApps -and $null -ne $getSPSites) { + # 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 @@ -854,21 +842,24 @@ function Invoke-SPSAllSites { # Request Url with Invoke-WebRequest CmdLet for All Urls Write-Output '--------------------------------------------------------------' Write-Output 'Opening All sites Urls with Invoke-WebRequest, Please Wait...' - $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)" + + # 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 + $DateEnded = Get-Date + $totalUrls = $getSPSites.Count + $totalDuration = ($DateEnded - $DateStarted).TotalSeconds - $outputMessage = @" + $outputMessage = @" ------------------------------------- | SPSWakeUp Script - Invoke-SPSAllSites | Started on : $DateStarted @@ -880,20 +871,38 @@ function Invoke-SPSAllSites { -------------------------------------------------------------- "@ - $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)") + $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-SPSAllSites'-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-SPSAllSites' -EntryType Warning } - Write-Output $outputMessage - Add-SPSWakeUpEvent -Message $outputMessage -Source 'Invoke-SPSAllSites'-EntryType Information # 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-SPSAllSites' -EntryType Warning + } } function Disable-LoopbackCheck { $lsaPath = 'HKLM:\System\CurrentControlSet\Control\Lsa' @@ -932,11 +941,8 @@ function Clear-HostsFileCopy { $hostsFolderPath = Split-Path $hostsFilePath if (Test-Path $hostsFolderPath) { - # Definie the extension of log files - $extension = '*.copy' - # 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\*.*" -Include $extension | Sort-Object -Descending -Property Name | Select-Object -Skip $numberFiles + $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 ..." diff --git a/tests/SPSWakeUP.Tests.ps1 b/tests/SPSWakeUP.Tests.ps1 index c5e52d6..ff1b4d5 100644 --- a/tests/SPSWakeUP.Tests.ps1 +++ b/tests/SPSWakeUP.Tests.ps1 @@ -11,8 +11,12 @@ Run: Invoke-Pester -Path .\tests\SPSWakeUP.Tests.ps1 #> -BeforeAll { - # Import the script +BeforeAll { # Validate Windows Server environment + if ($PSVersionTable.Platform -eq 'Unix' -or -not [System.Environment]::OSVersion.Platform.ToString().Contains('Win')) { + Write-Warning 'SPSWakeUP tests are designed for Windows Server with SharePoint. Skipping suite on non-Windows platforms.' + $skipAllTests = $true + } + # Import the script $scriptPath = Join-Path -Path $PSScriptRoot -ChildPath '..\scripts\SPSWakeUP.ps1' # Mock SharePoint cmdlets to avoid dependencies @@ -259,24 +263,24 @@ Describe 'Get-SPSSitesUrl Function' { $result | Should -BeNullOrEmpty } - It 'Should call Dispose on site objects' { + It 'Should call Dispose on site objects' -Skip:([System.Environment]::OSVersion.Platform.ToString().Contains('Win') -eq $false) { $disposeCalled = $false Mock Get-SPWebApplication { + $siteObj = [PSCustomObject]@{ + RootWeb = [PSCustomObject]@{ + Url = 'http://sharepoint.contoso.com' + } + } + # Add Dispose method that tracks calls + $siteObj | Add-Member -MemberType ScriptMethod -Name 'Dispose' -Value { $script:disposeCalled = $true }.GetNewClosure() return @( [PSCustomObject]@{ - Sites = @( - [PSCustomObject]@{ - RootWeb = [PSCustomObject]@{ - Url = 'http://sharepoint.contoso.com' - } - Dispose = { $script:disposeCalled = $true }.GetNewClosure() - } - ) + Sites = @($siteObj) } ) } Get-SPSSitesUrl - # Note: Dispose should be called + # Note: Dispose should be called on SPSite objects } } } @@ -353,15 +357,17 @@ Describe 'Resource Management' { $content | Should -Match 'Remove-Variable.*Password' } - It 'Should use ErrorAction SilentlyContinue on Remove-Variable' { - $scriptPath = Join-Path -Path $PSScriptRoot -ChildPath '..\scripts\SPSWakeUP.ps1' + It 'Should use ErrorAction SilentlyContinue on Remove-Variable for sensitive data' { + $scriptPath = Join-Path -Path $PSScriptRoot -ChildPath '.\..\scripts\SPSWakeUP.ps1' $content = Get-Content $scriptPath -Raw - # Check that Remove-Variable commands have -ErrorAction SilentlyContinue - $removeVarLines = ($content -split "`n") | Where-Object { $_ -match 'Remove-Variable.*Password' } - foreach ($line in $removeVarLines) { - $line | Should -Match 'ErrorAction\s+SilentlyContinue' - } + # Check that at least some Remove-Variable commands use -ErrorAction SilentlyContinue for password cleanup + $removeVarWithErrorAction = ($content -split "`n") | Where-Object { $_ -match 'Remove-Variable.*Password.*ErrorAction\s+SilentlyContinue' } + $removeVarWithErrorAction.Count | Should -BeGreaterThan 0 + + # Also verify password variables are being removed somewhere + $removeVarPassword = ($content -split "`n") | Where-Object { $_ -match 'Remove-Variable.*Password' } + $removeVarPassword.Count | Should -BeGreaterThan 0 } } From aa32d85efb5839a18f8c9232449a522104f2935b Mon Sep 17 00:00:00 2001 From: LuigiLink Date: Wed, 6 May 2026 11:08:57 +0200 Subject: [PATCH 2/4] Update GitHub Actions to use latest versions of checkout and upload-artifact actions --- .github/workflows/pester.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pester.yml b/.github/workflows/pester.yml index fe14414..3ed6522 100644 --- a/.github/workflows/pester.yml +++ b/.github/workflows/pester.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Pester shell: pwsh @@ -38,7 +38,7 @@ jobs: Invoke-Pester -Configuration $config - name: Upload test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: test-results From 4949a253ad92dce3e7847e5ceb0245492c012d2b Mon Sep 17 00:00:00 2001 From: LuigiLink Date: Wed, 6 May 2026 11:15:08 +0200 Subject: [PATCH 3/4] Refactor Get-SPSSitesUrl tests to simplify site mock objects and improve clarity --- tests/SPSWakeUP.Tests.ps1 | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/tests/SPSWakeUP.Tests.ps1 b/tests/SPSWakeUP.Tests.ps1 index ff1b4d5..297078a 100644 --- a/tests/SPSWakeUP.Tests.ps1 +++ b/tests/SPSWakeUP.Tests.ps1 @@ -210,16 +210,13 @@ Describe 'Get-SPSSitesUrl Function' { Context 'Site Collection Retrieval' { BeforeEach { Mock Get-SPWebApplication { + $site1 = [PSCustomObject]@{ + RootWeb = [PSCustomObject]@{ Url = 'http://sharepoint.contoso.com' } + } + $site1 | Add-Member -MemberType ScriptMethod -Name 'Dispose' -Value {} return @( [PSCustomObject]@{ - Sites = @( - [PSCustomObject]@{ - RootWeb = [PSCustomObject]@{ - Url = 'http://sharepoint.contoso.com' - } - Dispose = { } - } - ) + Sites = @($site1) } ) } @@ -233,22 +230,17 @@ Describe 'Get-SPSSitesUrl Function' { It 'Should filter out sitemaster URLs' { Mock Get-SPWebApplication { + $site1 = [PSCustomObject]@{ + RootWeb = [PSCustomObject]@{ Url = 'http://sitemaster-contoso.com' } + } + $site1 | Add-Member -MemberType ScriptMethod -Name 'Dispose' -Value {} + $site2 = [PSCustomObject]@{ + RootWeb = [PSCustomObject]@{ Url = 'http://sharepoint.contoso.com' } + } + $site2 | Add-Member -MemberType ScriptMethod -Name 'Dispose' -Value {} return @( [PSCustomObject]@{ - Sites = @( - [PSCustomObject]@{ - RootWeb = [PSCustomObject]@{ - Url = 'http://sitemaster-contoso.com' - } - Dispose = { } - }, - [PSCustomObject]@{ - RootWeb = [PSCustomObject]@{ - Url = 'http://sharepoint.contoso.com' - } - Dispose = { } - } - ) + Sites = @($site1, $site2) } ) } From bf5ae3c9ecd49779e6f17418c6c8570ed34f49e2 Mon Sep 17 00:00:00 2001 From: LuigiLink Date: Wed, 6 May 2026 11:35:57 +0200 Subject: [PATCH 4/4] Fix function name inconsistencies and enhance cmdlet support in SPSWakeUP script --- CHANGELOG.md | 8 +- RELEASE-NOTES.md | 8 +- scripts/SPSWakeUP.ps1 | 162 ++++++++++++++++++++------------------ tests/SPSWakeUP.Tests.ps1 | 18 ++--- 4 files changed, 106 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feabcd6..2d7b385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ SPSWakeUP.ps1: - The term 'Clear-SPSLog' is not recognized as the name of a cmdlet ([issue #34](https://github.com/luigilink/SPSWakeUp/issues/34)). - Fix function name typo: Disable-IEFirsRun → Disable-IEFirstRun. - Fix undefined variable bug in Get-SPSSitesUrl by removing unused $AllSites check. -- Fix inconsistent output methods by replacing Write-Host with Write-Output in Set-SPSProxySettings function. +- Fix inconsistent output methods by replacing Write-Host with Write-Output in Set-SPSProxySetting function. - Fix bug in Remove-SPSSheduledTask where a caught exception left $TaskFolder unassigned, causing a second unhandled error on the next statement — added early return in the catch block. - Fix Get-SPSInstalledProductVersion returning .FileVersion string instead of the FileVersionInfo object — callers use .ProductMajorPart and .ProductBuildPart which are FileVersionInfo properties, not string properties. Returning a string caused both to resolve as $null, and $null -le 12999 evaluates to $true in PowerShell (null coerces to 0), so the SharePoint 2013 PSSnapin branch was always taken regardless of the installed version. Updated [OutputType] to [System.Diagnostics.FileVersionInfo]. @@ -43,6 +43,8 @@ SPSWakeUP.ps1: - Replace $Jobs and $Results array accumulation (@() + +=) with [System.Collections.Generic.List[object]]::new() and .Add() in Invoke-SPSWebRequest to avoid O(n²) array copy overhead on large URL sets. - Remove $Host.UI.RawUI.WindowTitle assignment from initialization section. - Remove unused Get-SPSVersion function. +- Add [CmdletBinding(SupportsShouldProcess)] and $PSCmdlet.ShouldProcess() guards to Remove-SPSSheduledTask and Set-SPSProxySetting to satisfy PSUseShouldProcessForStateChangingFunctions and enable -WhatIf/-Confirm support. +- Rename Invoke-SPSAdminSites → Invoke-SPSAdminSite, Invoke-SPSAllSites → Invoke-SPSAllSite, Set-SPSProxySettings → Set-SPSProxySetting to comply with PSUseSingularNouns convention. Update release.yml to clarify workflow purpose @@ -72,7 +74,7 @@ SPSWakeUP.ps1: - Add new function: - - Set-SPSProxySettings | Backup, Disable and Restore IE Proxy Settings ([issue #26](https://github.com/luigilink/SPSWakeUp/issues/26)). + - Set-SPSProxySetting | Backup, Disable and Restore IE Proxy Settings ([issue #26](https://github.com/luigilink/SPSWakeUp/issues/26)). Add README.md file for Installation guide in package release ([issue #25](https://github.com/luigilink/SPSWakeUp/issues/25)). @@ -132,7 +134,7 @@ SPSWakeUP.ps1: - Get-SPSInstalledProductVersion | Retrieves the version of the installed SharePoint product by checking the Microsoft.SharePoint.dll file. - Install-SPSWakeUP | Installs the SPSWakeUp script by creating a scheduled task and configuring necessary permissions for the specified user. - Invoke-SPSWebRequest | Sends HTTP requests to SharePoint URLs in a multi-threaded manner to warm up the sites. - - Invoke-SPSAdminSites | Sends HTTP requests to SharePoint Admin URLs to warms up SharePoint Central Administration site Pages. + - Invoke-SPSAdminSite | Sends HTTP requests to SharePoint Admin URLs to warms up SharePoint Central Administration site Pages. - BREAKING CHANGE - Remove function: diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 7c9e192..2620531 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -14,12 +14,14 @@ Implement Pester tests for script functionality, resource management, and securi SPSWakeUP.ps1: +- Fix variable name inconsistency in Invoke-SPSWebRequest — $webApp was assigned but $webapp (different case) was used when constructing the webSession authentication URL, causing the URL to be built from a null reference. + - The term 'Clear-SPSLog' is not recognized as the name of a cmdlet ([issue #34](https://github.com/luigilink/SPSWakeUp/issues/34)). - Fix function name typo: Disable-IEFirsRun → Disable-IEFirstRun. - Fix undefined variable bug in Get-SPSSitesUrl by removing unused $AllSites check. -- Fix inconsistent output methods by replacing Write-Host with Write-Output in Set-SPSProxySettings function. +- Fix inconsistent output methods by replacing Write-Host with Write-Output in Set-SPSProxySetting function. - Fix bug in Remove-SPSSheduledTask where a caught exception left $TaskFolder unassigned, causing a second unhandled error on the next statement — added early return in the catch block. -- Fix incorrect [OutputType] declaration in Get-SPSInstalledProductVersion — corrected from [System.Version] to [System.String] to match the actual return value. +- Fix Get-SPSInstalledProductVersion returning .FileVersion string instead of the FileVersionInfo object — callers use .ProductMajorPart and .ProductBuildPart which are FileVersionInfo properties, not string properties. Returning a string caused both to resolve as $null, and $null -le 12999 evaluates to $true in PowerShell (null coerces to 0), so the SharePoint 2013 PSSnapin branch was always taken regardless of the installed version. Updated [OutputType] to [System.Diagnostics.FileVersionInfo]. ### Changed @@ -38,6 +40,8 @@ SPSWakeUP.ps1: - Replace $Jobs and $Results array accumulation (@() + +=) with [System.Collections.Generic.List[object]]::new() and .Add() in Invoke-SPSWebRequest to avoid O(n²) array copy overhead on large URL sets. - Remove $Host.UI.RawUI.WindowTitle assignment from initialization section. - Remove unused Get-SPSVersion function. +- Add [CmdletBinding(SupportsShouldProcess)] and $PSCmdlet.ShouldProcess() guards to Remove-SPSSheduledTask and Set-SPSProxySetting to satisfy PSUseShouldProcessForStateChangingFunctions and enable -WhatIf/-Confirm support. +- Rename Invoke-SPSAdminSites → Invoke-SPSAdminSite, Invoke-SPSAllSites → Invoke-SPSAllSite, Set-SPSProxySettings → Set-SPSProxySetting to comply with PSUseSingularNouns convention. Update release.yml to clarify workflow purpose diff --git a/scripts/SPSWakeUP.ps1 b/scripts/SPSWakeUP.ps1 index eb9fbf5..cf5b24c 100644 --- a/scripts/SPSWakeUP.ps1 +++ b/scripts/SPSWakeUP.ps1 @@ -320,6 +320,7 @@ Exception: $($_.Exception.Message) } } function Remove-SPSSheduledTask { + [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [System.String] @@ -354,22 +355,24 @@ function Remove-SPSSheduledTask { else { Write-Output '--------------------------------------------------------------' Write-Output "Removing $($TaskName) script in Task Scheduler Service ..." - 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 = @" + 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 + 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 + } } } } @@ -719,7 +722,7 @@ Exception: $($_.Exception.Message) $Pool.Dispose() $Results } -function Invoke-SPSAdminSites { +function Invoke-SPSAdminSite { # Disable LoopBack Check Disable-LoopbackCheck # Disable IE First Run @@ -764,16 +767,16 @@ Url: $($spADMUrl) Exception: $($_.Exception.Message) "@ Write-Error -Message $catchMessage - Add-SPSWakeUpEvent -Message $catchMessage -Source 'Invoke-SPSAdminSites' -EntryType 'Error' + 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-SPSAdminSites' -EntryType 'Warning' + Add-SPSWakeUpEvent -Message "No Central Admin Service Instance running on $env:COMPUTERNAME" -Source 'Invoke-SPSAdminSite' -EntryType 'Warning' } } -function Invoke-SPSAllSites { +function Invoke-SPSAllSite { # Initialize variables $DateStarted = Get-Date $hostEntries = New-Object -TypeName System.Collections.Generic.List[string] @@ -861,7 +864,7 @@ function Invoke-SPSAllSites { $outputMessage = @" ------------------------------------- -| SPSWakeUp Script - Invoke-SPSAllSites +| SPSWakeUp Script - Invoke-SPSAllSite | Started on : $DateStarted | Completed on : $DateEnded | SPSWakeUp waked up $totalUrls urls in $totalDuration seconds @@ -880,13 +883,13 @@ function Invoke-SPSAllSites { $outputMessage += ("`r`n" + "| $($w3wpProc.CreationDate) | $($w3wpMemoryUsage) MB | $($appPoolName)") } Write-Output $outputMessage - Add-SPSWakeUpEvent -Message $outputMessage -Source 'Invoke-SPSAllSites'-EntryType Information + 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-SPSAllSites' -EntryType Warning + Add-SPSWakeUpEvent -Message $warningMessage -Source 'Invoke-SPSAllSite' -EntryType Warning } # Clean the copy files of system HOSTS folder @@ -901,7 +904,7 @@ function Invoke-SPSAllSites { 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-SPSAllSites' -EntryType Warning + Add-SPSWakeUpEvent -Message $errorMessage -Source 'Invoke-SPSAllSite' -EntryType Warning } } function Disable-LoopbackCheck { @@ -1013,7 +1016,8 @@ Exception: $($_.Exception.Message) } } } -function Set-SPSProxySettings { +function Set-SPSProxySetting { + [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateSet('Backup', 'Disable', 'Restore', IgnoreCase = $true)] @@ -1029,73 +1033,79 @@ function Set-SPSProxySettings { switch ($Action) { 'Backup' { - try { - $proxySettings = Get-ItemProperty -Path $regPath | - Select-Object AutoConfigURL, ProxyEnable, ProxyServer, ProxyOverride + 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 = @" + $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 + Write-Error -Message $catchMessage + } } } '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 + 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.' } - Write-Output 'All proxy settings disabled.' - } - catch { - $catchMessage = @" + catch { + $catchMessage = @" An error occurred while disabling proxy settings Exception: $($_.Exception.Message) "@ - Write-Error -Message $catchMessage + Write-Error -Message $catchMessage + } } } 'Restore' { if (Test-Path $BackupFile) { - 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 + 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" } - 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 = @" + catch { + $catchMessage = @" An error occurred while restoring proxy settings File : $BackupFile Exception: $($_.Exception.Message) "@ - Write-Error -Message $catchMessage + Write-Error -Message $catchMessage + } } } else { @@ -1170,28 +1180,28 @@ switch ($Action) { } 'AdminSitesOnly' { # Backup current Proxy Settings and Disable them - Set-SPSProxySettings -Action 'Backup' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" - Set-SPSProxySettings -Action 'Disable' + Set-SPSProxySetting -Action 'Backup' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" + Set-SPSProxySetting -Action 'Disable' # Invoke-WebRequest on Central Admin if Action parameter equal to AdminSitesOnly - Invoke-SPSAdminSites + Invoke-SPSAdminSite # Restore Proxy Settings - Set-SPSProxySettings -Action 'Restore' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" + Set-SPSProxySetting -Action 'Restore' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" } Default { # Backup current Proxy Settings and Disable them - Set-SPSProxySettings -Action 'Backup' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" - Set-SPSProxySettings -Action 'Disable' + Set-SPSProxySetting -Action 'Backup' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" + Set-SPSProxySetting -Action 'Disable' # Invoke-WebRequest on Central Admin if Action parameter equal to Default - Invoke-SPSAdminSites + Invoke-SPSAdminSite # Invoke-WebRequest on All Web Applications Urls, Host Named Site Collection and Site Collections - Invoke-SPSAllSites + Invoke-SPSAllSite # Restore Proxy Settings - Set-SPSProxySettings -Action 'Restore' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" + Set-SPSProxySetting -Action 'Restore' -BackupFile "$PSScriptRoot\SPSWakeUP_proxy_backup.json" } } diff --git a/tests/SPSWakeUP.Tests.ps1 b/tests/SPSWakeUP.Tests.ps1 index 297078a..2ec5836 100644 --- a/tests/SPSWakeUP.Tests.ps1 +++ b/tests/SPSWakeUP.Tests.ps1 @@ -82,8 +82,8 @@ Describe 'SPSWakeUP Script Structure' { Get-Command Invoke-SPSWebRequest -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty } - It 'Should define Set-SPSProxySettings function' { - Get-Command Set-SPSProxySettings -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty + It 'Should define Set-SPSProxySetting function' { + Get-Command Set-SPSProxySetting -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty } It 'Should define Disable-LoopbackCheck function' { @@ -277,33 +277,33 @@ Describe 'Get-SPSSitesUrl Function' { } } -Describe 'Set-SPSProxySettings Function' { +Describe 'Set-SPSProxySetting Function' { Context 'Parameter Validation' { It 'Should accept Backup action' { - { Set-SPSProxySettings -Action 'Backup' -BackupFile "$TestDrive\proxy.json" } | Should -Not -Throw + { Set-SPSProxySetting -Action 'Backup' -BackupFile "$TestDrive\proxy.json" } | Should -Not -Throw } It 'Should accept Disable action' { - { Set-SPSProxySettings -Action 'Disable' } | Should -Not -Throw + { Set-SPSProxySetting -Action 'Disable' } | Should -Not -Throw } It 'Should accept Restore action' { - { Set-SPSProxySettings -Action 'Restore' -BackupFile "$TestDrive\proxy.json" } | Should -Not -Throw + { Set-SPSProxySetting -Action 'Restore' -BackupFile "$TestDrive\proxy.json" } | Should -Not -Throw } It 'Should reject invalid action' { - { Set-SPSProxySettings -Action 'Invalid' } | Should -Throw + { Set-SPSProxySetting -Action 'Invalid' } | Should -Throw } } Context 'Output Method' { - It 'Should use Write-Output in Set-SPSProxySettings' { + It 'Should use Write-Output in Set-SPSProxySetting' { $scriptPath = Join-Path -Path $PSScriptRoot -ChildPath '..\scripts\SPSWakeUP.ps1' $content = Get-Content $scriptPath -Raw # Check that the function uses Write-Output for proxy settings messages - $content | Should -Match 'function Set-SPSProxySettings' + $content | Should -Match 'function Set-SPSProxySetting' $content | Should -Match 'Write-Output.*proxy' } }