diff --git a/scripts_staging/Backend/Mail notification password expiry.ps1 b/scripts_staging/Backend/Mail notification password expiry.ps1 index 10ce765f..3ad7a771 100644 --- a/scripts_staging/Backend/Mail notification password expiry.ps1 +++ b/scripts_staging/Backend/Mail notification password expiry.ps1 @@ -1,41 +1,49 @@ <# .SYNOPSIS - Analyse et notification avancée des utilisateurs dont le mot de passe Active Directory approche de l’expiration. + Analyzes Active Directory user accounts for upcoming password expiration and optionally sends notifications. + .DESCRIPTION - Ce script interroge Active Directory pour lister les utilisateurs d’une OU cible et calcule la date d’expiration de leur mot de passe selon la politique du domaine. - Les comptes sont classés selon l’urgence : - - Expiré : mot de passe déjà expiré - - Critique : expiration imminente (seuil critique) - - Avertissement : expiration proche (seuil d’avertissement) - Notifications automatiques : - • Email pour tous les utilisateurs concernés - Un rapport HTML détaillé est généré : - • Politique de mot de passe du domaine - • Statistiques par catégorie - • Liste détaillée des comptes par statut -.PARAMETER TargetOU - OU cible pour la recherche des utilisateurs (ex : "OU=Utilisateurs,DC=domaine,DC=local") -.PARAMETER WarningThreshold - Jours avant expiration pour déclencher un avertissement (défaut : 15) -.PARAMETER CriticalThreshold - Jours avant expiration pour déclencher une alerte critique (défaut : 7) -.PARAMETER IncludeDisabled - Inclure les comptes désactivés dans le rapport (défaut : false) -.PARAMETER IncludeNeverExpires - Inclure les comptes dont le mot de passe n’expire jamais (défaut : false) -.PARAMETER EmailSignature - Signature personnalisée pour les emails (optionnel) + This script is configured entirely through environment variables and performs the following: + - Targets a specific Organizational Unit (OU) for user account analysis + - Uses configurable thresholds to classify accounts as warning or critical + - Optionally includes disabled accounts and accounts with passwords set to never expire + - Sends email reports to a list of administrator recipients or can generate reports only + - Supports customizable email signature and SMTP configuration for email delivery + + Accounts are classified based on password expiration: + - Warning: password is approaching expiration (WarningThreshold) + - Critical: password is close to expiring (CriticalThreshold) + .NOTES - Prérequis : - - Module ActiveDirectory - - Accès SMTP pour l’envoi d’emails - - Droits d’administration pour les tâches planifiées + Dependency: + CallPowerShell7 snippet Author: PQU Date: 29/04/2025 #public + +.EXAMPLE + # Example usage with environment variables set before running the script: + + TARGET_OU=OU=Employees,DC=example,DC=local + SMTP_SERVER=smtp.example.com + SMTP_PORT=587 + ADMIN_EMAIL=admin1@example.com,admin2@example.com + FROM_EMAIL=noreply@example.com + WARNING_THRESHOLD=14 + CRITICAL_THRESHOLD=7 + EMAIL_SIGNATURE=Best regards,
IT Department + INCLUDE_DISABLED=true + INCLUDE_NEVER_EXPIRES=false + GENERATE_REPORT_ONLY=false + .CHANGELOG 22.05.25 SAN – Added UTF8 encoding to resolve issues with Russian and French characters. 06.06.25 PQU – Added support for multiple admin emails and centralized config. + 03.07.25 SAN - Update docs + +.TODO + Multiple Locale support + #> diff --git a/scripts_staging/Checks/Active Directory Health.ps1 b/scripts_staging/Checks/Active Directory Health.ps1 index 1be837a2..ea45ffca 100644 --- a/scripts_staging/Checks/Active Directory Health.ps1 +++ b/scripts_staging/Checks/Active Directory Health.ps1 @@ -13,43 +13,113 @@ #public .CHANGELOG + 17.07.25 SAN Big cleanup of bug fixes for the dcdiag function, fixes of error codes, output in stderr of all errors for readability + +.TODO + Do a breakdown at the top of the output for easy read with ok/ko returns from functions #> # Initialize exit code -$exitCode = 0 +$global:exitCode = 0 # Function to perform Active Directory tests function CheckAD { [CmdletBinding()] param ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] - [string[]]$Tests + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] + [string[]]$Tests, + + [Parameter()] + [hashtable]$SuccessPatterns = @{ + 'en' = @('passed test') + 'fr' = @('a réussi', 'a reussi', 'a russi', 'ussi') + }, + + [Parameter()] + [int]$MinimumMatches = 2 ) - process { - $results = @{} + $DebugMode = $false + $global:exitCode = 0 - foreach ($test in $Tests) { - $output = dcdiag /test:$test + # Combine all success patterns from all languages into a single list + $allPatterns = @() + foreach ($lang in $SuccessPatterns.Keys) { + $allPatterns += $SuccessPatterns[$lang] + } - if ($output -notmatch "chou") { - $results[$test] = "OK" - } else { - $results[$test] = "Failed!" - $global:exitCode++ + if ($DebugMode) { + Write-Host "`n[DEBUG] Loaded Success Patterns:" + foreach ($p in $allPatterns) { + Write-Host " - $p" + } + Write-Host "" + } + + $results = @{} + + foreach ($test in $Tests) { + Write-Host "`nRunning DCDIAG test: $test" + + # Start dcdiag process and redirect output + $startInfo = New-Object System.Diagnostics.ProcessStartInfo + $startInfo.FileName = "dcdiag.exe" + $startInfo.Arguments = "/test:$test" + $startInfo.RedirectStandardOutput = $true + $startInfo.RedirectStandardError = $true + $startInfo.UseShellExecute = $false + $startInfo.CreateNoWindow = $true + $process = New-Object System.Diagnostics.Process + $process.StartInfo = $startInfo + $process.Start() | Out-Null + $stream = $process.StandardOutput.BaseStream + $memoryStream = New-Object System.IO.MemoryStream + $buffer = New-Object byte[] 4096 + while (($read = $stream.Read($buffer, 0, $buffer.Length)) -gt 0) { + $memoryStream.Write($buffer, 0, $read) + } + $process.WaitForExit() + + $bytes = $memoryStream.ToArray() + $output = [System.Text.Encoding]::GetEncoding(1252).GetString($bytes) + + if ($DebugMode) { + $preview = if ($output.Length -gt 800) { $output.Substring(0,800) + "`n..." } else { $output } + Write-Host "[DEBUG] DCDIAG Output Preview:" + Write-Host $preview + Write-Host "" + } + + $matchCount = 0 + foreach ($pattern in $allPatterns) { + $count = ([regex]::Matches($output, [regex]::Escape($pattern))).Count + $matchCount += $count + + if ($DebugMode) { + Write-Host "[DEBUG] Pattern '$pattern' matched $count time(s)." } + } + + if ($DebugMode) { + Write-Host "[DEBUG] Total success match count: $matchCount`n" + } - # Output individual test result - Write-Host "DCDIAG Test: $test Result: $($results[$test])" + if ($matchCount -ge $MinimumMatches) { + $results[$test] = "OK" + } else { + $results[$test] = "Failed!" + Write-Error "$results[$test] = Failed!" + $global:exitCode++ } - $results + Write-Host "DCDIAG Test: $test Result: $($results[$test])" } + + return $results } # Function to compare GPO version numbers - function Compare-GPOVersions { [CmdletBinding()] param () @@ -72,34 +142,36 @@ function Compare-GPOVersions { # USER - Compare version numbers if ($NumUserSysvol -ne $NumUserAD) { - Write-Host "$GPOName ($GPOId) : USER Versions différentes (Sysvol : $NumUserSysvol | AD : $NumUserAD)" -ForegroundColor Red + Write-Host "$GPOName ($GPOId) : USER Versions différentes (Sysvol : $NumUserSysvol | AD : $NumUserAD)" + Write-Error "$GPOName ($GPOId) : USER Versions différentes (Sysvol : $NumUserSysvol | AD : $NumUserAD)" $global:exitCode++ } else { - Write-Host "$GPOName : USER Versions identiques" -ForegroundColor Green + Write-Host "$GPOName : USER Versions identiques" } # COMPUTER - Compare version numbers - if ($NumComputerSysvol -ne $NumComputerAD) { - Write-Host "$GPOName ($GPOId) : COMPUTER Versions différentes (Sysvol : $NumComputerSysvol | AD : $NumComputerAD)" -ForegroundColor Red + if ($NumComputerSysvol -ne $NumComputerAD) {Health + Write-Host "$GPOName ($GPOId) : COMPUTER Versions différentes (Sysvol : $NumComputerSysvol | AD : $NumComputerAD)" + Write-Error "$GPOName ($GPOId) : COMPUTER Versions différentes (Sysvol : $NumComputerSysvol | AD : $NumComputerAD)" $global:exitCode++ } else { - Write-Host "$GPOName : COMPUTER Versions identiques" -ForegroundColor Green + Write-Host "$GPOName : COMPUTER Versions identiques" } } - Write-Host "GPO USER/COMPUTER Version OK" -ForegroundColor Green + Write-Host "GPO USER/COMPUTER Version OK" } } # Function to check if the Recycle Bin in enabled - function Check-ADRecycleBin { $recycleFeatures = Get-ADOptionalFeature -Filter {name -like "recycle bin feature"} foreach ($feature in $recycleFeatures) { if ($null -ne $feature.EnabledScopes) { - Write-Output "OK: Recycle Bin enabled" + Write-Host "OK: Recycle Bin enabled" } else { - Write-Output "KO: Recycle Bin disabled" + Write-Host "KO: Recycle Bin disabled" + Write-Error "KO: Recycle Bin disabled" $global:exitCode++ } } @@ -110,31 +182,29 @@ try { $adFeature = Get-WindowsFeature -Name AD-Domain-Services -ErrorAction Stop if ($adFeature.InstallState -eq "Installed") { - # Specify your AD tests + + # function with the AD tests $tests = ("Advertising", "FrsSysVol", "MachineAccount", "Replications", "RidManager", "Services", "FsmoCheck", "SysVolCheck") - # Call the function with the AD tests - Write-Host "DCDIAG" + Write-Host "DCDIAG tests: $tests" $testResults = CheckAD -Tests $tests - $failedTests = $testResults.GetEnumerator() | Where-Object { $_.Value -eq "Failed!" } - if ($failedTests) { Write-Error "Some Active Directory tests failed." - $failedTests | ForEach-Object { Write-Error "$($_.Key) test failed." } - $global:exitCode += $failedTests.Count } else { Write-Host "All Active Directory tests passed successfully." } Write-Host "" + + # function to compare GPO versions Write-Host "GPO Versions checks" - # Call the function to compare GPO versions Compare-GPOVersions - Write-Host "" + + # function to check the Recycle Bin Write-Host "Recycle Bin checks" - # Call the function to check the Recycle Bin Check-ADRecycleBin - + Write-Host "" + } else { Write-Host "Active Directory Domain Services feature is not installed or not in the 'Installed' state." exit @@ -144,4 +214,5 @@ try { $global:exitCode++ } -exit $exitCode \ No newline at end of file +$host.SetShouldExit($global:exitCode) +exit $global:exitCode \ No newline at end of file diff --git a/scripts_staging/Checks/Disk Free Space.ps1 b/scripts_staging/Checks/Disk Free Space.ps1 index 2f71887b..4ba8b717 100644 --- a/scripts_staging/Checks/Disk Free Space.ps1 +++ b/scripts_staging/Checks/Disk Free Space.ps1 @@ -29,18 +29,19 @@ #public .CHANGELOG - + 17.07.25 SAN Added debug flag, taken into account cases where all drives are ignored. + + .TODO - Add debug flag move flags to env #> - param( [int]$warningThreshold = 10, [int]$errorThreshold = 5, - [string[]]$ignoreDisks = @() + [string[]]$ignoreDisks = @(), + [bool]$DebugOutput = $false ) function CheckDiskSpace { @@ -48,7 +49,18 @@ function CheckDiskSpace { param() # Get all local drives excluding network drives and the ones specified to ignore - $drives = Get-WmiObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 -and $_.DeviceID -notin $ignoreDisks } + $allDrives = Get-WmiObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } + $drives = $allDrives | Where-Object { $_.DeviceID -notin $ignoreDisks } + + if ($drives.Count -eq 0) { + Write-Host "OK: disks $($ignoreDisks -join ', ') are ignored" + if ($DebugOutput) { + Write-Host "[DEBUG] Total drives found: $($allDrives.Count)" + Write-Host "[DEBUG] Ignored drives: $($ignoreDisks -join ', ')" + } + $host.SetShouldExit(0) + return + } $failedDrives = @() $warningDrives = @() @@ -77,23 +89,38 @@ function CheckDiskSpace { Write-Host "OK: $($drive.DeviceID) has $($freeSpacePercent)% free space." } } + + if ($DebugOutput) { + if ($failedDrives.Count -gt 0) { + Write-Host "DEBUG: The following drives failed:" + $failedDrives | ForEach-Object { + $p = [math]::Round(($_.FreeSpace / $_.Size) * 100, 2) + Write-Host "DEBUG: $($_.DeviceID): $p%" + } + } elseif ($warningDrives.Count -gt 0) { + Write-Host "DEBUG: The following drives are in warning:" + $warningDrives | ForEach-Object { + $p = [math]::Round(($_.FreeSpace / $_.Size) * 100, 2) + Write-Host "DEBUG: $($_.DeviceID): $p%" + } + } else { + Write-Host "DEBUG: All drives have sufficient free space." + } + } + if ($failedDrives.Count -gt 0) { - # Write-Host "ERROR: The following drives have less than $($errorThreshold)% free space:" - # $failedDrives | ForEach-Object { Write-Host "$($_.DeviceID): $([math]::Round(($_.FreeSpace / $_.Size) * 100, 2))%" } - # Write-Host "ERROR: exit 2" + if ($DebugOutput) { Write-Host "DEBUG: exit code 2" } $host.SetShouldExit(2) } elseif ($warningDrives.Count -gt 0) { - # Write-Host "WARNING: The following drives have less than $($warningThreshold)% free space:" - # $warningDrives | ForEach-Object { Write-Host "$($_.DeviceID): $([math]::Round(($_.FreeSpace / $_.Size) * 100, 2))%" } - # Write-Host "Warning: exit 1" + if ($DebugOutput) { Write-Host "DEBUG: exit code 1" } $host.SetShouldExit(1) } else { - # Write-Host "OK: All drives have sufficient free space." + if ($DebugOutput) { Write-Host "DEBUG: exit code 0" } $host.SetShouldExit(0) } } # Execute the function -CheckDiskSpace \ No newline at end of file +CheckDiskSpace diff --git a/scripts_staging/Checks/Task Scheduler scanner.ps1 b/scripts_staging/Checks/Task Scheduler scanner.ps1 index 19684a27..82382c0f 100644 --- a/scripts_staging/Checks/Task Scheduler scanner.ps1 +++ b/scripts_staging/Checks/Task Scheduler scanner.ps1 @@ -20,6 +20,7 @@ .CHANGELOG 02.07.25 SAN Added company name to the folders + 17.07.25 SAN added powertoys .TODO Use a flag for debug @@ -43,7 +44,8 @@ $ignoreFolders = @( "\Mozilla\", "\Microsoft\Office\", "\Microsoft\Windows\", - "\MySQL\Installer\" + "\MySQL\Installer\", + "\PowerToys\" ) # If it exists and is not empty, add it to the ignore list @@ -62,8 +64,8 @@ $ignoreNames = @( "edgeupdate", "OneDrive Reporting Task", "ZoomUpdateTaskUser", - "OneDrive Standalone Update Task" - "OneDrive Startup Task" + "OneDrive Standalone Update Task", + "OneDrive Startup Task", "CreateExplorerShellUnelevatedTask" ) $ignoreUsers = @( diff --git a/scripts_staging/Checks/Windows Services.ps1 b/scripts_staging/Checks/Windows Services.ps1 index 1b5c145a..83db3abf 100644 --- a/scripts_staging/Checks/Windows Services.ps1 +++ b/scripts_staging/Checks/Windows Services.ps1 @@ -28,7 +28,7 @@ 21.01.25 SAN Code cleanup 27.03.25 SAN added kerberos local key to default 31.03.25 SAN Added a new patern for ignroring user services (servicename_XXX) while keeping their system counterpart inculded - + 17.07.25 SAN Added InventorySvc it is expected to randomly turn on and off #> @@ -63,7 +63,8 @@ $ignoredByDefault = @( "AGSService", "ShellHWDetection", # Frequently failing; unclear if actionable "DropboxUpdater", - "LocalKDC" # https://learn.microsoft.com/en-us/answers/questions/2136070/windows-server-2025-kerberos-local-key-distributio + "LocalKDC", # https://learn.microsoft.com/en-us/answers/questions/2136070/windows-server-2025-kerberos-local-key-distributio + "InventorySvc" #https://learn.microsoft.com/en-us/answers/questions/2258983/inventory-and-compatibility-appraisal-service-in-m ) # Define a list of services to ignore that match the pattern "nameoftheservice_xxxx" diff --git a/scripts_staging/Tools/Activate windows with KMS.ps1 b/scripts_staging/Tools/Activate windows with KMS.ps1 index 49bf0f79..8048f82b 100644 --- a/scripts_staging/Tools/Activate windows with KMS.ps1 +++ b/scripts_staging/Tools/Activate windows with KMS.ps1 @@ -16,29 +16,46 @@ .CHANGELOG 11.12.24 SAN Code Cleanup + 16.07.25 SAN added network check + check if activation is successful .TODO Convert the script to use the PowerShell module as the future of vbs is uncertain. see code bellow for prototype #> - - - - # Check if the 'kms_server' environment variable exists if (-not $env:kms_server) { Write-Host "The 'kms_server' environment variable is not set." exit 1 } -$kmsServer = $env:kms_server +# Parse kms_server into host and optional port (defaults to 1688 if not provided) +$rawKmsServer = $env:kms_server +$kmsParts = $rawKmsServer -split ":", 2 +$kmsHost = $kmsParts[0] +$kmsPort = if ($kmsParts.Count -eq 2 -and $kmsParts[1]) { $kmsParts[1] } else { "1688" } + +Write-Host "Parsed KMS server: Host = $kmsHost, Port = $kmsPort" + +# Test TCP connectivity to the KMS server +Write-Host "Testing network connectivity to $kmsHost on port $kmsPort..." +try { + $testResult = Test-NetConnection -ComputerName $kmsHost -Port ([int]$kmsPort) -WarningAction SilentlyContinue + if (-not $testResult.TcpTestSucceeded) { + Write-Host "Network test failed. Cannot reach $kmsHost on port $kmsPort." + exit 1 + } + Write-Host "Network test passed. KMS server is reachable." +} catch { + Write-Host "An error occurred during network test: $_" + exit 1 +} # Set KMS server -Write-Host "Setting KMS server to: $kmsServer..." +Write-Host "Setting KMS server to: $rawKmsServer..." try { - Start-Process -FilePath "cscript.exe" -ArgumentList "$env:SystemRoot\System32\slmgr.vbs /skms $kmsServer" -NoNewWindow -Wait -ErrorAction Stop - Write-Host "Successfully set KMS server to: $kmsServer" + Start-Process -FilePath "cscript.exe" -ArgumentList "$env:SystemRoot\System32\slmgr.vbs /skms $rawKmsServer" -NoNewWindow -Wait -ErrorAction Stop + Write-Host "Successfully set KMS server." } catch { Write-Host "Failed to set KMS server. Error: $_" exit 1 @@ -48,13 +65,31 @@ try { Write-Host "Activating Windows..." try { Start-Process -FilePath "cscript.exe" -ArgumentList "$env:SystemRoot\System32\slmgr.vbs /ato" -NoNewWindow -Wait -ErrorAction Stop - Write-Host "Windows activation process complete." + Write-Host "Activation command sent." +} catch { + Write-Host "Windows activation command failed. Error: $_" + exit 1 +} + +# Check Windows activation status +Write-Host "Checking Windows activation status..." +try { + $licenseStatus = (Get-CimInstance -Query "SELECT LicenseStatus FROM SoftwareLicensingProduct WHERE PartialProductKey IS NOT NULL AND LicenseStatus = 1").LicenseStatus + if ($licenseStatus -eq 1) { + Write-Host "Windows is activated." + exit 0 + } else { + Write-Host "Windows is NOT activated." + exit 1 + } } catch { - Write-Host "Windows activation failed. Error: $_" + Write-Host "Failed to check activation status. Error: $_" exit 1 } + + <# # Check if the environment variable 'kms_server' exists diff --git a/scripts_staging/Tools/Deploy diagnostic toolkit.ps1 b/scripts_staging/Tools/Deploy diagnostic toolkit.ps1 index 05c0cdfc..428a1ddb 100644 --- a/scripts_staging/Tools/Deploy diagnostic toolkit.ps1 +++ b/scripts_staging/Tools/Deploy diagnostic toolkit.ps1 @@ -30,10 +30,12 @@ if ($uninstall -eq "1") { Write-Host "Start uninstall" choco uninstall sysinternals -y choco uninstall nirlauncher -y + choco uninstall powertoys -y } else { Write-Host "Start install" choco install sysinternals -y --ignore-checksums --no-progress --force choco install nirlauncher -y --package-parameters="/Sysinternals" --no-progress --force + choco install powertoys -y --no-progress --force Write-Host "Launcher available at C:\tools\NirLauncher" } \ No newline at end of file