From 902b219b3bd296e67ecf1bf1874efeeaedc02eae Mon Sep 17 00:00:00 2001 From: Leonardo Klein Date: Wed, 18 Feb 2026 19:50:46 -0300 Subject: [PATCH 1/6] refactor: improve code quality, documentation, and add Pester tests Code quality improvements: - Replace @() += anti-pattern with List in Get-FolderPermissions, New-ShareAndDFS, and Deploy-ScheduledTasks - Modernize New-Object to [PSCustomObject] in Get-FolderPermissions - Add SupportsShouldProcess to Remove-OrphanedProfiles for destructive ops - Parameterize FullAccess "Everyone" in New-ShareAndDFS (-ShareAccess) - Add #Requires -Modules ActiveDirectory to Get-DeletedUsers and Get-UserLastLogon - Add Version field (1.0.0) to .NOTES in all scripts - Add explanatory comment for group name regex in Get-UserLastLogon - Clean up trailing blank lines across all scripts Documentation fixes: - Add CI status badge to root README - Fix missing .ps1 extension for Send-PasswordExpiryNotification in README - Add Invoke-WindowsUpdateMaintenance to root README System section - Fix broken Markdown code fence in ActiveDirectory/README.md - Update ActiveDirectory/README.md examples for new PascalCase params - Fix CONTRIBUTING.md category SystemMaintenance -> System, add Registry - Add maintainer contact link to SECURITY.md - Document sample-servers.csv in System/README.md - Update NetworkShares/README.md to mention -ShareAccess parameter Testing: - Add Tests/ directory with Pester test suite (ScriptValidation.Tests.ps1) - Tests validate syntax, help docs, coding standards, and param declarations - Add Pester to CI pipeline --- .github/workflows/validate.yml | 12 ++- CONTRIBUTING.md | 3 +- README.md | 5 +- SECURITY.md | 2 +- Scripts/ActiveDirectory/Get-DeletedUsers.ps1 | 11 ++- Scripts/ActiveDirectory/Get-UserLastLogon.ps1 | 12 +-- Scripts/ActiveDirectory/README.md | 11 +-- Scripts/FileSystem/Get-FolderPermissions.ps1 | 11 +-- Scripts/NetworkShares/New-ShareAndDFS.ps1 | 20 +++-- Scripts/NetworkShares/README.md | 2 +- Scripts/Registry/Disable-LanmanCache.ps1 | 5 +- Scripts/System/Enable-FullDump.ps1 | 3 +- Scripts/System/README.md | 30 +++++++ .../TaskScheduler/Deploy-ScheduledTasks.ps1 | 29 +++--- .../UserProfiles/Remove-OrphanedProfiles.ps1 | 11 +-- Tests/ScriptValidation.Tests.ps1 | 89 +++++++++++++++++++ 16 files changed, 194 insertions(+), 62 deletions(-) create mode 100644 Tests/ScriptValidation.Tests.ps1 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 37a3086..1ae9dec 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -19,10 +19,11 @@ jobs: Write-Host "PowerShell Version: $($PSVersionTable.PSVersion)" Write-Host "OS: $($PSVersionTable.OS)" - - name: Install PSScriptAnalyzer + - name: Install Modules shell: pwsh run: | Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser + Install-Module -Name Pester -Force -Scope CurrentUser -SkipPublisherCheck - name: Run PSScriptAnalyzer shell: pwsh @@ -96,3 +97,12 @@ jobs: } else { Write-Host "All scripts have proper documentation!" } + + - name: Run Pester Tests + shell: pwsh + run: | + $config = New-PesterConfiguration + $config.Run.Path = "Tests" + $config.Run.Exit = $true + $config.Output.Verbosity = "Detailed" + Invoke-Pester -Configuration $config diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c24744..8a4b8e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -103,7 +103,8 @@ Organize scripts into appropriate categories: - **NetworkShares** - SMB shares and DFS management - **TaskScheduler** - Scheduled task management - **UserProfiles** - Windows user profile operations -- **SystemMaintenance** - General system maintenance +- **System** - System-level configuration and maintenance +- **Registry** - Windows Registry configuration - **Monitoring** - System and service monitoring - **Security** - Security-related operations diff --git a/README.md b/README.md index f996ea4..76ddcd8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # PowerShell Scripts Collection +[![PowerShell Script Validation](https://github.com/leonardokr/powershell-scripts/actions/workflows/validate.yml/badge.svg)](https://github.com/leonardokr/powershell-scripts/actions/workflows/validate.yml) + A collection of PowerShell scripts for Windows system administration, Active Directory management, and IT automation tasks. ## 📁 Repository Structure @@ -20,7 +22,7 @@ Scripts/ ### Active Directory - **Get-DeletedUsers.ps1** - Exports deleted AD users within a specified date range - **Get-UserLastLogon.ps1** - Reports user last logon times and group memberships -- **Send-PasswordExpiryNotification** - Password expiration notification for AD users. +- **Send-PasswordExpiryNotification.ps1** - Password expiration notification for AD users ### File System - **Get-FolderPermissions.ps1** - Audits folder permissions across multiple servers @@ -33,6 +35,7 @@ Scripts/ ### System - **Enable-FullDump.ps1** - Configures Windows Error Reporting for full memory dumps +- **Invoke-WindowsUpdateMaintenance.ps1** - Manages Windows Updates across servers during maintenance windows ### Task Scheduler - **Deploy-ScheduledTasks.ps1** - Deploys scheduled tasks to multiple servers diff --git a/SECURITY.md b/SECURITY.md index a3f5b20..37279ec 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -15,7 +15,7 @@ This project supports the following PowerShell versions: If you discover a security vulnerability in any of these scripts, please follow these steps: 1. **Do not** create a public GitHub issue -2. Send an email to the repository maintainer with: +2. Send an email to the repository maintainer via [GitHub profile](https://github.com/leonardokr) with: - Description of the vulnerability - Steps to reproduce - Potential impact diff --git a/Scripts/ActiveDirectory/Get-DeletedUsers.ps1 b/Scripts/ActiveDirectory/Get-DeletedUsers.ps1 index dc01d61..8f24056 100644 --- a/Scripts/ActiveDirectory/Get-DeletedUsers.ps1 +++ b/Scripts/ActiveDirectory/Get-DeletedUsers.ps1 @@ -1,4 +1,6 @@ -<# +#Requires -Modules ActiveDirectory + +<# .SYNOPSIS Exports deleted Active Directory users within a specified date range. @@ -30,7 +32,8 @@ Author : Leonardo Klein Rezende Prerequisite : Active Directory PowerShell module Creation Date : 2025-09-04 - + Version : 1.0.0 + Requires Domain Admin or equivalent permissions to query deleted objects. .LINK @@ -95,7 +98,3 @@ catch { Write-Information "Script execution completed." -InformationAction Continue - - - - diff --git a/Scripts/ActiveDirectory/Get-UserLastLogon.ps1 b/Scripts/ActiveDirectory/Get-UserLastLogon.ps1 index 7f0716e..ea112bd 100644 --- a/Scripts/ActiveDirectory/Get-UserLastLogon.ps1 +++ b/Scripts/ActiveDirectory/Get-UserLastLogon.ps1 @@ -1,4 +1,6 @@ -<# +#Requires -Modules ActiveDirectory + +<# .SYNOPSIS Reports Active Directory user last logon times and group memberships. @@ -34,7 +36,8 @@ Author : Leonardo Klein Rezende Prerequisite : Active Directory PowerShell module Creation Date : 2025-09-04 - + Version : 1.0.0 + LastLogon attribute may not be accurate in multi-DC environments. Consider using lastLogonTimestamp for more accurate results across DCs. @@ -98,6 +101,7 @@ try { @{Name = "Enabled"; Expression = { $_.Enabled } }, @{Name = "GroupMemberships"; Expression = { if ($_.MemberOf) { + # Extract group common names from DN format (e.g., "CN=GroupName,OU=Groups,DC=..." -> "GroupName") ($_.MemberOf -replace '^CN=|,(OU|CN).+') -join ";" } else { @@ -124,7 +128,3 @@ catch { Write-Information "Script execution completed." -InformationAction Continue - - - - diff --git a/Scripts/ActiveDirectory/README.md b/Scripts/ActiveDirectory/README.md index 4c389c7..1fc8b4e 100644 --- a/Scripts/ActiveDirectory/README.md +++ b/Scripts/ActiveDirectory/README.md @@ -62,14 +62,12 @@ Automated password expiration notification system for Active Directory users. **Usage:** ```powershell -# Run with default settings -.\Send-PasswordExpiryNotification.ps1 +# Run with specified SMTP server and search base +.\Send-PasswordExpiryNotification.ps1 -SmtpServer "mail.company.com" -FromAddress "noreply@company.com" -SearchBase "OU=Users,DC=domain,DC=com" # Run in test mode with logging enabled -.\Send-PasswordExpiryNotification.ps1 -testMode -enableLogging - -# Specify custom search base -.\Send-PasswordExpiryNotification.ps1 -searchBase "OU=CompanyUsers,DC=domain,DC=com" +.\Send-PasswordExpiryNotification.ps1 -SmtpServer "mail.company.com" -FromAddress "noreply@company.com" -SearchBase "OU=Users,DC=domain,DC=com" -TestMode -EnableLogging +``` ## Prerequisites @@ -85,4 +83,3 @@ Automated password expiration notification system for Active Directory users. - Always test in non-production environment first - Review and modify configuration variables before production use - Ensure SMTP server allows relay from execution host -``` diff --git a/Scripts/FileSystem/Get-FolderPermissions.ps1 b/Scripts/FileSystem/Get-FolderPermissions.ps1 index 43e5fa1..48fdf6c 100644 --- a/Scripts/FileSystem/Get-FolderPermissions.ps1 +++ b/Scripts/FileSystem/Get-FolderPermissions.ps1 @@ -36,7 +36,8 @@ Author : Leonardo Klein Rezende Prerequisite : PowerShell remoting and administrative access to target servers Creation Date : 2025-09-04 - + Version : 1.0.0 + Requires administrative privileges on target servers. Large folder structures may take considerable time to process. @@ -69,7 +70,7 @@ Write-Information "Starting folder permissions audit..." -InformationAction Cont Write-Information "Target servers: $($ServerList -join ', ')" -InformationAction Continue Write-Information "Base path: $BasePath" -InformationAction Continue -$Report = @() +$Report = [System.Collections.Generic.List[PSObject]]::new() $ProcessedServers = 0 $TotalServers = $ServerList.Count @@ -117,7 +118,7 @@ foreach ($Server in $ServerList) { 'Inherited' = $Access.IsInherited 'ScanDate' = Get-Date -Format "yyyy-MM-dd HH:mm:ss" } - $Report += New-Object -TypeName PSObject -Property $Properties + $Report.Add([PSCustomObject]$Properties) } } catch { @@ -153,7 +154,3 @@ else { Write-Information "Script execution completed." -InformationAction Continue - - - - diff --git a/Scripts/NetworkShares/New-ShareAndDFS.ps1 b/Scripts/NetworkShares/New-ShareAndDFS.ps1 index b306530..24f8e1c 100644 --- a/Scripts/NetworkShares/New-ShareAndDFS.ps1 +++ b/Scripts/NetworkShares/New-ShareAndDFS.ps1 @@ -15,6 +15,10 @@ .PARAMETER ServerName The server name for DFS targets. Default is current computer name. +.PARAMETER ShareAccess + The identity to grant full access on created SMB shares. Default is "Everyone". + Modify to restrict access as needed (e.g., "Domain Users", "DOMAIN\ShareGroup"). + .PARAMETER LogPath Path for the log file. Default is "C:\Logs". @@ -36,7 +40,8 @@ Author : Leonardo Klein Rezende Prerequisite : DFSN PowerShell module, Administrative privileges Creation Date : 2025-08-10 - + Version : 1.0.0 + Requires: - Administrative privileges - DFS Management features installed @@ -57,7 +62,10 @@ param ( [string]$DomainNamespace = "\\domain.local", [Parameter(Mandatory = $false)] - [string]$ServerName = $env:COMPUTERNAME + [string]$ServerName = $env:COMPUTERNAME, + + [Parameter(Mandatory = $false)] + [string]$ShareAccess = "Everyone" ) @@ -99,7 +107,7 @@ function New-SMBShareSafe { } if ($PSCmdlet.ShouldProcess($Name, "Create SMB share")) { - New-SmbShare -Name $Name -Path $Path -Description $Description -FullAccess "Everyone" + New-SmbShare -Name $Name -Path $Path -Description $Description -FullAccess $ShareAccess Write-ScriptLog "SMB share '$Name' created successfully" 'Info' return $true } @@ -168,7 +176,7 @@ try { $successCount = 0 $failureCount = 0 - $results = @() + $results = [System.Collections.Generic.List[PSObject]]::new() foreach ($folder in $folders) { $folderName = $folder.Name @@ -200,13 +208,13 @@ try { Write-ScriptLog "Failed to process folder '$folderName': $message" 'Error' } - $results += [PSCustomObject]@{ + $results.Add([PSCustomObject]@{ FolderName = $folderName ShareName = $shareName FolderPath = $folderPath Status = $status Message = $message - } + }) } Write-ScriptLog "Processing completed" 'Info' diff --git a/Scripts/NetworkShares/README.md b/Scripts/NetworkShares/README.md index d93c179..f714677 100644 --- a/Scripts/NetworkShares/README.md +++ b/Scripts/NetworkShares/README.md @@ -44,5 +44,5 @@ Creates SMB shares and corresponding DFS namespaces for all folders in a specifi - Script automatically handles existing shares/namespaces - DFS namespaces are created as DomainV2 type -- All shares are created with "Everyone" full access (modify as needed) +- Shares are created with "Everyone" full access by default; use `-ShareAccess` to restrict (e.g., `-ShareAccess "Domain Users"`) - Comprehensive logging available in debug mode diff --git a/Scripts/Registry/Disable-LanmanCache.ps1 b/Scripts/Registry/Disable-LanmanCache.ps1 index 3dc96c3..276f322 100644 --- a/Scripts/Registry/Disable-LanmanCache.ps1 +++ b/Scripts/Registry/Disable-LanmanCache.ps1 @@ -33,7 +33,8 @@ Author : Leonardo Klein Rezende Prerequisite : Administrator privileges Creation Date : 2025-09-05 - + Version : 1.0.0 + IMPORTANT: This script requires administrator privileges to modify registry settings. Restart may be required for changes to take effect. @@ -115,5 +116,3 @@ catch { exit 1 } - - diff --git a/Scripts/System/Enable-FullDump.ps1 b/Scripts/System/Enable-FullDump.ps1 index 6973617..398058d 100644 --- a/Scripts/System/Enable-FullDump.ps1 +++ b/Scripts/System/Enable-FullDump.ps1 @@ -33,7 +33,8 @@ Author : Leonardo Klein Rezende Prerequisite : Administrator privileges Creation Date : 2025-09-05 - + Version : 1.0.0 + IMPORTANT: This script requires administrator privileges to modify registry settings. Full dumps can be very large and consume significant disk space. diff --git a/Scripts/System/README.md b/Scripts/System/README.md index aab7a59..d566be9 100644 --- a/Scripts/System/README.md +++ b/Scripts/System/README.md @@ -4,6 +4,36 @@ This folder contains PowerShell scripts for managing Windows system settings and ## Scripts +### Invoke-WindowsUpdateMaintenance.ps1 +Manages Windows Updates across multiple servers during scheduled maintenance windows with staged execution. + +**Purpose:** +- Orchestrates Windows Updates installation across server infrastructure +- Supports CSV-based server lists and SQL Server service management +- Provides comprehensive logging for audit trails +- Enables staged execution for maintenance windows + +**Requirements:** +- Administrator privileges +- PowerShell 5.1 or later +- PSWindowsUpdate module +- WinRM enabled on target servers + +**Usage:** +```powershell +# Check for updates +.\Invoke-WindowsUpdateMaintenance.ps1 -ServerListPath "servers.csv" -Stage Check + +# Reboot servers after installation +.\Invoke-WindowsUpdateMaintenance.ps1 -ServerListPath "servers.csv" -Stage Reboot + +# Final verification +.\Invoke-WindowsUpdateMaintenance.ps1 -ServerListPath "servers.csv" -Stage Finalize +``` + +**Sample CSV:** +A sample server list file (`sample-servers.csv`) is included in this directory for reference. + ### Enable-FullDump.ps1 Configures Windows Error Reporting to create full memory dumps when applications crash. diff --git a/Scripts/TaskScheduler/Deploy-ScheduledTasks.ps1 b/Scripts/TaskScheduler/Deploy-ScheduledTasks.ps1 index 07e1420..738cca3 100644 --- a/Scripts/TaskScheduler/Deploy-ScheduledTasks.ps1 +++ b/Scripts/TaskScheduler/Deploy-ScheduledTasks.ps1 @@ -45,7 +45,8 @@ Author : Leonardo Klein Rezende Prerequisite : PowerShell remoting, Administrative access to target servers Creation Date : 2025-09-05 - + Version : 1.0.0 + Requires: - PowerShell remoting enabled on target servers - Administrative privileges on target servers @@ -221,47 +222,47 @@ try { $successCount = 0 $failureCount = 0 - $results = @() - + $results = [System.Collections.Generic.List[PSObject]]::new() + foreach ($server in $ServerList) { Write-ScriptLog "Processing server: $server" 'Info' - + try { if (-not (Test-NetConnection -ComputerName $server -Port 5985 -InformationLevel Quiet)) { throw "Cannot connect to $server on port 5985 (WinRM)" } - + Copy-FilesToServer -ServerName $server -Files $FilesToCopy -TargetDir $TargetDirectory - + $taskResult = Register-ScheduledTaskOnServer -ServerName $server -TaskName $TaskName -TargetDir $TargetDirectory -ExecuteAfter $ExecuteAfterRegister - + if ($taskResult) { $successCount++ - $results += [PSCustomObject]@{ + $results.Add([PSCustomObject]@{ Server = $server Status = 'Success' Message = 'Task deployed successfully' - } + }) } else { $failureCount++ - $results += [PSCustomObject]@{ + $results.Add([PSCustomObject]@{ Server = $server Status = 'Failed' Message = 'Task registration failed' - } + }) } } catch { $failureCount++ $errorMessage = $_.Exception.Message Write-ScriptLog "Server $server failed: $errorMessage" 'Error' - - $results += [PSCustomObject]@{ + + $results.Add([PSCustomObject]@{ Server = $server Status = 'Failed' Message = $errorMessage - } + }) } } diff --git a/Scripts/UserProfiles/Remove-OrphanedProfiles.ps1 b/Scripts/UserProfiles/Remove-OrphanedProfiles.ps1 index 1ca6176..4bc54fb 100644 --- a/Scripts/UserProfiles/Remove-OrphanedProfiles.ps1 +++ b/Scripts/UserProfiles/Remove-OrphanedProfiles.ps1 @@ -40,7 +40,8 @@ Author : Leonardo Klein Rezende Prerequisite : Administrative privileges Creation Date : 2025-09-04 - + Version : 1.0.0 + WARNING: This script modifies the Windows registry. Always test in a non-production environment first and ensure you have a system backup. @@ -50,7 +51,7 @@ https://docs.microsoft.com/en-us/windows/win32/shell/user-profiles #> -[CmdletBinding()] +[CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $false)] [switch]$RemoveProfiles, @@ -146,7 +147,7 @@ try { Action = "None" } - if ($RemoveProfiles) { + if ($RemoveProfiles -and $PSCmdlet.ShouldProcess($ProfileSID, "Remove orphaned profile registry entry")) { try { Write-Information "Removing orphaned profile: $ProfileSID" -InformationAction Continue Remove-Item -Path $ProfileEntry.PSPath -Recurse -Force -ErrorAction Stop @@ -208,7 +209,3 @@ Write-Information "Script execution completed." -InformationAction Continue Stop-Transcript - - - - diff --git a/Tests/ScriptValidation.Tests.ps1 b/Tests/ScriptValidation.Tests.ps1 new file mode 100644 index 0000000..ae404aa --- /dev/null +++ b/Tests/ScriptValidation.Tests.ps1 @@ -0,0 +1,89 @@ +#Requires -Modules Pester + +<# +.SYNOPSIS + Pester tests for validating PowerShell scripts in the repository. + +.DESCRIPTION + Validates script syntax, parameter declarations, help documentation, + and coding standards across all scripts in the Scripts directory. +#> + +BeforeDiscovery { + $ScriptFiles = Get-ChildItem -Path "$PSScriptRoot\..\Scripts" -Recurse -Filter "*.ps1" +} + +Describe "Script Validation: <_.Name>" -ForEach $ScriptFiles { + BeforeAll { + $ScriptPath = $_.FullName + $ScriptContent = Get-Content $ScriptPath -Raw + $ScriptHelp = Get-Help $ScriptPath -ErrorAction SilentlyContinue + } + + Context "Syntax" { + It "Should have valid PowerShell syntax" { + $errors = $null + [System.Management.Automation.PSParser]::Tokenize($ScriptContent, [ref]$errors) + $errors.Count | Should -Be 0 + } + } + + Context "Help Documentation" { + It "Should have a SYNOPSIS" { + $ScriptContent | Should -Match '\.SYNOPSIS' + } + + It "Should have a DESCRIPTION" { + $ScriptContent | Should -Match '\.DESCRIPTION' + } + + It "Should have at least one EXAMPLE" { + $ScriptContent | Should -Match '\.EXAMPLE' + } + + It "Should have NOTES" { + $ScriptContent | Should -Match '\.NOTES' + } + + It "Should have a LINK" { + $ScriptContent | Should -Match '\.LINK' + } + + It "Should have a Version in NOTES" { + $ScriptContent | Should -Match 'Version\s*:' + } + } + + Context "Coding Standards" { + It "Should use CmdletBinding" { + $ScriptContent | Should -Match '\[CmdletBinding' + } + + It "Should not have trailing whitespace on lines" { + $lines = $ScriptContent -split "`n" + $trailingWhitespace = $lines | Where-Object { $_ -match '\S\s+$' } + $trailingWhitespace.Count | Should -Be 0 + } + + It "Should use UTF-8 encoding for CSV exports" { + if ($ScriptContent -match 'Export-Csv') { + $ScriptContent | Should -Match 'Encoding\s+(UTF8|utf8)' + } + } + } + + Context "Parameter Validation" { + It "Should declare parameters documented in help" { + if ($ScriptHelp.parameters.parameter) { + foreach ($param in $ScriptHelp.parameters.parameter) { + $paramName = $param.Name + # Skip common parameters provided by CmdletBinding + if ($paramName -in @('WhatIf', 'Confirm', 'Verbose', 'Debug', 'ErrorAction', 'WarningAction', 'InformationAction', 'ErrorVariable', 'WarningVariable', 'InformationVariable', 'OutVariable', 'OutBuffer', 'PipelineVariable')) { + continue + } + $ScriptContent | Should -Match "\`$$paramName" -Because "Parameter '$paramName' is documented but should be declared" + } + } + } + } +} From 6b34411dfe4a54faf117a6efc5964bb257ad5106 Mon Sep 17 00:00:00 2001 From: Leonardo Klein Date: Wed, 18 Feb 2026 20:19:48 -0300 Subject: [PATCH 2/6] fix: remove obsolete SuppressMessage for EnableDebugMode in New-ShareAndDFS After merging the bug fix PR, EnableDebugMode is now properly used in the script, making the PSReviewUnusedParameter suppression unnecessary and causing a DiagnosticRecord error in PSScriptAnalyzer. --- Scripts/NetworkShares/New-ShareAndDFS.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Scripts/NetworkShares/New-ShareAndDFS.ps1 b/Scripts/NetworkShares/New-ShareAndDFS.ps1 index 839b864..f373b0e 100644 --- a/Scripts/NetworkShares/New-ShareAndDFS.ps1 +++ b/Scripts/NetworkShares/New-ShareAndDFS.ps1 @@ -53,7 +53,6 @@ #> [CmdletBinding(SupportsShouldProcess)] -[System.Diagnostics.CodeAnalysis.SuppressMessage('PSReviewUnusedParameter', 'EnableDebugMode', Justification = 'Used in Write-ScriptLog function scope')] param ( [Parameter(Mandatory = $false)] [ValidateScript({ Test-Path $_ -PathType Container })] From e419668b7d8b0fd2c3c5a6c82f29aa2fae96dbce Mon Sep 17 00:00:00 2001 From: Leonardo Klein Date: Wed, 18 Feb 2026 20:21:31 -0300 Subject: [PATCH 3/6] fix: add SuppressMessage for ShareAccess param in New-ShareAndDFS PSScriptAnalyzer cannot detect usage of ShareAccess inside the nested New-ShareFolder function scope. Adding targeted suppression. --- Scripts/NetworkShares/New-ShareAndDFS.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Scripts/NetworkShares/New-ShareAndDFS.ps1 b/Scripts/NetworkShares/New-ShareAndDFS.ps1 index f373b0e..8e4dbdb 100644 --- a/Scripts/NetworkShares/New-ShareAndDFS.ps1 +++ b/Scripts/NetworkShares/New-ShareAndDFS.ps1 @@ -52,6 +52,8 @@ https://docs.microsoft.com/en-us/powershell/module/smbshare/ #> +[System.Diagnostics.CodeAnalysis.SuppressMessage('PSReviewUnusedParameter', 'ShareAccess', + Justification = 'Used in New-ShareFolder function scope')] [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $false)] From 418f1c6d4315dfaad77f1b8a524339d8bb0b57ed Mon Sep 17 00:00:00 2001 From: Leonardo Klein Date: Wed, 18 Feb 2026 20:24:58 -0300 Subject: [PATCH 4/6] fix: correct trailing whitespace test to handle Windows line endings Split by \r?\n instead of \n and match only spaces/tabs (not \r) to avoid false positives from carriage returns on Windows. --- Tests/ScriptValidation.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/ScriptValidation.Tests.ps1 b/Tests/ScriptValidation.Tests.ps1 index ae404aa..bd6e17d 100644 --- a/Tests/ScriptValidation.Tests.ps1 +++ b/Tests/ScriptValidation.Tests.ps1 @@ -60,8 +60,8 @@ Describe "Script Validation: <_.Name>" -ForEach $ScriptFiles { } It "Should not have trailing whitespace on lines" { - $lines = $ScriptContent -split "`n" - $trailingWhitespace = $lines | Where-Object { $_ -match '\S\s+$' } + $lines = $ScriptContent -split '\r?\n' + $trailingWhitespace = $lines | Where-Object { $_ -match '\S[ \t]+$' } $trailingWhitespace.Count | Should -Be 0 } From 101e316e739e18ec324b4dd8962164ee7c81c0fa Mon Sep 17 00:00:00 2001 From: Leonardo Klein Date: Wed, 18 Feb 2026 20:30:49 -0300 Subject: [PATCH 5/6] fix: resolve remaining Pester test failures - Add missing comma after ShareAccess param in New-ShareAndDFS (syntax error) - Add Version to NOTES in Send-PasswordExpiryNotification and Invoke-WindowsUpdateMaintenance - Remove trailing whitespace in Invoke-WindowsUpdateMaintenance and Remove-OrphanedProfiles --- .../ActiveDirectory/Send-PasswordExpiryNotification.ps1 | 1 + Scripts/NetworkShares/New-ShareAndDFS.ps1 | 4 +++- Scripts/System/Invoke-WindowsUpdateMaintenance.ps1 | 9 +++++---- Scripts/UserProfiles/Remove-OrphanedProfiles.ps1 | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Scripts/ActiveDirectory/Send-PasswordExpiryNotification.ps1 b/Scripts/ActiveDirectory/Send-PasswordExpiryNotification.ps1 index 8840365..fb52884 100644 --- a/Scripts/ActiveDirectory/Send-PasswordExpiryNotification.ps1 +++ b/Scripts/ActiveDirectory/Send-PasswordExpiryNotification.ps1 @@ -50,6 +50,7 @@ Author : Leonardo Klein Rezende Prerequisite : PowerShell 5.1+, ActiveDirectory Module, SMTP Server Access Creation Date : 2025-09-04 + Version : 1.0.0 .LINK https://github.com/leonardokr/powershell-scripts diff --git a/Scripts/NetworkShares/New-ShareAndDFS.ps1 b/Scripts/NetworkShares/New-ShareAndDFS.ps1 index 8e4dbdb..7bff845 100644 --- a/Scripts/NetworkShares/New-ShareAndDFS.ps1 +++ b/Scripts/NetworkShares/New-ShareAndDFS.ps1 @@ -67,7 +67,9 @@ param ( [string]$ServerName = $env:COMPUTERNAME, [Parameter(Mandatory = $false)] - [string]$ShareAccess = "Everyone" + [string]$ShareAccess = "Everyone", + + [Parameter(Mandatory = $false)] [string]$LogPath = "C:\Logs", [Parameter(Mandatory = $false)] diff --git a/Scripts/System/Invoke-WindowsUpdateMaintenance.ps1 b/Scripts/System/Invoke-WindowsUpdateMaintenance.ps1 index 20fc92f..78b0728 100644 --- a/Scripts/System/Invoke-WindowsUpdateMaintenance.ps1 +++ b/Scripts/System/Invoke-WindowsUpdateMaintenance.ps1 @@ -13,7 +13,7 @@ .PARAMETER Stage Execution stage: Check, Reboot, Recheck, or Finalize. -.PARAMETER LogPath +.PARAMETER LogPath Directory path for log files. Default: C:\Logs\WindowsUpdates .PARAMETER RebootTimeoutMinutes @@ -37,7 +37,8 @@ Author : Leonardo Klein Rezende Prerequisite : Administrator privileges, PowerShell 5.1+, PSWindowsUpdate module Creation Date : 2025-10-03 - + Version : 1.0.0 + MAINTENANCE WINDOW WORKFLOW: 1. Initial update installation 2. Server reboots and SQL Server update approval @@ -150,8 +151,8 @@ function Get-SQLServerService { try { $sqlServices = Invoke-Command -ComputerName $ServerName -ScriptBlock { Get-Service | Where-Object { - $_.Name -like "MSSQL*" -or - $_.Name -like "SQLServer*" -or + $_.Name -like "MSSQL*" -or + $_.Name -like "SQLServer*" -or $_.Name -eq "SQLSERVERAGENT" -or $_.Name -like "SQL*Agent*" } | Select-Object Name, Status, StartType diff --git a/Scripts/UserProfiles/Remove-OrphanedProfiles.ps1 b/Scripts/UserProfiles/Remove-OrphanedProfiles.ps1 index 4bc54fb..58e2572 100644 --- a/Scripts/UserProfiles/Remove-OrphanedProfiles.ps1 +++ b/Scripts/UserProfiles/Remove-OrphanedProfiles.ps1 @@ -42,7 +42,7 @@ Creation Date : 2025-09-04 Version : 1.0.0 - WARNING: This script modifies the Windows registry. Always test in a + WARNING: This script modifies the Windows registry. Always test in a non-production environment first and ensure you have a system backup. Requires administrative privileges to modify registry. @@ -103,7 +103,7 @@ $OrphanedCount = 0 $RemovedCount = 0 try { - $ProfileEntries = Get-ChildItem -Path $ProfileListPath -ErrorAction Stop | + $ProfileEntries = Get-ChildItem -Path $ProfileListPath -ErrorAction Stop | Where-Object { $_.PSChildName -notin $ExcludedSIDs } $TotalProfiles = $ProfileEntries.Count From 94625d1b326761541f87b2f4cb496647b5e81f62 Mon Sep 17 00:00:00 2001 From: Leonardo Klein Date: Wed, 18 Feb 2026 20:32:59 -0300 Subject: [PATCH 6/6] fix: restore SuppressMessage for EnableDebugMode in New-ShareAndDFS PSScriptAnalyzer cannot detect usage of EnableDebugMode inside the nested Write-ScriptLog function scope. Re-adding targeted suppression. --- Scripts/NetworkShares/New-ShareAndDFS.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Scripts/NetworkShares/New-ShareAndDFS.ps1 b/Scripts/NetworkShares/New-ShareAndDFS.ps1 index 7bff845..710bf89 100644 --- a/Scripts/NetworkShares/New-ShareAndDFS.ps1 +++ b/Scripts/NetworkShares/New-ShareAndDFS.ps1 @@ -54,6 +54,8 @@ [System.Diagnostics.CodeAnalysis.SuppressMessage('PSReviewUnusedParameter', 'ShareAccess', Justification = 'Used in New-ShareFolder function scope')] +[System.Diagnostics.CodeAnalysis.SuppressMessage('PSReviewUnusedParameter', 'EnableDebugMode', + Justification = 'Used in Write-ScriptLog function scope')] [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $false)]