From 7eb14a67cc9f6fad8612ce690d1836c8a410bd0d Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:14:05 -0400 Subject: [PATCH 1/4] fix: address all code review findings - Remove-OldFiles: add Mandatory/ValidateScript on -Path, ValidateRange on -Days, use LiteralPath, filter by LastWriteTime (not CreationTime), enumerate files with ShouldProcess before Remove-Item - Clear-CurrentUserTemp: fix Out-Null pipe that nulled the collection, wrap directory removal in ShouldProcess, add break guard for WhatIf - Clear-OldExchangeLog: fix wrong function name Clear-OldIisLogFiles -> Clear-OldIISLog, pass -WhatIf:False, use Remove-Item with LiteralPath instead of .Delete(), add ValidateRange on -Days - Clear-OldIISLog: add SupportsShouldProcess, wrap each Remove-OldFiles call in ShouldProcess so -WhatIf/-Confirm propagate correctly - Get-StaleUserProfile: fix missing \$ prefix on !(StaleUserProfiles) - Start-Cleaning: fix comment-based help -NoLogo -> -Dedication - TheCleaners.psd1: remove WebAdministration from ExternalModuleDependencies (the module is optional at runtime) - Activate skipped tests: move ExportedFunctions and TheCleaners-Module tests from SkipUnit/ to Unit/, add CleanupBehavior regression suite - Update ExportedFunctions tests to Pester v5 -ForEach pattern - CI workflow: switch to windows-latest, add permissions: contents: read, remove verbose debug step and auto-commit step, update generated docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Build Module.yml | 16 +- docs/Clear-OldIISLog.md | 160 +++++++++++------- docs/Start-Cleaning.md | 114 ++++++------- .../SkipUnit/ExportedFunctions.Tests.ps1 | 66 -------- src/Tests/Unit/CleanupBehavior.Tests.ps1 | 102 +++++++++++ src/Tests/Unit/ExportedFunctions.Tests.ps1 | 42 +++++ .../TheCleaners-Module.Tests.ps1 | 20 ++- src/TheCleaners.build.ps1 | 10 +- src/TheCleaners/Private/Remove-OldFiles.ps1 | 19 ++- .../Public/Clear-CurrentUserTemp.ps1 | 14 +- .../Public/Clear-OldExchangeLog.ps1 | 13 +- src/TheCleaners/Public/Clear-OldIISLog.ps1 | 14 +- .../Public/Get-StaleUserProfile.ps1 | 2 +- src/TheCleaners/Public/Start-Cleaning.ps1 | 4 +- src/TheCleaners/TheCleaners.psd1 | 4 +- 15 files changed, 374 insertions(+), 226 deletions(-) delete mode 100644 src/Tests/SkipUnit/ExportedFunctions.Tests.ps1 create mode 100644 src/Tests/Unit/CleanupBehavior.Tests.ps1 create mode 100644 src/Tests/Unit/ExportedFunctions.Tests.ps1 rename src/Tests/{SkipUnit => Unit}/TheCleaners-Module.Tests.ps1 (92%) diff --git a/.github/workflows/Build Module.yml b/.github/workflows/Build Module.yml index f502168..b2b59ee 100644 --- a/.github/workflows/Build Module.yml +++ b/.github/workflows/Build Module.yml @@ -19,10 +19,13 @@ on: workflow_dispatch: +permissions: + contents: read + jobs: test: name: ๐Ÿงช Run Tests - runs-on: ubuntu-latest + runs-on: windows-latest strategy: fail-fast: false @@ -31,11 +34,6 @@ jobs: - name: โœ… Checkout Repository uses: actions/checkout@v4 - # Uncomment below to explore what modules/variables/env variables are available in the build image - - name: ๐Ÿ“ฆ Modules and Variables Display - shell: pwsh - run: Get-Module -ListAvailable; (Get-Variable).GetEnumerator() | Sort-Object Name | Out-String; (Get-ChildItem env:*).GetEnumerator() | Sort-Object Name | Out-String - - name: ๐Ÿฅพ Bootstrap shell: pwsh run: ./actions_bootstrap.ps1 @@ -57,9 +55,3 @@ jobs: name: zip-archive path: .\src\Archive if-no-files-found: warn - # git-auto-commit-action only runs on Linux-based platforms. - - - name: ๐Ÿ’พ Commit Changes - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: 'Commit Build' diff --git a/docs/Clear-OldIISLog.md b/docs/Clear-OldIISLog.md index 36696ef..df818f2 100644 --- a/docs/Clear-OldIISLog.md +++ b/docs/Clear-OldIISLog.md @@ -1,65 +1,97 @@ ---- -external help file: TheCleaners-help.xml -Module Name: TheCleaners -online version: -schema: 2.0.0 ---- - -# Clear-OldIISLog - -## SYNOPSIS -A script to clean out old IIS log files. - -## SYNTAX - -``` -Clear-OldIISLog [[-Days] ] [] -``` - -## DESCRIPTION -This script will clean out IIS log files older than x days. - -## EXAMPLES - -### EXAMPLE 1 -``` -Clear-OldIISLogFile -Days 60 -``` - -Removes all IIS log files that are older than 60 days. - -## PARAMETERS - -### -Days -The number of days to keep log files. -The default is 30 days. - -```yaml -Type: Int16 -Parameter Sets: (All) -Aliases: - -Required: False -Position: 1 -Default value: 60 -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. -For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES -If the WebAdministration module is available, it will use that to check the specific log file locations for -each web site. -Otherwise, it checks the assumed default log folder location and the registry for the IIS -log file location. - -To Do: Add a summary of which blocks were run and possibly a count of log files removed. - +--- +external help file: TheCleaners-help.xml +Module Name: TheCleaners +online version: +schema: 2.0.0 +--- + +# Clear-OldIISLog + +## SYNOPSIS +A script to clean out old IIS log files. + +## SYNTAX + +``` +Clear-OldIISLog [[-Days] ] [-WhatIf] [-Confirm] + [] +``` + +## DESCRIPTION +This script will clean out IIS log files older than x days. + +## EXAMPLES + +### EXAMPLE 1 +``` +Clear-OldIISLogFile -Days 60 +``` + +Removes all IIS log files that are older than 60 days. + +## PARAMETERS + +### -Days +The number of days to keep log files. +The default is 30 days. + +```yaml +Type: Int16 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: 60 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. +For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES +If the WebAdministration module is available, it will use that to check the specific log file locations for +each web site. +Otherwise, it checks the assumed default log folder location and the registry for the IIS +log file location. + +To Do: Add a summary of which blocks were run and possibly a count of log files removed. + ## RELATED LINKS diff --git a/docs/Start-Cleaning.md b/docs/Start-Cleaning.md index 03d6b57..90d676a 100644 --- a/docs/Start-Cleaning.md +++ b/docs/Start-Cleaning.md @@ -1,58 +1,58 @@ ---- -external help file: TheCleaners-help.xml -Module Name: TheCleaners -online version: -schema: 2.0.0 ---- - -# Start-Cleaning - -## SYNOPSIS -Show the commands you can give The Cleaners. - -## SYNTAX - -``` -Start-Cleaning [-Dedication] [] -``` - -## DESCRIPTION -Get started with a menu of services The Cleaners can offer. - -## EXAMPLES - -### EXAMPLE 1 -``` -Start-Cleaning -``` - -View the menu of services that TheCleaners provide. - -## PARAMETERS - -### -Dedication -Show dedication - -```yaml -Type: SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: False -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. -For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -## OUTPUTS - -## NOTES - +--- +external help file: TheCleaners-help.xml +Module Name: TheCleaners +online version: +schema: 2.0.0 +--- + +# Start-Cleaning + +## SYNOPSIS +Show the commands you can give The Cleaners. + +## SYNTAX + +``` +Start-Cleaning [-Dedication] [] +``` + +## DESCRIPTION +Get started with a menu of services The Cleaners can offer. + +## EXAMPLES + +### EXAMPLE 1 +``` +Start-Cleaning +``` + +View the menu of services that TheCleaners provide. + +## PARAMETERS + +### -Dedication +Show a short dedication before the command menu. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. +For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES + ## RELATED LINKS diff --git a/src/Tests/SkipUnit/ExportedFunctions.Tests.ps1 b/src/Tests/SkipUnit/ExportedFunctions.Tests.ps1 deleted file mode 100644 index e1ad8a0..0000000 --- a/src/Tests/SkipUnit/ExportedFunctions.Tests.ps1 +++ /dev/null @@ -1,66 +0,0 @@ -#------------------------------------------------------------------------- -Set-Location -Path $PSScriptRoot -#------------------------------------------------------------------------- -$ModuleName = 'TheCleaners' -$PathToManifest = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psd1") -#------------------------------------------------------------------------- -if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') { - #if the module is already in memory, remove it - Remove-Module -Name $ModuleName -Force -} -Import-Module $PathToManifest -Force -#------------------------------------------------------------------------- - -BeforeAll { - Set-Location -Path $PSScriptRoot - $ModuleName = 'TheCleaners' - $PathToManifest = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psd1") - $manifestContent = Test-ModuleManifest -Path $PathToManifest - $moduleExported = Get-Command -Module $ModuleName | Select-Object -ExpandProperty Name - $manifestExported = ($manifestContent.ExportedFunctions).Keys -} -Describe $ModuleName { - - Context 'Exported Commands' -Fixture { - - Context 'Number of commands' -Fixture { - It -Name 'Exports the same number of public functions as what is listed in the Module Manifest' -Test { - $manifestExported.Count | Should -BeExactly $moduleExported.Count - } - } - - Context 'Explicitly exported commands' -ForEach $moduleExported { - foreach ($command in $moduleExported) { - BeforeAll { - $command = $_ - } - It -Name "Includes the $command in the Module Manifest ExportedFunctions" -Test { - $manifestExported -contains $command | Should -BeTrue - } - } - } - } - - Context 'Command Help' -ForEach $moduleExported { - foreach ($command in $moduleExported) { - BeforeAll { - $help = Get-Help -Name $_ -Full - } - Context $command -Fixture { - $help = Get-Help -Name $command -Full - - It -Name 'Includes a Synopsis' -Test { - $help.Synopsis | Should -Not -BeNullOrEmpty - } - - It -Name 'Includes a Description' -Test { - $help.description.Text | Should -Not -BeNullOrEmpty - } - - It -Name 'Includes an Example' -Test { - $help.examples.example | Should -Not -BeNullOrEmpty - } - } - } - } -} diff --git a/src/Tests/Unit/CleanupBehavior.Tests.ps1 b/src/Tests/Unit/CleanupBehavior.Tests.ps1 new file mode 100644 index 0000000..594ad24 --- /dev/null +++ b/src/Tests/Unit/CleanupBehavior.Tests.ps1 @@ -0,0 +1,102 @@ +BeforeAll { + $ModuleRoot = (Resolve-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..\..\TheCleaners')).Path + . (Join-Path -Path $ModuleRoot -ChildPath 'Private\Remove-OldFiles.ps1') + . (Join-Path -Path $ModuleRoot -ChildPath 'Public\Clear-CurrentUserTemp.ps1') + . (Join-Path -Path $ModuleRoot -ChildPath 'Public\Clear-OldIISLog.ps1') + . (Join-Path -Path $ModuleRoot -ChildPath 'Public\Clear-OldExchangeLog.ps1') +} + +Describe 'Remove-OldFiles' -Tag Unit { + BeforeEach { + $TestRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([guid]::NewGuid().Guid) + New-Item -Path $TestRoot -ItemType Directory -Force | Out-Null + $OldFile = New-Item -Path (Join-Path -Path $TestRoot -ChildPath 'old.log') -ItemType File + $NewFile = New-Item -Path (Join-Path -Path $TestRoot -ChildPath 'new.log') -ItemType File + $OldFile.LastWriteTime = (Get-Date).AddDays(-31) + $NewFile.LastWriteTime = Get-Date + } + + AfterEach { + Remove-Item -LiteralPath $TestRoot -Recurse -Force -ErrorAction SilentlyContinue + } + + It 'removes files older than the retention window by LastWriteTime' { + Remove-OldFiles -Path $TestRoot -Days 30 -Confirm:$false + + $OldFile.FullName | Should -Not -Exist + $NewFile.FullName | Should -Exist + } + + It 'does not remove matching files when WhatIf is used' { + Remove-OldFiles -Path $TestRoot -Days 30 -WhatIf + + $OldFile.FullName | Should -Exist + $NewFile.FullName | Should -Exist + } +} + +Describe 'Clear-CurrentUserTemp' -Tag Unit { + BeforeEach { + $PreviousTemp = $env:TEMP + $TestRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([guid]::NewGuid().Guid) + $NestedDirectory = Join-Path -Path $TestRoot -ChildPath 'empty\child' + New-Item -Path $NestedDirectory -ItemType Directory -Force | Out-Null + $OldFile = New-Item -Path (Join-Path -Path $NestedDirectory -ChildPath 'old.tmp') -ItemType File + $OldFile.LastWriteTime = (Get-Date).AddDays(-31) + $env:TEMP = $TestRoot + } + + AfterEach { + $env:TEMP = $PreviousTemp + Remove-Item -LiteralPath $TestRoot -Recurse -Force -ErrorAction SilentlyContinue + } + + It 'removes empty directories after removing old files' -Skip:$IsLinux { + Clear-CurrentUserTemp -Days 30 -TimeOut 5 -Confirm:$false | Out-Null + + (Join-Path -Path $TestRoot -ChildPath 'empty') | Should -Not -Exist + } +} + +Describe 'Clear-OldExchangeLog' -Tag Unit { + BeforeEach { + $TestRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([guid]::NewGuid().Guid) + $ExchangeLoggingPath = Join-Path -Path $TestRoot -ChildPath 'Logging' + New-Item -Path $ExchangeLoggingPath -ItemType Directory -Force | Out-Null + New-Item -Path (Join-Path -Path $TestRoot -ChildPath 'Bin\Search\Ceres\Diagnostics\ETLTraces') -ItemType Directory -Force | Out-Null + New-Item -Path (Join-Path -Path $TestRoot -ChildPath 'Bin\Search\Ceres\Diagnostics\Logs') -ItemType Directory -Force | Out-Null + New-Item -Path (Join-Path -Path $TestRoot -ChildPath 'TransportRoles\Logs\MessageTracking') -ItemType Directory -Force | Out-Null + $OldLog = New-Item -Path (Join-Path -Path $ExchangeLoggingPath -ChildPath 'old.log') -ItemType File + $NewLog = New-Item -Path (Join-Path -Path $ExchangeLoggingPath -ChildPath 'new.log') -ItemType File + $IgnoredFile = New-Item -Path (Join-Path -Path $ExchangeLoggingPath -ChildPath 'old.txt') -ItemType File + $OldLog.LastWriteTime = (Get-Date).AddDays(-31) + $IgnoredFile.LastWriteTime = (Get-Date).AddDays(-31) + $NewLog.LastWriteTime = Get-Date + + Mock Get-ItemProperty { + [pscustomobject]@{ + MsiInstallPath = $TestRoot + } + } + Mock Clear-OldIISLog {} + } + + AfterEach { + Remove-Item -LiteralPath $TestRoot -Recurse -Force -ErrorAction SilentlyContinue + } + + It 'calls the existing IIS cleanup command' { + Clear-OldExchangeLog -Days 30 -WhatIf + + Should -Invoke Clear-OldIISLog -Exactly 1 -ParameterFilter { $Days -eq 30 } + } + + It 'removes only old Exchange log files' { + Clear-OldExchangeLog -Days 30 -Confirm:$false + + $OldLog.FullName | Should -Not -Exist + $NewLog.FullName | Should -Exist + $IgnoredFile.FullName | Should -Exist + } +} + diff --git a/src/Tests/Unit/ExportedFunctions.Tests.ps1 b/src/Tests/Unit/ExportedFunctions.Tests.ps1 new file mode 100644 index 0000000..86165c5 --- /dev/null +++ b/src/Tests/Unit/ExportedFunctions.Tests.ps1 @@ -0,0 +1,42 @@ +#------------------------------------------------------------------------- +BeforeAll { + Set-Location -Path $PSScriptRoot + $ModuleName = 'TheCleaners' + $PathToManifest = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psd1") + if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') { + Remove-Module -Name $ModuleName -Force + } + Import-Module $PathToManifest -Force + $manifestContent = Test-ModuleManifest -Path $PathToManifest + $moduleExported = Get-Command -Module $ModuleName | Select-Object -ExpandProperty Name + $manifestExported = ($manifestContent.ExportedFunctions).Keys +} + +Describe $ModuleName { + + Context 'Exported Commands' -Fixture { + + Context 'Number of commands' -Fixture { + It -Name 'Exports the same number of public functions as what is listed in the Module Manifest' -Test { + $manifestExported.Count | Should -BeExactly $moduleExported.Count + } + } + + Context 'Explicitly exported commands' { + It -Name 'Includes <_> in the Module Manifest ExportedFunctions' -ForEach $moduleExported -Test { + $manifestExported | Should -Contain $_ + } + } + } + + Context 'Command Help' { + It -Name '<_> includes complete help' -ForEach $moduleExported -Test { + $help = Get-Help -Name $_ -Full + + $help.Synopsis | Should -Not -BeNullOrEmpty + $help.description.Text | Should -Not -BeNullOrEmpty + $help.examples.example | Should -Not -BeNullOrEmpty + } + } +} + diff --git a/src/Tests/SkipUnit/TheCleaners-Module.Tests.ps1 b/src/Tests/Unit/TheCleaners-Module.Tests.ps1 similarity index 92% rename from src/Tests/SkipUnit/TheCleaners-Module.Tests.ps1 rename to src/Tests/Unit/TheCleaners-Module.Tests.ps1 index 7fde39a..2858d4d 100644 --- a/src/Tests/SkipUnit/TheCleaners-Module.Tests.ps1 +++ b/src/Tests/Unit/TheCleaners-Module.Tests.ps1 @@ -7,43 +7,57 @@ BeforeAll { $PathToModule = [System.IO.Path]::Combine('..', '..', $ModuleName, "$ModuleName.psm1") #------------------------------------------------------------------------- } + Describe 'Module Tests' -Tag Unit { - Context "Module Tests" { - $script:manifestEval = $null + Context 'Module Tests' { + BeforeAll { + $script:manifestEval = $null + } + It 'Passes Test-ModuleManifest' { { $script:manifestEval = Test-ModuleManifest -Path $PathToManifest } | Should -Not -Throw $? | Should -BeTrue } #manifestTest + It 'root module TheCleaners.psm1 should exist' { $PathToModule | Should -Exist $? | Should -BeTrue } #psm1Exists + It 'manifest should contain TheCleaners.psm1' { $PathToManifest | - Should -FileContentMatchExactly "TheCleaners.psm1" + Should -FileContentMatchExactly 'TheCleaners.psm1' } #validPSM1 + It 'should have a matching module name in the manifest' { $script:manifestEval.Name | Should -BeExactly $ModuleName } #name + It 'should have a valid description in the manifest' { $script:manifestEval.Description | Should -Not -BeNullOrEmpty } #description + It 'should have a valid author in the manifest' { $script:manifestEval.Author | Should -Not -BeNullOrEmpty } #author + It 'should have a valid version in the manifest' { $script:manifestEval.Version -as [Version] | Should -Not -BeNullOrEmpty } #version + It 'should have a valid guid in the manifest' { { [guid]::Parse($script:manifestEval.Guid) } | Should -Not -Throw } #guid + It 'should not have any spaces in the tags' { foreach ($tag in $script:manifestEval.Tags) { $tag | Should -Not -Match '\s' } } #tagSpaces + It 'should have a valid project Uri' { $script:manifestEval.ProjectUri | Should -Not -BeNullOrEmpty } #uri } #context_ModuleTests } #describe_ModuleTests + diff --git a/src/TheCleaners.build.ps1 b/src/TheCleaners.build.ps1 index 934b069..e5b3c6f 100644 --- a/src/TheCleaners.build.ps1 +++ b/src/TheCleaners.build.ps1 @@ -249,7 +249,10 @@ Add-BuildTask Test { $pesterConfiguration.Run.PassThru = $true $pesterConfiguration.Run.Exit = $false $pesterConfiguration.CodeCoverage.Enabled = $true - $pesterConfiguration.CodeCoverage.Path = "..\..\..\$ModuleName\*\*.ps1" + $pesterConfiguration.CodeCoverage.Path = @( + Join-Path -Path $script:ModuleSourcePath -ChildPath '*.ps1' + Join-Path -Path $script:ModuleSourcePath -ChildPath '*\*.ps1' + ) $pesterConfiguration.CodeCoverage.CoveragePercentTarget = $script:coverageThreshold $pesterConfiguration.CodeCoverage.OutputPath = "$codeCovPath\CodeCoverage.xml" $pesterConfiguration.CodeCoverage.OutputFormat = 'JaCoCo' @@ -303,7 +306,10 @@ Add-BuildTask DevCC { $pesterConfiguration = New-PesterConfiguration $pesterConfiguration.run.Path = $script:UnitTestsPath $pesterConfiguration.CodeCoverage.Enabled = $true - $pesterConfiguration.CodeCoverage.Path = "$PSScriptRoot\$ModuleName\*\*.ps1" + $pesterConfiguration.CodeCoverage.Path = @( + Join-Path -Path $script:ModuleSourcePath -ChildPath '*.ps1' + Join-Path -Path $script:ModuleSourcePath -ChildPath '*\*.ps1' + ) $pesterConfiguration.CodeCoverage.CoveragePercentTarget = $script:coverageThreshold $pesterConfiguration.CodeCoverage.OutputPath = '..\..\..\cov.xml' $pesterConfiguration.CodeCoverage.OutputFormat = 'CoverageGutters' diff --git a/src/TheCleaners/Private/Remove-OldFiles.ps1 b/src/TheCleaners/Private/Remove-OldFiles.ps1 index 2d3abff..0d5e861 100644 --- a/src/TheCleaners/Private/Remove-OldFiles.ps1 +++ b/src/TheCleaners/Private/Remove-OldFiles.ps1 @@ -12,13 +12,18 @@ #> function Remove-OldFiles { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')] param ( # The path containing files to remove + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })] [string] $Path, # How many days worth of logs to retain (how far back to filter) + [Parameter()] + [ValidateRange(1, [int16]::MaxValue)] [int16] $Days = 60 ) @@ -29,9 +34,15 @@ function Remove-OldFiles { process { Write-Verbose -Message "Finding and removing files older than $Days." - Get-ChildItem -Path $Path -Recurse | Where-Object { - $_.CreationTime -le ([datetime]::Now.AddDays( -$Days )) - } | Remove-Item + $OldFiles = Get-ChildItem -LiteralPath $Path -File -Recurse -ErrorAction Stop | Where-Object { + $_.LastWriteTime -le ([datetime]::Now.AddDays(-$Days)) + } + + foreach ($File in $OldFiles) { + if ($PSCmdlet.ShouldProcess($File.FullName, 'Remove old file')) { + Remove-Item -LiteralPath $File.FullName -ErrorAction Stop + } + } } end { diff --git a/src/TheCleaners/Public/Clear-CurrentUserTemp.ps1 b/src/TheCleaners/Public/Clear-CurrentUserTemp.ps1 index 263304b..93d8204 100644 --- a/src/TheCleaners/Public/Clear-CurrentUserTemp.ps1 +++ b/src/TheCleaners/Public/Clear-CurrentUserTemp.ps1 @@ -85,9 +85,19 @@ function Clear-CurrentUserTemp { break } # Get directories that have 0 files in them. - $EmptyDirectories = Get-ChildItem -Path $UserTempPath -Directory -Recurse | Where-Object { $_.GetFileSystemInfos().Count -eq 0 } | Out-Null + $EmptyDirectories = @(Get-ChildItem -Path $UserTempPath -Directory -Recurse | Where-Object { $_.GetFileSystemInfos().Count -eq 0 }) Write-Verbose "$($EmptyDirectories.Count) empty directories found." - $EmptyDirectories | Remove-Item + $RemovedDirectory = $false + foreach ($Directory in $EmptyDirectories) { + if ($PSCmdlet.ShouldProcess("Removing $($Directory.FullName)", $Directory.FullName, 'Remove-Item')) { + Remove-Item -LiteralPath $Directory.FullName -ErrorAction Stop + $RemovedDirectory = $true + } + } + + if ($EmptyDirectories.Count -gt 0 -and -not $RemovedDirectory) { + break + } } until ( $EmptyDirectories.Count -eq 0 ) diff --git a/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 b/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 index 8dece43..60f9b62 100644 --- a/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 +++ b/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 @@ -28,6 +28,7 @@ function Clear-OldExchangeLog { # Logs older than this number of days will be removed. param ( [Parameter()] + [ValidateRange(1, [int16]::MaxValue)] [int] $Days = 60 ) @@ -54,7 +55,7 @@ function Clear-OldExchangeLog { process { # Clean up the IIS log files - Clear-OldIisLogFiles -Days $Days + Clear-OldIISLog -Days $Days -WhatIf:$WhatIfPreference foreach ($LogLocation in $LogLocations.GetEnumerator()) { if (-not (Test-Path -Path $LogLocation.Value)) { @@ -62,12 +63,12 @@ function Clear-OldExchangeLog { continue } - $OldFiles = Get-ChildItem -Path $($LogLocation.Value) -Recurse | - Where-Object { ($_.Name -like '*.log') -and ($_.lastWriteTime -le "$LastWriteDate") } | Select-Object FullName + $OldFiles = Get-ChildItem -Path $($LogLocation.Value) -File -Recurse | + Where-Object { ($_.Name -like '*.log') -and ($_.LastWriteTime -le $LastWriteDate) } - foreach ($file in $OldFiles) { - if ( $PSCmdlet.ShouldProcess($file.Name) ) { - $file.Delete() + foreach ($File in $OldFiles) { + if ($PSCmdlet.ShouldProcess($File.FullName, 'Remove old Exchange log file')) { + Remove-Item -LiteralPath $File.FullName -ErrorAction Stop } } # end foreach $file diff --git a/src/TheCleaners/Public/Clear-OldIISLog.ps1 b/src/TheCleaners/Public/Clear-OldIISLog.ps1 index 6e3ca8d..a5808a7 100644 --- a/src/TheCleaners/Public/Clear-OldIISLog.ps1 +++ b/src/TheCleaners/Public/Clear-OldIISLog.ps1 @@ -22,7 +22,7 @@ function Clear-OldIISLog { To Do: Add a summary of which blocks were run and possibly a count of log files removed. #> - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] [Alias('Clean-IISLog')] #[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns')] param ( @@ -40,7 +40,9 @@ function Clear-OldIISLog { $SiteLogFileDirectory = ("$($Site.logFile.directory)\W3SVC$($Site.id)").Replace( '%SystemDrive%', $env:SystemDrive ) Write-Information -MessageData "Removing old IIS log files from $($Site.name) at $SiteLogFileDirectory." -InformationAction Continue try { - Remove-OldFiles -Path $SiteLogFileDirectory -Days $Days -WhatIf + if ($PSCmdlet.ShouldProcess($SiteLogFileDirectory, "Remove IIS log files older than $Days days")) { + Remove-OldFiles -Path $SiteLogFileDirectory -Days $Days -Confirm:$false + } } catch { Write-Error -Message $_.Exception.Message -ErrorAction Continue Write-Warning "Failed to remove old IIS log files from $($Site.name) at $SiteLogFileDirectory." -WarningAction Continue @@ -52,7 +54,9 @@ function Clear-OldIISLog { Write-Information "The WebAdministration module is not installed. We will check the default IIS log file location at '$DefaultIISLogLocation'." -InformationAction Continue if (Test-Path -Path $DefaultIISLogLocation -ErrorAction SilentlyContinue) { try { - Remove-OldFiles -Path $DefaultIISLogLocation -Days $Days + if ($PSCmdlet.ShouldProcess($DefaultIISLogLocation, "Remove IIS log files older than $Days days")) { + Remove-OldFiles -Path $DefaultIISLogLocation -Days $Days -Confirm:$false + } } catch { Write-Error -Message $_.Exception.Message -ErrorAction Continue Write-Warning "Failed to remove old log files from the default IIS log file location at '$DefaultIISLogLocation'." -WarningAction Continue @@ -65,7 +69,9 @@ function Clear-OldIISLog { $LogDir = Get-ItemProperty -Path 'HKLM:\\System\CurrentControlSet\Services\W3SVC\Parameters' -Name 'LogDir' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty LogDir if ($LogDir -and (Test-Path -Path $LogDir)) { try { - Remove-OldFiles -Path $LogDir -Days $Days + if ($PSCmdlet.ShouldProcess($LogDir, "Remove IIS log files older than $Days days")) { + Remove-OldFiles -Path $LogDir -Days $Days -Confirm:$false + } } catch { Write-Error -Message $_.Exception.Message -ErrorAction Continue Write-Warning "Failed to remove old IIS log files from the location specified in the directory ($LogDir)." -WarningAction Continue diff --git a/src/TheCleaners/Public/Get-StaleUserProfile.ps1 b/src/TheCleaners/Public/Get-StaleUserProfile.ps1 index e09bfa7..b9ac789 100644 --- a/src/TheCleaners/Public/Get-StaleUserProfile.ps1 +++ b/src/TheCleaners/Public/Get-StaleUserProfile.ps1 @@ -37,7 +37,7 @@ function Get-StaleUserProfile { [array]$StaleUserProfiles = Get-CimInstance -Class Win32_UserProfile | Where-Object { ($_.LastUseTime -lt (Get-Date).AddDays(-$Days)) -and (!$_.Special) -and (!$_.Loaded) } # Might need to check last modified date using NTFS: foreach ($profile in $StaleUserProfiles) { Get-Item -Path $($_.LocalPath).LastWriteTime } - if ($StaleUserProfiles.Count -lt 1 -or !(StaleUserProfiles)) { + if ($StaleUserProfiles.Count -lt 1 -or -not $StaleUserProfiles) { Write-Information 'No stale user profiles were found.' -InformationAction Continue } else { if ($ShowSummary) { diff --git a/src/TheCleaners/Public/Start-Cleaning.ps1 b/src/TheCleaners/Public/Start-Cleaning.ps1 index 5f208e9..11cba1a 100644 --- a/src/TheCleaners/Public/Start-Cleaning.ps1 +++ b/src/TheCleaners/Public/Start-Cleaning.ps1 @@ -6,8 +6,8 @@ .DESCRIPTION Get started with a menu of services The Cleaners can offer. - .PARAMETER NoLogo - Do not display the logo. + .PARAMETER Dedication + Show a short dedication before the command menu. .EXAMPLE Start-Cleaning diff --git a/src/TheCleaners/TheCleaners.psd1 b/src/TheCleaners/TheCleaners.psd1 index 3e2553f..7fa35c3 100644 --- a/src/TheCleaners/TheCleaners.psd1 +++ b/src/TheCleaners/TheCleaners.psd1 @@ -107,9 +107,7 @@ # RequireLicenseAcceptance = $false # External dependent modules of this module - ExternalModuleDependencies = @( - 'WebAdministration' - ) + ExternalModuleDependencies = @() } # End of PSData hashtable From 02330ddcd09f28e3712ace29e8ac0514df76b617 Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:22:27 -0400 Subject: [PATCH 2/4] Update src/TheCleaners/Public/Clear-OldExchangeLog.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/TheCleaners/Public/Clear-OldExchangeLog.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 b/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 index 60f9b62..db6444b 100644 --- a/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 +++ b/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 @@ -68,7 +68,9 @@ function Clear-OldExchangeLog { foreach ($File in $OldFiles) { if ($PSCmdlet.ShouldProcess($File.FullName, 'Remove old Exchange log file')) { - Remove-Item -LiteralPath $File.FullName -ErrorAction Stop + # Confirmation is handled by the outer ShouldProcess check, so suppress + # nested Remove-Item confirmation to avoid duplicate prompts. + Remove-Item -LiteralPath $File.FullName -Confirm:$false -ErrorAction Stop } } # end foreach $file From a08eb46c5cb4ea08650d6289b29e33f02b0c8762 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:24:44 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9Bfix:=20address=20all=20PR=20rev?= =?UTF-8?q?iew=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change [int] to [int16] for Days in Clear-OldExchangeLog (type/range consistency) - Wrap Remove-Item in try/catch in Clear-CurrentUserTemp empty-dir loop - Fix example cmdlet name in docs/Clear-OldIISLog.md (Clear-OldIISLogFile โ†’ Clear-OldIISLog) - Fix default Days description in docs/Clear-OldIISLog.md (30 โ†’ 60) Agent-Logs-Url: https://github.com/SamErde/TheCleaners/sessions/a9cd91d7-216d-48e8-bce3-bbbac2f05bf1 Co-authored-by: SamErde <20478745+SamErde@users.noreply.github.com> --- docs/Clear-OldIISLog.md | 4 ++-- src/TheCleaners/Public/Clear-CurrentUserTemp.ps1 | 8 ++++++-- src/TheCleaners/Public/Clear-OldExchangeLog.ps1 | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/Clear-OldIISLog.md b/docs/Clear-OldIISLog.md index df818f2..3f7ae3f 100644 --- a/docs/Clear-OldIISLog.md +++ b/docs/Clear-OldIISLog.md @@ -24,7 +24,7 @@ This script will clean out IIS log files older than x days. ### EXAMPLE 1 ``` -Clear-OldIISLogFile -Days 60 +Clear-OldIISLog -Days 60 ``` Removes all IIS log files that are older than 60 days. @@ -33,7 +33,7 @@ Removes all IIS log files that are older than 60 days. ### -Days The number of days to keep log files. -The default is 30 days. +The default is 60 days. ```yaml Type: Int16 diff --git a/src/TheCleaners/Public/Clear-CurrentUserTemp.ps1 b/src/TheCleaners/Public/Clear-CurrentUserTemp.ps1 index 93d8204..1131621 100644 --- a/src/TheCleaners/Public/Clear-CurrentUserTemp.ps1 +++ b/src/TheCleaners/Public/Clear-CurrentUserTemp.ps1 @@ -90,8 +90,12 @@ function Clear-CurrentUserTemp { $RemovedDirectory = $false foreach ($Directory in $EmptyDirectories) { if ($PSCmdlet.ShouldProcess("Removing $($Directory.FullName)", $Directory.FullName, 'Remove-Item')) { - Remove-Item -LiteralPath $Directory.FullName -ErrorAction Stop - $RemovedDirectory = $true + try { + Remove-Item -LiteralPath $Directory.FullName -ErrorAction Stop + $RemovedDirectory = $true + } catch { + Write-Warning -Message "Failed to remove directory '$($Directory.FullName)': $($_.Exception.Message)" + } } } diff --git a/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 b/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 index db6444b..4eaae5d 100644 --- a/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 +++ b/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 @@ -29,7 +29,7 @@ function Clear-OldExchangeLog { param ( [Parameter()] [ValidateRange(1, [int16]::MaxValue)] - [int] + [int16] $Days = 60 ) From 32628779cb673a15b00373203cb8def6c9c29b3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:36:15 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=90=9Bfix:=20address=20second=20round?= =?UTF-8?q?=20of=20PR=20review=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove leading backslashes from Join-Path ChildPath in Clear-OldExchangeLog so paths stay under ExchangeInstallPath instead of resolving from drive root - Fix Remove-OldFiles help example: remove -Recurse (no such param), fix typo 'does' โ†’ 'days' - Add -CommandType Function to Get-Command in ExportedFunctions.Tests.ps1 so the runtime query matches the manifest's ExportedFunctions collection Agent-Logs-Url: https://github.com/SamErde/TheCleaners/sessions/21c43959-dae5-453b-af3c-86f58b8a38f6 Co-authored-by: SamErde <20478745+SamErde@users.noreply.github.com> --- src/Tests/Unit/ExportedFunctions.Tests.ps1 | 3 ++- src/TheCleaners/Private/Remove-OldFiles.ps1 | 4 ++-- src/TheCleaners/Public/Clear-OldExchangeLog.ps1 | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Tests/Unit/ExportedFunctions.Tests.ps1 b/src/Tests/Unit/ExportedFunctions.Tests.ps1 index 86165c5..6ce2572 100644 --- a/src/Tests/Unit/ExportedFunctions.Tests.ps1 +++ b/src/Tests/Unit/ExportedFunctions.Tests.ps1 @@ -8,7 +8,8 @@ BeforeAll { } Import-Module $PathToManifest -Force $manifestContent = Test-ModuleManifest -Path $PathToManifest - $moduleExported = Get-Command -Module $ModuleName | Select-Object -ExpandProperty Name + # Limit the runtime query to functions so it matches the manifest's ExportedFunctions collection. + $moduleExported = Get-Command -Module $ModuleName -CommandType Function | Select-Object -ExpandProperty Name $manifestExported = ($manifestContent.ExportedFunctions).Keys } diff --git a/src/TheCleaners/Private/Remove-OldFiles.ps1 b/src/TheCleaners/Private/Remove-OldFiles.ps1 index 0d5e861..6567cfc 100644 --- a/src/TheCleaners/Private/Remove-OldFiles.ps1 +++ b/src/TheCleaners/Private/Remove-OldFiles.ps1 @@ -6,9 +6,9 @@ Remove files in a path that are older than the specified number of days. This function is used by other functions within the module when removing old files. .EXAMPLE - Remove-OldFiles -Path "C:\Windows\Temp" -Days 60 -Recurse + Remove-OldFiles -Path "C:\Windows\Temp" -Days 60 - Removes all files older than 60 does in C:\Windows\Temp with recursion to clean subfolders. + Removes all files older than 60 days in C:\Windows\Temp, including subfolders. #> function Remove-OldFiles { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] diff --git a/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 b/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 index 4eaae5d..d374e36 100644 --- a/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 +++ b/src/TheCleaners/Public/Clear-OldExchangeLog.ps1 @@ -46,8 +46,8 @@ function Clear-OldExchangeLog { $LogLocations = @{ ExchangeLoggingPath = Join-Path -Path $ExchangeInstallPath -ChildPath 'Logging\' -ErrorAction Ignore ETLTracesPath = Join-Path -Path $ExchangeInstallPath -ChildPath 'Bin\Search\Ceres\Diagnostics\ETLTraces\' -ErrorAction Ignore - DiagnosticLogsPath = Join-Path -Path $ExchangeInstallPath -ChildPath '\Bin\Search\Ceres\Diagnostics\Logs' -ErrorAction Ignore - MessageTrackingLogsPath = Join-Path -Path $ExchangeInstallPath -ChildPath '\TransportRoles\Logs\MessageTracking\' -ErrorAction Ignore + DiagnosticLogsPath = Join-Path -Path $ExchangeInstallPath -ChildPath 'Bin\Search\Ceres\Diagnostics\Logs' -ErrorAction Ignore + MessageTrackingLogsPath = Join-Path -Path $ExchangeInstallPath -ChildPath 'TransportRoles\Logs\MessageTracking\' -ErrorAction Ignore } $LastWriteDate = (Get-Date).AddDays(-$Days)