From f4a433d0bdc1a6560fb0473060392549bb101e0c Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:10:48 +0000 Subject: [PATCH 01/43] Update ./scripts/Tasks/Import RD Gateway Cert From IIS.ps1 --- .../Tasks/Import RD Gateway Cert From IIS.ps1 | 302 ++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 scripts_staging/Tasks/Import RD Gateway Cert From IIS.ps1 diff --git a/scripts_staging/Tasks/Import RD Gateway Cert From IIS.ps1 b/scripts_staging/Tasks/Import RD Gateway Cert From IIS.ps1 new file mode 100644 index 00000000..a467b024 --- /dev/null +++ b/scripts_staging/Tasks/Import RD Gateway Cert From IIS.ps1 @@ -0,0 +1,302 @@ +<# +.SYNOPSIS +Configures the RD Gateway SSL certificate and checks settings for the "win-acme" task. + +.DESCRIPTION +This script checks if the RD Gateway service and a task with "win-acme" in its name exist. +It also verifies and updates the "settings.json" file to ensure PrivateKeyExportable is set to true. +If all conditions are met, it imports a new SSL certificate to the RD Gateway. +Optionally, it can run the win-acme (wacs.exe) command to install a Let's Encrypt certificate. + +.PARAMETER settingsJsonPath +Specifies the path to the settings.json file. Default is "C:\tools\win-acme\settings.json". the default location of instalation of Win-Acme by chocolatey. + +.PARAMETER InstallLE +Specifies whether to install a Let's Encrypt certificate using win-acme. Default is false. + +.PARAMETER RDSURL +Specifies what url will be set in the bindings when installLE is called + +.PARAMETER ForceReplaceCertRDS +ignore the fail-safe checks and force the replacement of rds certs and restart the gateway + +.EXEMPLE +-settingsJsonPath "C:\tools\win-acme\settings.json" +-RDSURL {{agent.RDSURL}} +-ForceReplaceCertRDS +-InstallLE + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + 28/08/24 SAN Added a deletion of old cert when changes happens (this may not be possible with the TODO planned and would require a scraping of the idea) + 02/09/24 SAN Full re-write of the cert management to make it smart, it will not restart the service if no change is needed or fore re-write of the cert and also change logic for deleting old certs + 02/09/24 SAN Legacy Code cleanup + 02/09/24 SAN added -ForceReplaceCertRDS and a couple of fail-safe + 03/09/24 SAN added choco install to install section + 04/09/24 SAN corrected logic for deployement and force + + +.TODO + find a way to call the script from the renew -script process of win-acme + for referance: C:\tools\win-acme\wacs.exe --source iis --verbose --siteid 1 --commonname $RDSURL --installation iis --installationsiteid 1 --script "C:\tools\win-acme\Scripts\ImportRDGateway.ps1" --scriptparameters '{CertThumbprint}' + change pathing based on folder for both .json and .exe + better way than calling iis 0 for the change ? probably possible if called from -script +#> +param ( + [string]$settingsJsonPath = "C:\tools\win-acme\settings.json", + [switch]$InstallLE, + [string]$RDSURL, + [switch]$ForceReplaceCertRDS +) + +Function InstallLetEncryptCertificate { + choco install win-acme + $wacsCommand = "C:\tools\win-acme\wacs.exe --source iis --siteid 1 --commonname $RDSURL --installation iis --installationsiteid 1" + Write-Host "Executing command: $wacsCommand" + try { + Invoke-Expression $wacsCommand + } catch { + Write-Error "Failed to execute win-acme command. Error: $_" + exit 1 + } +} + +Function BindRDSURL { + if ($RDSURL) { + Write-Host "Binding RDSURL to HTTPS of the default IIS site..." + try { + New-WebBinding -Name "Default Web Site" -IPAddress "*" -Port 443 -HostHeader $RDSURL -Protocol "https" + Write-Host "RDSURL bound to HTTPS of the default IIS site." + } catch { + Write-Error "Failed to bind RDSURL. Error: $_" + } + } else { + Write-Warning "RDSURL is not provided. Skipping binding process." + } +} + +Function Get-RDGatewaySSLCertificateThumbprint { + param ( + [string]$Path = 'RDS:\GatewayServer\SSLCertificate\Thumbprint' + ) + + try { + $thumbprintValue = (Get-Item -Path $Path).CurrentValue + if ([string]::IsNullOrWhiteSpace($thumbprintValue)) { + return $null + } else { + return $thumbprintValue + } + } catch { + Write-Error "An error occurred while retrieving the SSL certificate thumbprint: $_" + return $null + } +} + +function Is-ValidThumbprint { + param ( + [string]$Thumbprint + ) + return $Thumbprint -and $Thumbprint.Length -eq 40 -and $Thumbprint -match '^[0-9A-Fa-f]+$' +} + +Function Remove-OldCertificates { + param ( + [string]$OldThumbprint + ) + + if (-not $OldThumbprint) { + Write-Warning "Old thumbprint is not provided. Skipping certificate removal process." + return $false + } + + $stores = @( + "Cert:\LocalMachine\My", + "Cert:\LocalMachine\WebHosting", + "Cert:\LocalMachine\Remote Desktop" + ) + + $certsRemoved = $false + + foreach ($store in $stores) { + try { + $certs = Get-ChildItem -Path $store -Recurse | Where-Object {$_.Thumbprint -eq $OldThumbprint} + if ($certs.Count -eq 0) { + Write-Host "No certificates with thumbprint $OldThumbprint found in $store." + } else { + foreach ($cert in $certs) { + Remove-Item -Path $cert.PSPath -Confirm:$false + Write-Host "Removed certificate with thumbprint $OldThumbprint from $store." + $certsRemoved = $true + } + } + } catch { + Write-Error "Failed to remove certificates from $store. Error: $_" + } + } + + return $certsRemoved +} + +# Check if Get-RDUserSession is available, if not exit with code 0 +try { + $null = Get-RDUserSession -ErrorAction Stop +} +catch { + if ($_.Exception.Message -match "A Remote Desktop Services deployment does not exist") { + Write-Output "Remote Desktop Services deployment does not exist. Exiting." + exit 0 + } + else { + Write-Output "An unexpected error occurred while checking for RDS deployment." + Write-Output "Error: $($_.Exception.Message)" + exit 0 + } +} + +# Check if settings.json file exists +if (-not $InstallLE.IsPresent -and -not (Test-Path $settingsJsonPath)) { + Write-Host "settings.json not found. EXIT" + exit 1 +} + +# Install Let's Encrypt certificate if InstallLE is set to true +if ($InstallLE) { + if (-not $RDSURL) { + Write-Error "RDSURL is required when InstallLE is true. Exiting script." + exit 1 + } + + BindRDSURL + InstallLetEncryptCertificate +} + +# Check if PrivateKeyExportable is set to true in settings.json +$settingsJson = Get-Content -Path $settingsJsonPath -Raw | ConvertFrom-Json +$privateKeyExportable = $settingsJson.Store.CertificateStore.PrivateKeyExportable + +if (-not $privateKeyExportable) { + $settingsJson.Store.CertificateStore.PrivateKeyExportable = $true + try { + $settingsJson | ConvertTo-Json | Set-Content -Path $settingsJsonPath + Write-Host "PrivateKeyExportable set to true in settings.json" + } catch { + Write-Error "Failed to update settings.json. Error: $_" + exit 1 + } +} + +# Check if the RD Gateway service exists +$gatewayService = Get-Service -Name TSGateway -ErrorAction SilentlyContinue +# Check if a task with "win-acme" in its name exists +$winAcmeTask = Get-ScheduledTask -TaskName "*win-acme*" -ErrorAction SilentlyContinue + +if ($gatewayService -and $winAcmeTask) { + Import-Module RemoteDesktopServices + Import-Module WebAdministration + + # Retrieve thumbprints currents + $IISCertThumbprint = (Get-ChildItem IIS:SSLBindings)[0].Thumbprint + $RDSCertThumbprint = Get-RDGatewaySSLCertificateThumbprint + + if (-not $ForceReplaceCertRDS) { + if ($RDSCertThumbprint -eq $IISCertThumbprint) { + Write-Host "The RD Gateway SSL certificate is already the same as IIS. No replacement needed." + exit 0 + } + + # Validate IIS certificate thumbprint + if (Is-ValidThumbprint -Thumbprint $IISCertThumbprint) { + Write-Host "IIS certificate thumbprint $IISCertThumbprint is valid. Continuing." + } else { + Write-Error "Invalid IIS certificate thumbprint: $IISCertThumbprint. Exiting script." + exit 1 + } +<# + # Validate RD Gateway certificate thumbprint + if (Is-ValidThumbprint -Thumbprint $RDSCertThumbprint) { + Write-Host "RD Gateway certificate thumbprint $RDSCertThumbprint is valid. Continuing." + } else { + Write-Error "Invalid RD Gateway certificate thumbprint: $RDSCertThumbprint. Exiting script." + exit 1 + }#> + } + + # Retrieve the certificate from the local machine store that matches the specified thumbprint + $CertInStore = Get-ChildItem -Path Cert:\LocalMachine -Recurse | Where-Object {$_.Thumbprint -eq $IISCertThumbprint} | Sort-Object -Descending | Select-Object -First 1 + + if ($CertInStore) { + try { + # Check if the certificate is not already in the 'LocalMachine\My' store + if ($CertInStore.PSPath -notlike "*LocalMachine\My\*") { + # The certificate is not in the 'My' store, so we will move it there + $SourceStoreScope = 'LocalMachine' + $SourceStorename = $CertInStore.PSParentPath.Split("\")[-1] + + Write-Host "Certificate found in '$SourceStorename' store. Moving it to 'LocalMachine\My'." + + # Open the source certificate store (Read-Only) + $SourceStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $SourceStorename, $SourceStoreScope + $SourceStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) + + # Retrieve the certificate from the source store + $cert = $SourceStore.Certificates | Where-Object {$_.Thumbprint -eq $CertInStore.Thumbprint} + + # Define the destination store ('My') and open it (Read-Write) + $DestStoreScope = 'LocalMachine' + $DestStoreName = 'My' + $DestStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $DestStoreName, $DestStoreScope + $DestStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + + # Add the certificate to the destination store + $DestStore.Add($cert) + Write-Host "Certificate successfully added to 'LocalMachine\My'." + + # Close both stores + $SourceStore.Close() + $DestStore.Close() + + # Update the $CertInStore variable to reference the newly moved certificate + $CertInStore = Get-ChildItem -Path Cert:\LocalMachine\My -Recurse | Where-Object {$_.Thumbprint -eq $IISCertThumbprint} | Sort-Object -Descending | Select-Object -First 1 + } else { + Write-Host "Certificate is already in the 'LocalMachine\My' store." + } + + # Set the certificate thumbprint in the RD Gateway listener + Set-Item -Path RDS:\GatewayServer\SSLCertificate\Thumbprint -Value $CertInStore.Thumbprint -ErrorAction Stop + Write-Host "RD Gateway listener thumbprint set to the new certificate." + + # Restart the Terminal Services Gateway service to apply the new certificate + Restart-Service TSGateway -Force -ErrorAction Stop + Write-Host "TSGateway service restarted successfully." + + # Call function to remove old certificates (assumes function is defined elsewhere) + $certsRemoved = Remove-OldCertificates -OldThumbprint $RDSCertThumbprint + + # Check if old certificates were removed + if (-not $certsRemoved) { + Write-Error "No old certificates were removed. Exiting script." + exit 1 + } else { + Write-Host "Old certificates removed successfully." + } + + } catch { + # Handle any errors that occurred during the process + Write-Error "Failed to set certificate thumbprint or restart the service. Error: $_" + exit 1 + } + } else { + # Certificate with the specified thumbprint was not found in the certificate store + Write-Error "Certificate with thumbprint '$IISCertThumbprint' not found in the certificate store." + exit 1 + } +} elseif (-not $gatewayService) { + Write-Error "RD Gateway service not found." +} elseif (-not $winAcmeTask) { + Write-Error "Task with 'win-acme' not found." +} \ No newline at end of file From 74eba5d10fe129ffa230f00334aa67c388efcce0 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:23:10 +0000 Subject: [PATCH 02/43] Update ./scripts/Tasks/Kill Switch Manager.ps1 --- scripts_staging/Tasks/Kill Switch Manager.ps1 | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 scripts_staging/Tasks/Kill Switch Manager.ps1 diff --git a/scripts_staging/Tasks/Kill Switch Manager.ps1 b/scripts_staging/Tasks/Kill Switch Manager.ps1 new file mode 100644 index 00000000..03d0c99d --- /dev/null +++ b/scripts_staging/Tasks/Kill Switch Manager.ps1 @@ -0,0 +1,124 @@ +<# +.SYNOPSIS + A PowerShell script to implement a kill switch mechanism for Tactical RMM using scheduled tasks and DNS TXT records. + +.DESCRIPTION + This script sets up a kill switch by creating a scheduled task that runs hourly. + It checks DNS TXT records for specific flags (`stop=true` or `uninstall=true`) and executes corresponding actions like stopping services or uninstalling Tactical RMM. + The script is designed as a safeguard in case the RMM system behaves unexpectedly or goes rogue, allowing administrators to disable or uninstall it remotely and securely. + +.PARAMETER killswitchdomain + The domain used to resolve the DNS TXT records containing kill switch flags. + This can be specified through the environment variable `killswitchdomain`. + +.PARAMETER companyfolder + The folder path where the script file (`RMM_Kill_Switch.ps1`) will be saved. + This can be specified through the environment variable `companyfolder`. + +.EXAMPLE + $env:killswitchdomain="example.com" + $env:companyfolder="C:\CompanyFolder" + + Run the script to set up the kill switch for Tactical RMM. + +.NOTES + Author: SAN + Date: ??? + #public + +.CHANGELOG + + +.TODO + Integrate this script into the deployment process. + Add global var to var +#> + + + +# Retrieve the domain and path from environment variables +$domain = [System.Environment]::GetEnvironmentVariable('killswitchdomain') +$envVar = [System.Environment]::GetEnvironmentVariable('companyfolder') + +if (-not $domain) { + Write-Host "Environment variable 'killswitchdomain' not found." + exit 1 +} + +if (-not $envVar) { + Write-Host "Environment variable 'companyfolder' not found." + exit 1 +} + +$scriptPath = Join-Path -Path $envVar -ChildPath "RMM_Kill_Switch.ps1" +$taskName = "RMM_Kill_Switch" + +# Delete the existing task if it exists +Unregister-ScheduledTask -TaskName $taskName -Confirm:$false + +# Script content to save in the file +$scriptContent = @" +# Function to execute the stop branch +function ExecuteStopBranch { + # Stop Service name: tacticalrmm + Stop-Service -Name "tacticalrmm" -Force + + # Kill all tacticalrmm.exe processes + Get-Process -Name "tacticalrmm" | Stop-Process -Force + + # Stop Service name: Mesh Agent + Stop-Service -Name "Mesh Agent" -Force + + # Kill all MeshAgent.exe processes + Get-Process -Name "MeshAgent" | Stop-Process -Force +} + +# Function to execute the uninstall branch +function ExecuteUninstallBranch { + # Execute the uninstall command silently + #Start-Process -FilePath "C:\Program Files\TacticalAgent\unins000.exe" -ArgumentList "/VERYSILENT" -Wait +} + +# Resolve the TXT record +\$record = Resolve-DnsName -Name "$domain" -Type "TXT" + +# Check if the record was found +if (\$record) { + \$txtData = \$record | Select-Object -ExpandProperty Strings + \$foundStop = \$txtData -match "stop=true" + \$foundUninstall = \$txtData -match "uninstall=true" + + if (-not \$foundStop -and -not \$foundUninstall) { + # Neither stop=true nor uninstall=true found + Write-Host "Neither 'stop=true' nor 'uninstall=true' found in the TXT record for $domain." + # Add your code for the default case here + } + elseif (\$foundStop) { + # Branch for stop=true + ExecuteStopBranch + } + elseif (\$foundUninstall) { + # Branch for uninstall=true + ExecuteUninstallBranch + } +} else { + Write-Host "TXT record for $domain not found." +} +"@ + +# Save the script content to the file +$scriptContent | Out-File -FilePath $scriptPath -Encoding UTF8 -Force + +# Create a scheduled task to run the script hourly and daily +$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" + +# Specify hourly triggers for 24 hours with random minutes +$triggers = @() +for ($hour = 0; $hour -lt 24; $hour++) { + $randomMinutes = Get-Random -Minimum 0 -Maximum 59 + $triggerHourly = New-ScheduledTaskTrigger -At (Get-Date).AddHours($hour).AddMinutes($randomMinutes) -Daily + $triggers += $triggerHourly +} + +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries +Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $triggers -Settings $settings -Description "Task to run the Tactical RMM Kill Switch script hourly and daily." -User "SYSTEM" \ No newline at end of file From 08164737f16e1cb28a988b360101aca1ee559a0e Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:29:01 +0000 Subject: [PATCH 03/43] Update ./scripts/Checks/Internet uplink.ps1 --- scripts_staging/Checks/Internet uplink.ps1 | 88 ++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 scripts_staging/Checks/Internet uplink.ps1 diff --git a/scripts_staging/Checks/Internet uplink.ps1 b/scripts_staging/Checks/Internet uplink.ps1 new file mode 100644 index 00000000..c4eeef8e --- /dev/null +++ b/scripts_staging/Checks/Internet uplink.ps1 @@ -0,0 +1,88 @@ +<# +.SYNOPSIS + Tests connectivity to a predefined list of IP addresses either randomly or all at once. + +.DESCRIPTION + This script checks network connectivity by pinging a list of predefined IP addresses. + The user can choose to test all the IP addresses or a randomly selected one. + If a ping fails, the script exits with a status code of 1. + +.PARAMETER TestAll + A switch parameter to test all IP addresses in the list. + If not specified, the script selects a random IP address for testing. + +.EXAMPLE + -TestAll + +.NOTES + Author: SAN + Date: ??? + #public + +.CHANGELOG + +.TODO + Include customizable input for the list of IP addresses. + Enhance error handling for unreachable hosts. + for test all to env + tnc has some relability issue maybe use normal ping +#> + + +param ( + [switch]$TestAll +) + +# List of IP addresses with their respective owners +$ipAddresses = @( + @{ IP="8.8.8.8"; Owner="Google DNS" }, + @{ IP="8.8.4.4"; Owner="Google DNS" }, + @{ IP="1.1.1.1"; Owner="Cloudflare DNS" }, + @{ IP="1.0.0.1"; Owner="Cloudflare DNS" }, + @{ IP="208.67.222.222"; Owner="OpenDNS" }, + @{ IP="208.67.220.220"; Owner="OpenDNS" }, + @{ IP="9.9.9.9"; Owner="Quad9 DNS" }, + @{ IP="149.112.112.112"; Owner="Quad9 DNS" }, + @{ IP="13.107.42.14"; Owner="Microsoft Azure" }, + @{ IP="20.190.160.1"; Owner="Microsoft Azure" }, + @{ IP="54.239.28.85"; Owner="Amazon AWS" }, + @{ IP="205.251.242.103"; Owner="Amazon AWS" } +) + +$pingFailed = $false + +if ($TestAll) { + # Test all IP addresses + foreach ($entry in $ipAddresses) { + $ip = $entry.IP + $owner = $entry.Owner + $pingResult = Test-Connection -ComputerName $ip -Count 1 -Quiet + + if (-not $pingResult) { + Write-Host "Ping to $ip ($owner) failed." + $pingFailed = $true + } else { + Write-Host "Ping to $ip ($owner) succeeded." + } + } + + if ($pingFailed) { + exit 1 + } +} else { + # Randomly select an IP address + $randomEntry = $ipAddresses | Get-Random + $randomIp = $randomEntry.IP + $owner = $randomEntry.Owner + + # Ping the selected IP address + $pingResult = Test-Connection -ComputerName $randomIp -Count 1 -Quiet + + # Check the result of the ping and exit with status code 1 if it fails + if (-not $pingResult) { + Write-Host "Ping to $randomIp ($owner) failed." + exit 1 + } else { + Write-Host "Ping to $randomIp ($owner) succeeded." + } +} \ No newline at end of file From a4c1577b3f7a6136378f17f7bf40bd328c1f7c43 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:29:11 +0000 Subject: [PATCH 04/43] Update ./scripts/Tools/Activate windows with KMS.ps1 --- .../Tools/Activate windows with KMS.ps1 | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 scripts_staging/Tools/Activate windows with KMS.ps1 diff --git a/scripts_staging/Tools/Activate windows with KMS.ps1 b/scripts_staging/Tools/Activate windows with KMS.ps1 new file mode 100644 index 00000000..7f8de58c --- /dev/null +++ b/scripts_staging/Tools/Activate windows with KMS.ps1 @@ -0,0 +1,91 @@ +<# +.SYNOPSIS + Script to activate Windows using a KMS server, with support for specifying the server and port via an environment variable. + +.DESCRIPTION + This script checks for the presence of the `kms_server` environment variable. If found, it sets the KMS server and initiates the Windows activation process using the specified server and port. If the `kms_server` is not set, the script prompts the user to set it. + +.EXAMPLE + kms_server=kms.example.com:1688 + kms_server=host:port + +.NOTES + Author: SAN + Date: ??? + #public + +.CHANGELOG + 11.12.24 SAN Code Cleanup + +.TODO + Convert the script to use the PowerShell module as the future of vbs is uncertain. +#> + + + + +# 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 + +# Set KMS server +Write-Host "Setting KMS server to: $kmsServer..." +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" +} catch { + Write-Host "Failed to set KMS server. Error: $_" + exit 1 +} + +# Activate Windows +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." +} catch { + Write-Host "Windows activation failed. Error: $_" + exit 1 +} + + +<# + +# Check if the environment variable 'kms_server' exists +if ($env:kms_server) { + # Extract the KMS server address and port + $kmsServerInfo = $env:kms_server + $kmsServerParts = $kmsServerInfo -split ':' + + if ($kmsServerParts.Length -eq 2) { + $kmsServer = $kmsServerParts[0] + $kmsPort = $kmsServerParts[1] + Write-Host "Found 'kms_server' environment variable: $kmsServer:$kmsPort" + + # Install the slmgr-ps module if it's not already installed + if (-not (Get-Module -ListAvailable -Name slmgr-ps)) { + Write-Host "Installing slmgr-ps module..." + Install-Module -Name slmgr-ps -Force -AllowClobber + } + + # Import the module + Import-Module -Name slmgr-ps -Force + + # Activate Windows using the KMS server and port extracted + Write-Host "Activating Windows with KMS server: $kmsServer and port: $kmsPort" + Start-WindowsActivation -KMSServerFQDN $kmsServer -KMSServerPort $kmsPort + + Write-Host "Windows activation process initiated." + } else { + Write-Host "Invalid 'kms_server' format. It should be in the form 'server:port'." + } +} else { + Write-Host "The 'kms_server' environment variable is not set." + Write-Host "Please set the 'kms_server' variable before running the script." +} + +#> \ No newline at end of file From cd1bf87b3df55a490d4f1bc3bb05f8ec775b3908 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:35:19 +0000 Subject: [PATCH 05/43] Update ./scripts/Fixes/Bluescreen report.ps1 --- scripts_staging/Fixes/Bluescreen report.ps1 | 103 ++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 scripts_staging/Fixes/Bluescreen report.ps1 diff --git a/scripts_staging/Fixes/Bluescreen report.ps1 b/scripts_staging/Fixes/Bluescreen report.ps1 new file mode 100644 index 00000000..609307b6 --- /dev/null +++ b/scripts_staging/Fixes/Bluescreen report.ps1 @@ -0,0 +1,103 @@ +<# +.SYNOPSIS + This script automates the process of installing Bluescreen Viewer, running it to generate a crash log, and uploading Minidump files to a Nextcloud WebDAV server. + +.DESCRIPTION + The script installs Bluescreen Viewer using Chocolatey, runs it to generate a crash log, and displays the log in the terminal. + It then checks the local Minidump folder for dump files, uploads them to a specified Nextcloud WebDAV URL, and renames them with a "_sent" suffix after a successful upload. + +.EXEMPLE + NEXTCLOUD_WEBDAV_URL=https://nextcloud.XYZ.AB/public.php/webdav/ + NEXTCLOUD_TOKEN=SHARETOKEN + +.NOTES + Author: SAN + Date: 02.12.24 + Dependencies: Chocolatey, Nextcloud public share + #PUBLIC + +.CHANGELOG + +#> + +# Step 1: Retrieve Nextcloud WebDAV URL and Token from environment variables +$nextcloudWebdavUrl = [System.Environment]::GetEnvironmentVariable("NEXTCLOUD_WEBDAV_URL") +$webdavUser = [System.Environment]::GetEnvironmentVariable("NEXTCLOUD_TOKEN") + +# Exit the script if the Nextcloud WebDAV URL or token is not provided +if (-not $nextcloudWebdavUrl -or -not $webdavUser) { + Write-Host "Error: Nextcloud WebDAV URL or token is not provided in environment variables." + exit 1 +} + +# Variables (defined at the top for easy configuration) +$minidumpPath = "C:\Windows\Minidump" # Path to Minidump folder +$hostname = (Get-WmiObject -Class Win32_ComputerSystem).Name # Get the system hostname + +# Bluescreen Viewer Installation Variables +$bluescreenViewerPath = "C:\Program Files (x86)\NirSoft\BlueScreenView\BlueScreenView.exe" +$bluescreenLogFile = "$env:temp\bluescreen_log.txt" # Path to save Bluescreen log file + +# Force TLS 1.2 for secure connection when uploading to WebDAV +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 + +# Step 2: Install Bluescreen Viewer using Chocolatey (silent installation) +Write-Host "Installing Bluescreen Viewer..." +choco install bluescreenview -y --no-progress | Out-Null +if ($LASTEXITCODE -ne 0) { + Write-Host "Installation failed. Continuing with script." +} + +# Step 3: Run Bluescreen Viewer to generate the crash log and save it to a text file +Write-Host "Running Bluescreen Viewer to generate crash log..." +Start-Process $bluescreenViewerPath -ArgumentList "/stext $bluescreenLogFile" -Wait +if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to run Bluescreen Viewer. Continuing with script." +} + +# Step 4: Output the content of the crash log file to the terminal +Write-Host "Displaying crash logs..." +if (Test-Path $bluescreenLogFile) { + Get-Content $bluescreenLogFile +} else { + Write-Host "The crash log file does not exist." +} + +# Step 5: Check if the Minidump directory exists and process the files +if (Test-Path $minidumpPath) { + # Get all files in the Minidump directory, excluding those already marked as "_sent" + $files = Get-ChildItem -Path $minidumpPath | Where-Object { $_.Name -notlike "*_sent*" } + + # Step 6: Loop through each Minidump file and upload to Nextcloud WebDAV + foreach ($file in $files) { + # Construct a new file name with the hostname at the beginning + $newFileName = "$hostname" + "_" + $file.Name + $uploadUrl = $nextcloudWebdavUrl + $newFileName # Construct WebDAV URL for each file + + # Step 7: Prepare the authorization header for WebDAV (no password) + $headers = @{ + "Authorization" = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${webdavUser}:")))" + "X-Requested-With" = "XMLHttpRequest" + } + + # Step 8: Upload the file to Nextcloud WebDAV + try { + Write-Host "Uploading $($file.Name) to $uploadUrl..." + Invoke-WebRequest -Uri $uploadUrl -Method Put -InFile $file.FullName -Headers $headers -UseBasicParsing + Write-Host "Successfully uploaded $newFileName" + + # Step 9: Rename the file by appending "_sent" after successful upload + $fileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($file.Name) + $fileExtension = [System.IO.Path]::GetExtension($file.Name) + $newSentName = "$fileBaseName`_sent$fileExtension" + + # Step 10: Rename the file to indicate it has been successfully sent + Rename-Item -Path $file.FullName -NewName $newSentName + Write-Host "Renamed $($file.Name) to $newSentName" + } catch { + Write-Host "Failed to upload $($file.Name): $_" + } + } +} else { + Write-Host "Minidump folder not found!" +} From a36714f271ee33fc85fe4a39ea228a9e591a3a67 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:35:21 +0000 Subject: [PATCH 06/43] Update ./scripts/Fixes/Fix broken ESET installation.ps1 --- .../Fixes/Fix broken ESET installation.ps1 | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 scripts_staging/Fixes/Fix broken ESET installation.ps1 diff --git a/scripts_staging/Fixes/Fix broken ESET installation.ps1 b/scripts_staging/Fixes/Fix broken ESET installation.ps1 new file mode 100644 index 00000000..ac033a83 --- /dev/null +++ b/scripts_staging/Fixes/Fix broken ESET installation.ps1 @@ -0,0 +1,81 @@ +<# +.SYNOPSIS + This PowerShell script is designed to fix a broken installation of ESET Security that generaly happens after an update. + +.DESCRIPTION + The script performs the following actions: + 1. Deletes the ESET Security directory in Program Files. + 2. Deletes the ESET Security directory in ProgramData. + 3. Stops and deletes the ekrn service. + 4. Deletes the registry key for the ekrn service. + +.EXEMPLE + force_execution=true + +.NOTES + Author: SAN + Date: 19.08.24 + #public + +.CHANGELOG + + +#> + +# Check if the environment variable 'force_execution' is set to 'true' +if ($env:force_execution -eq 'true') { + Write-Host "Force execution is enabled. Skipping file existence check." +} else { + # Only check if the file exists if force_execution is not enabled + if (Test-Path "C:\Program Files\ESET\ESET Security\ermm.exe") { + Write-Error "Error: The file 'ermm.exe' exists at the specified path. This script may not work as expected." + exit 1 + } else { + Write-Host "The file 'ermm.exe' does not exist. The script can proceed." + } +} + + +Write-Host "Fixing broken installation of ESET Security..." + +# Define paths and service name +$ESET_PROG_FILES_DIR = "C:\Program Files\ESET\ESET Security" +$ESET_PROG_DATA_DIR = "C:\ProgramData\ESET\ESET Security" +$serviceName = "ekrn" +$registryPath = "HKLM:\SYSTEM\CurrentControlSet\Services\ekrn" + +# Delete the ESET Security directory in Program Files +if (Test-Path $ESET_PROG_FILES_DIR) { + Write-Host "Deleting directory: $ESET_PROG_FILES_DIR" + Remove-Item -Recurse -Force $ESET_PROG_FILES_DIR +} else { + Write-Host "Directory not found: $ESET_PROG_FILES_DIR" +} + +# Delete the ESET Security directory in ProgramData +if (Test-Path $ESET_PROG_DATA_DIR) { + Write-Host "Deleting directory: $ESET_PROG_DATA_DIR" + Remove-Item -Recurse -Force $ESET_PROG_DATA_DIR +} else { + Write-Host "Directory not found: $ESET_PROG_DATA_DIR" +} + +# Stop and delete the ekrn service +if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) { + Write-Host "Stopping service: $serviceName" + Stop-Service -Name $serviceName -Force + Write-Host "Deleting service: $serviceName" + Remove-Service -Name $serviceName -Force +} else { + Write-Host "Service not found: $serviceName" +} + +# Delete the registry key +if (Test-Path $registryPath) { + Write-Host "Deleting registry key: $registryPath" + Remove-Item -Path $registryPath -Recurse -Force +} else { + Write-Host "Registry key not found: $registryPath" +} + +Write-Host "Operation completed." \ No newline at end of file From 8179023cdb3b95561bade4e8a572c06212f67b92 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:43:17 +0000 Subject: [PATCH 07/43] Update ./scripts/Checks/Maximum UpTime.ps1 --- scripts_staging/Checks/Maximum UpTime.ps1 | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 scripts_staging/Checks/Maximum UpTime.ps1 diff --git a/scripts_staging/Checks/Maximum UpTime.ps1 b/scripts_staging/Checks/Maximum UpTime.ps1 new file mode 100644 index 00000000..2cb527cc --- /dev/null +++ b/scripts_staging/Checks/Maximum UpTime.ps1 @@ -0,0 +1,46 @@ +<# +.SYNOPSIS + This script calculates the uptime of a computer and compares it to a specified maximum time. + +.DESCRIPTION + The script retrieves the LastBootUpTime of the computer and calculates the current uptime. + If the uptime exceeds the maximum time, the script exits with an exit code of 1. + If the uptime is within the allowed range, the script exits with an exit code of 0. + +.PARAMETER MaxTime + Specifies the maximum allowed uptime in days. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.TODO + move var to env + +.CHANGELOG + +#> + +param ( + [Parameter(Mandatory = $true, HelpMessage = "Specify the maximum allowed uptime in days.")] + [int]$MaxTime +) + +# Calculate the uptime +$uptime = (Get-Date) - (Get-CimInstance -Class Win32_OperatingSystem).LastBootUpTime +$uptimeDays = $uptime.Days +$uptimeTimeSpan = $uptime.ToString("hh\:mm\:ss") +$formattedUptime = "{0} days, {1}" -f $uptimeDays, $uptimeTimeSpan + +# Compare the uptime with the maximum time +if ($uptimeDays -gt $MaxTime) { + Write-Output "The computer has an uptime of $formattedUptime." + Write-Output "The computer has an uptime greater than $MaxTime days." + exit 1 +} else { + Write-Output "Uptime OK" + #Write-Output "The computer has an uptime of $formattedUptime." + #Write-Output "The computer has an uptime lower than $MaxTime days." + exit 0 +} \ No newline at end of file From 4ef08b7c8a815603718936bb82360756c4b042f3 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:43:22 +0000 Subject: [PATCH 08/43] Update ./scripts/Fixes/RDS Fix taskbar.ps1 --- scripts_staging/Fixes/RDS Fix taskbar.ps1 | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 scripts_staging/Fixes/RDS Fix taskbar.ps1 diff --git a/scripts_staging/Fixes/RDS Fix taskbar.ps1 b/scripts_staging/Fixes/RDS Fix taskbar.ps1 new file mode 100644 index 00000000..1c416f69 --- /dev/null +++ b/scripts_staging/Fixes/RDS Fix taskbar.ps1 @@ -0,0 +1,39 @@ +<# +.SYNOPSIS + Fixes taskbar issues on RDS servers by resetting and reconfiguring firewall rules in the Windows Registry. + +.DESCRIPTION + This script addresses taskbar issues on Remote Desktop Services (RDS) servers. + It removes and recreates specific firewall-related registry keys and sets the `DeleteUserAppContainersOnLogoff` configuration. + A manual reboot is required after running the script. + +.NOTES + Author: SAN + Date: ??? + #public + +.CHANGELOG + +.TODO + Implement a reboot flag + +#> + + +# Remove existing AppIso FirewallRules +Remove-Item "HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\RestrictedServices\AppIso\FirewallRules" -Force + +# Create new AppIso FirewallRules key +New-Item "HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\RestrictedServices\AppIso\FirewallRules" -Force + +# Remove existing FirewallRules +Remove-Item "HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules" -Force + +# Create new FirewallRules key +New-Item "HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules" -Force + +# Set DWORD DeleteUserAppContainersOnLogoff to 1 +Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy" -Name "DeleteUserAppContainersOnLogoff" -Value 1 -Type DWord + + +Write-Host "Registery fixed, Please reboot the device manualy" \ No newline at end of file From c044c76cb67dd301eb1aa206d55f3327dd7bb721 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:43:24 +0000 Subject: [PATCH 09/43] Update ./scripts/Fixes/Resync time NTP.ps1 --- scripts_staging/Fixes/Resync time NTP.ps1 | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 scripts_staging/Fixes/Resync time NTP.ps1 diff --git a/scripts_staging/Fixes/Resync time NTP.ps1 b/scripts_staging/Fixes/Resync time NTP.ps1 new file mode 100644 index 00000000..ec708f8b --- /dev/null +++ b/scripts_staging/Fixes/Resync time NTP.ps1 @@ -0,0 +1,44 @@ +<# +.SYNOPSIS + Restarts the Windows Time Service, resyncs system time, and queries the current time source. + +.DESCRIPTION + This script ensures that the Windows Time Service (w32time) is restarted, the system clock is resynced with its configured time source, + and the current time source is queried. Useful for troubleshooting time synchronization issues on a Windows system. + +.NOTES + Author: SAN + Date: 15.11.24 + #public + +.CHANGELOG + 15.11.24 v2.0 SAN Cleanup of the code & added header + +#> + +Write-Host "Restarting time service..." +try { + Restart-Service w32time -ErrorAction Stop + Write-Host "Time service restarted successfully." +} catch { + Write-Host "Failed to restart time service: $_" -ForegroundColor Red + exit 1 +} + +Write-Host "Waiting for 10 seconds..." +Start-Sleep -Seconds 10 + +Write-Host "Resyncing system time..." +try { + w32tm /resync + Write-Host "System time resynced successfully." +} catch { + Write-Host "Failed to resync system time." -ForegroundColor Red +} + +Write-Host "Querying time source..." +try { + w32tm /query /source +} catch { + Write-Host "Failed to query time source." -ForegroundColor Red +} From 23599b28f572efff6b88a7c92435145d2075b892 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:43:35 +0000 Subject: [PATCH 10/43] Update ./scripts/Tools/SSL Certificate manager.ps1 --- .../Tools/SSL Certificate manager.ps1 | 283 ++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 scripts_staging/Tools/SSL Certificate manager.ps1 diff --git a/scripts_staging/Tools/SSL Certificate manager.ps1 b/scripts_staging/Tools/SSL Certificate manager.ps1 new file mode 100644 index 00000000..7379f28b --- /dev/null +++ b/scripts_staging/Tools/SSL Certificate manager.ps1 @@ -0,0 +1,283 @@ +<# +.SYNOPSIS + This script manages SSL certificates for IIS, RDS Gateway, and common Windows certificate stores. It identifies, lists, and optionally deletes certificates based on thumbprints. + +.DESCRIPTION + The script performs the following tasks: + - Imports necessary modules (WebAdministration for IIS, RemoteDesktopServices for RDS). + - Lists SSL certificates bound to IIS sites. + - Retrieves the SSL certificate thumbprint for an RDS Gateway (if applicable). + - Lists all self-signed certificates across common certificate stores. + - Identifies and lists certificates that are not already listed by other functions. + - Deletes certificates using an environment variable (DeleteThumbprint) if set. + +.EXEMPLE + DeleteThumbprint=DFDF45DF45F8DFD92QA + +.NOTES + Author:SAN + Date: 15.08.24 + #public + +.TODO + Add exchange section + Add CA to the reports. + +#> + + +# Import the necessary modules +$importIIS = $false +$importRDS = $false + +try { + Import-Module WebAdministration -ErrorAction Stop + $importIIS = $true +} catch { + Write-Host "Failed to import WebAdministration module. IIS-related functions will not run." +} + +try { + Import-Module RemoteDesktopServices -ErrorAction Stop + $importRDS = $true +} catch { + Write-Host "Failed to import RemoteDesktopServices module. RDS-related functions will not run." +} + +# List of thumbprints already found +$global:listedThumbprints = @() + +# Function to add thumbprints to the list +function Add-ToListedThumbprints { + param ( + [string]$Thumbprint + ) + if ($Thumbprint -notin $global:listedThumbprints) { + $global:listedThumbprints += $Thumbprint + } +} + +# Function to get certificate details from all known stores +function Get-CertificateDetails { + param ( + [string]$Thumbprint + ) + + $stores = Get-AllCertificateStores + + foreach ($store in $stores) { + try { + $certs = Get-ChildItem -Path $store -ErrorAction SilentlyContinue + $cert = $certs | Where-Object { $_.Thumbprint -eq $Thumbprint } + if ($cert) { + return @{ + Certificate = $cert + StorePath = $store + } + } + } catch { + Write-Host "Failed to access certificate store: $store" + } + } + + return $null +} + +# Function to get certificate details given its thumbprint +function Get-CertificateDetailsByThumbprint { + param ( + [string]$Thumbprint + ) + + $result = Get-CertificateDetails -Thumbprint $Thumbprint + if ($result) { + return [PSCustomObject]@{ + "Thumbprint" = $result.Certificate.Thumbprint + "Subject" = $result.Certificate.Subject + "ExpirationDate" = $result.Certificate.NotAfter + } + } else { + Write-Host "Certificate with Thumbprint $Thumbprint not found in any store." + return $null + } +} + +# Function to list IIS SSL certificate thumbprints +function List-IIS-SSL-Thumbprints { + $results = @() + $sites = Get-Website + + foreach ($site in $sites) { + foreach ($binding in $site.Bindings.Collection) { + if ($binding.Protocol -eq "https") { + $sslCertHash = $binding.CertificateHash + $thumbprint = -join ($sslCertHash | ForEach-Object { "{0:X2}" -f $_ }) + + $certDetails = Get-CertificateDetailsByThumbprint -Thumbprint $thumbprint + + # Add to listed thumbprints + Add-ToListedThumbprints -Thumbprint $thumbprint + + $results += [PSCustomObject]@{ + "Thumbprint" = $thumbprint + "Subject" = $certDetails.Subject + "Expiration Date"= $certDetails.ExpirationDate + "IIS Site" = $site.Name + "Binding Info" = $binding.BindingInformation + } + } + } + } + + $results | Format-Table -AutoSize +} + +# Function to get RDS Gateway SSL certificate thumbprint +function Get-RDGatewaySSLCertificateThumbprint { + param ( + [string]$Path = 'RDS:\GatewayServer\SSLCertificate\Thumbprint' + ) + + try { + $thumbprintValue = (Get-Item -Path $Path).CurrentValue + + if ([string]::IsNullOrWhiteSpace($thumbprintValue)) { + Write-Host "The SSL certificate thumbprint set for RD Gateway is empty or not set." + } else { + $certDetails = Get-CertificateDetailsByThumbprint -Thumbprint $thumbprintValue + + # Add to listed thumbprints + Add-ToListedThumbprints -Thumbprint $thumbprintValue + + [PSCustomObject]@{ + "Thumbprint" = $thumbprintValue + "Subject" = $certDetails.Subject + "Expiration Date" = $certDetails.ExpirationDate + } | Format-Table -AutoSize + } + } + catch { + Write-Host "An error occurred while retrieving the SSL certificate thumbprint or the RDS Gateway role is not installed." + } +} + +# Function to get all common certificate stores +function Get-AllCertificateStores { + return @( + "Cert:\LocalMachine\My", + "Cert:\LocalMachine\WebHosting", # Web hosting store, if applicable + "Cert:\LocalMachine\RDS\GatewayServer", # RDS Gateway Server, if applicable + "Cert:\LocalMachine\RDS\ConnectionBroker", # RDS Connection Broker, if applicable + "Cert:\LocalMachine\Remote Desktop" + ) +} + +# Function to list certificates that are self-signed +function List-SelfSignedCertificates { + $results = @() + Write-Host "Listing Self-Signed Certificates:" + + $stores = Get-AllCertificateStores + + foreach ($store in $stores) { + try { + $certs = Get-ChildItem -Path $store -ErrorAction SilentlyContinue + foreach ($cert in $certs) { + if ($cert.Issuer -eq $cert.Subject) { # Self-signed certificates + $thumbprint = $cert.Thumbprint + + # Add to listed thumbprints + Add-ToListedThumbprints -Thumbprint $thumbprint + + $results += [PSCustomObject]@{ + "Thumbprint" = $thumbprint + "Subject" = $cert.Subject + "Expiration Date"= $cert.NotAfter + "Store Location" = $store + } + } + } + } catch { + Write-Host "Failed to access certificate store: $store" + } + } + + $results | Format-Table -AutoSize +} + +# Function to list certificates that are not listed by other functions +function List-UnlistedCertificates { + $results = @() + Write-Host "Listing Unlisted Certificates:" + + $stores = Get-AllCertificateStores + + foreach ($store in $stores) { + try { + $certs = Get-ChildItem -Path $store -ErrorAction SilentlyContinue + foreach ($cert in $certs) { + $thumbprint = $cert.Thumbprint + + if ($thumbprint -notin $global:listedThumbprints) { + $results += [PSCustomObject]@{ + "Thumbprint" = $thumbprint + "Store Location" = $store + "Subject" = $cert.Subject + "Expiration Date"= $cert.NotAfter + } + } + } + } catch { + Write-Host "Failed to access certificate store: $store" + } + } + + $results | Format-Table -AutoSize +} + +# Function to delete certificates by thumbprint based on an environment variable +function Delete-CertificateByThumbprint { + $thumbprintToDelete = $env:DeleteThumbprint + + if ([string]::IsNullOrWhiteSpace($thumbprintToDelete)) { + Write-Host "Environment variable 'DeleteThumbprint' is not set or empty." + return + } + + Write-Host "Attempting to delete certificates with Thumbprint: $thumbprintToDelete" + $stores = Get-AllCertificateStores + + foreach ($store in $stores) { + try { + $certs = Get-ChildItem -Path $store -ErrorAction SilentlyContinue + foreach ($cert in $certs) { + if ($cert.Thumbprint -eq $thumbprintToDelete) { + Write-Host "Deleting certificate with Thumbprint: $thumbprintToDelete from $store" + Remove-Item -Path $cert.PSPath -Force + } + } + } catch { + Write-Host "Failed to delete certificate from store: $store" + } + } +} + +# Main script execution +if ($importIIS) { + Write-Host "Listing IIS SSL Thumbprints:" + List-IIS-SSL-Thumbprints +} + +if ($importRDS) { + Write-Host "Getting RDS Gateway SSL Certificate Thumbprint:" + Get-RDGatewaySSLCertificateThumbprint +} + +# List self-signed certificates +List-SelfSignedCertificates + +# List certificates that were not listed by other functions +List-UnlistedCertificates + +# Attempt to delete certificates based on the DeleteThumbprint environment variable +Delete-CertificateByThumbprint \ No newline at end of file From 356b46db835a94a9ed29c8bd64b5541029625efc Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:56:00 +0000 Subject: [PATCH 11/43] Update ./scripts/Tasks/Start Eset update.ps1 --- scripts_staging/Tasks/Start Eset update.ps1 | 74 +++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 scripts_staging/Tasks/Start Eset update.ps1 diff --git a/scripts_staging/Tasks/Start Eset update.ps1 b/scripts_staging/Tasks/Start Eset update.ps1 new file mode 100644 index 00000000..c9a37f8c --- /dev/null +++ b/scripts_staging/Tasks/Start Eset update.ps1 @@ -0,0 +1,74 @@ +<# +.SYNOPSIS + This script attempts to execute the ESET Security update process, retrying if the process fails due to specific errors or no output being returned. + +.DESCRIPTION + The script runs the ESET Security update command using `ermm.exe`, capturing the output in a temporary file. + If the process exits with a non-zero code or produces invalid output, the script retries the operation up to a maximum retry count. + +.NOTES + Author: SAN + Date: 2024-12-11 + #public + +.CHANGELOG + +.TODO + +#> + +$retryCount = 10 +$retryDelaySeconds = 5 + +for ($i = 0; $i -lt $retryCount; $i++) { + try { + $outputFile = [System.IO.Path]::GetTempFileName() + $process = Start-Process -FilePath "C:\Program Files\ESET\ESET Security\ermm.exe" -ArgumentList "start update" -NoNewWindow -RedirectStandardOutput $outputFile -PassThru -Wait + + if ($process.ExitCode -ne 0) { + Write-Host "Error: The process exited with code $($process.ExitCode)." + exit 1 + } + + $output = Get-Content -Path $outputFile -Raw + + if ($null -eq $output) { + Write-Host "Error: No output received from the process." + exit 1 + } + + if ($output -notmatch '"error":null') { + Write-Host "Error: 'error':null not found in output." + Write-Host "Output: $output" + # Continue to retry + Write-Host "Retrying in $retryDelaySeconds seconds..." + Start-Sleep -Seconds $retryDelaySeconds + continue + } + + # If execution reaches here, the operation was successful + Write-Host "Update process completed successfully." + break + } catch { + $errorMessage = $_.Exception.Message + Write-Host "Attempt $($i+1): An error occurred: $errorMessage" + if ($errorMessage -match "Cannot process request because the process" -or $errorMessage -match "Impossible de traiter la demande, car le processus") { + Write-Host "Retrying in $retryDelaySeconds seconds..." + Start-Sleep -Seconds $retryDelaySeconds + continue + } + else { + exit 1 + } + } finally { + if (Test-Path $outputFile) { + Write-Host "Output: $output" + Remove-Item $outputFile -Force + } + } +} + +if ($i -eq $retryCount) { + Write-Host "Error: Maximum retry attempts reached. Update process failed." + exit 1 +} \ No newline at end of file From 50331e62e5c11e1fc0a4f783938f5bbda881a018 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:56:08 +0000 Subject: [PATCH 12/43] Update ./scripts/Tools/Reset permission of target folder.ps1 --- .../Reset permission of target folder.ps1 | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 scripts_staging/Tools/Reset permission of target folder.ps1 diff --git a/scripts_staging/Tools/Reset permission of target folder.ps1 b/scripts_staging/Tools/Reset permission of target folder.ps1 new file mode 100644 index 00000000..a5171013 --- /dev/null +++ b/scripts_staging/Tools/Reset permission of target folder.ps1 @@ -0,0 +1,100 @@ +<# +.SYNOPSIS + This script resets folder and file permissions by applying inherited permissions from the target folder and checks for permission mismatches across subfolders and files. + +.DESCRIPTION + The script retrieves the target folder path from an environment variable and ensures the folder exists. + It then gets the ACL (Access Control List) of the target folder and applies the inherited permissions to all subfolders and files within the target folder. + It also compares the ACLs of child items with their parent folder to identify permission mismatches. The script includes two main functions: + - `Set-InheritedPermissions`: Resets permissions and inheritance on a folder or file. + - `Compare-Acls`: Compares the ACLs of a child item with its parent to identify mismatched permissions. + +.EXAMPLE + TARGETFOLDER=C:\TargetFolder + + +.NOTES + Author: SAN + Date: ??? + #public + +.CHANGELOG + + +#> + + + +# Get the target folder from environment variable +$TargetFolder = $env:TARGETFOLDER + +if (-not (Test-Path -Path $TargetFolder)) { + Write-Output "The specified path does not exist: $TargetFolder" + exit +} + +# Get the ACL of the target folder +$targetAcl = Get-Acl -Path $TargetFolder + +# Function to reset permissions and inheritance +function Set-InheritedPermissions { + param ( + [string]$Path + ) + + try { + # Reset ACLs to match the target folder + $acl = Get-Acl -Path $Path + $acl.SetAccessRuleProtection($false, $true) # Enable inheritance, remove explicit permissions + Set-Acl -Path $Path -AclObject $targetAcl + + # Get the owner of the target folder + $owner = $targetAcl.Owner + # Set owner to be the same as the target folder + $acl.SetOwner([System.Security.Principal.NTAccount]$owner) + Set-Acl -Path $Path -AclObject $acl + + Write-Output "Reset permissions and ownership for: $Path" + } catch { + Write-Output "Failed to process permissions for: $Path" + } +} + +# Function to compare ACLs of child items with parent folder +function Compare-Acls { + param ( + [string]$Path + ) + + try { + $parentAcl = Get-Acl -Path (Get-Item -Path $Path).Parent.FullName + $itemAcl = Get-Acl -Path $Path + + # Compare ACLs + if ($itemAcl -ne $parentAcl) { + Write-Output "Permission mismatch for: $Path" + } + } catch { + Write-Output "Unreadable ACL or permission issue with: $Path" + } +} + +Write-Output "Processing folder: $TargetFolder" + +# Set permissions for the target folder itself +Set-InheritedPermissions -Path $TargetFolder + +# Process all subfolders and files to reset permissions +$items = Get-ChildItem -Path $TargetFolder -Recurse +foreach ($item in $items) { + Set-InheritedPermissions -Path $item.FullName +} + +Write-Output "`nScanning for files with unreadable or mismatched permissions..." + +# Scan all subfolders and files to check for permission issues +foreach ($item in $items) { + Compare-Acls -Path $item.FullName +} + +Write-Output "Permission scan completed." \ No newline at end of file From eca3c15073c1ab75ebf27871b4e7dd2bd0086ccc Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:01:13 +0000 Subject: [PATCH 13/43] Update ./scripts/Build/Create generic admin account.ps1 --- .../Build/Create generic admin account.ps1 | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 scripts_staging/Build/Create generic admin account.ps1 diff --git a/scripts_staging/Build/Create generic admin account.ps1 b/scripts_staging/Build/Create generic admin account.ps1 new file mode 100644 index 00000000..9b159c3d --- /dev/null +++ b/scripts_staging/Build/Create generic admin account.ps1 @@ -0,0 +1,74 @@ +<# +.SYNOPSIS + This script checks if an admin user exists, and if so, changes the password and ensures the user is added to the Administrators group. + +.DESCRIPTION + The script retrieves the admin username from the environment variable `adminusername` and generates a passphrase. + It checks if the user exists on the system, then either updates the password for an existing user or creates the user if they do not exist. + It also ensures the user is added to both the 'Administrators' and 'Administrateurs' local groups and disables the password expiration. + +.PARAMETER adminusername + The environment variable `adminusername` should be set with the desired username for the admin account. + +.EXAMPLE + adminusername=adminUser + +.NOTES + Author: SAN + Date: 01.01.24 + Dependencies: + GeneratedPassphrase snippet + #public + +.CHANGELOG + + + +#> + + +{{GeneratedPassphrase}} + +# Get admin username and password +$adminUsername = $env:adminusername +$adminPassword = $GeneratedPassphrase + +# Check if the admin username is provided +if (-not $adminUsername) { + Write-Output "adminusername environment variable is not set. Exiting script." + exit 1 +} + +# Check if the user already exists +$existingUser = & net user $adminUsername 2>&1 +if ($LASTEXITCODE -eq 0) { + # User already exists + Write-Output "The user '$adminUsername' already exists." + try { + # Change password + & net user $adminUsername $adminPassword + & wmic UserAccount where "Name='$adminUsername'" set PasswordExpires=False + & net localgroup Administrators $adminUsername /add + & net localgroup Administrateurs $adminUsername /add + Write-Output "The password for user '$adminUsername' has been changed." + } + catch { + Write-Output "Failed to change the password for user '$adminUsername'." + } +} +else { + # User doesn't exist + Write-Output "The user '$adminUsername' does not exist." + try { + # Create user + & net user $adminUsername $adminPassword /add /Y + Write-Output "The user '$adminUsername' has been created with the password '$adminPassword'." + & net localgroup Administrators $adminUsername /add + & net localgroup Administrateurs $adminUsername /add + Write-Output "The user '$adminUsername' has been added to the Administrators group." + & wmic UserAccount where "Name='$adminUsername'" set PasswordExpires=False + } + catch { + Write-Output "Failed to create the user '$adminUsername'." + } +} \ No newline at end of file From 0f0c82378773b4c5ef69bf5811b81ac816ee501d Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:01:40 +0000 Subject: [PATCH 14/43] Update ./scripts/Checks/Active Directory Health.ps1 --- .../Checks/Active Directory Health.ps1 | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 scripts_staging/Checks/Active Directory Health.ps1 diff --git a/scripts_staging/Checks/Active Directory Health.ps1 b/scripts_staging/Checks/Active Directory Health.ps1 new file mode 100644 index 00000000..a112caef --- /dev/null +++ b/scripts_staging/Checks/Active Directory Health.ps1 @@ -0,0 +1,126 @@ +<# +.SYNOPSIS + This script performs Active Directory (AD) diagnostics and compares Group Policy Object (GPO) version numbers between Sysvol and Active Directory. + +.DESCRIPTION + The script performs a series of Active Directory tests using DCDIAG, checks for discrepancies in GPO versions between Sysvol and AD, and outputs the results. + It also checks if the Active Directory Domain Services (AD-DS) feature is installed on the system before performing these tests. + If any test fails, the exit code is incremented. The script provides detailed output for each test and comparison, indicating success or failure. + +.NOTES + Author: SAN + Date: ??? + #public + +.CHANGELOG + +#> + +# Initialize exit code +$exitCode = 0 + +# Function to perform Active Directory tests +function CheckAD { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] + [string[]]$Tests + ) + + process { + $results = @{} + + foreach ($test in $Tests) { + $output = dcdiag /test:$test + + if ($output -notmatch "chou") { + $results[$test] = "OK" + } else { + $results[$test] = "Failed!" + $global:exitCode++ + } + + # Output individual test result + Write-Host "DCDIAG Test: $test Result: $($results[$test])" + } + + $results + } +} + +# Function to compare GPO version numbers + +function Compare-GPOVersions { + [CmdletBinding()] + param () + + process { + Import-Module GroupPolicy + + Get-GPO -All | ForEach-Object { + # Retrieve GPO information (GUID and Name) + $GPOId = $_.Id + $GPOName = $_.DisplayName + + # Version GPO User + $NumUserSysvol = (Get-Gpo -Guid $GPOId).User.SysvolVersion + $NumUserAD = (Get-Gpo -Guid $GPOId).User.DSVersion + + # Version GPO Machine + $NumComputerSysvol = (Get-Gpo -Guid $GPOId).Computer.SysvolVersion + $NumComputerAD = (Get-Gpo -Guid $GPOId).Computer.DSVersion + + # USER - Compare version numbers + if ($NumUserSysvol -ne $NumUserAD) { + Write-Host "$GPOName ($GPOId) : USER Versions différentes (Sysvol : $NumUserSysvol | AD : $NumUserAD)" -ForegroundColor Red + $global:exitCode++ + } else { + Write-Host "$GPOName : USER Versions identiques" -ForegroundColor Green + } + + # COMPUTER - Compare version numbers + if ($NumComputerSysvol -ne $NumComputerAD) { + Write-Host "$GPOName ($GPOId) : COMPUTER Versions différentes (Sysvol : $NumComputerSysvol | AD : $NumComputerAD)" -ForegroundColor Red + $global:exitCode++ + } else { + Write-Host "$GPOName : COMPUTER Versions identiques" -ForegroundColor Green + } + } + Write-Host "GPO USER/COMPUTER Version OK" -ForegroundColor Green + } +} + +# Check if Active Directory Domain Services feature is installed +try { + $adFeature = Get-WindowsFeature -Name AD-Domain-Services -ErrorAction Stop + + if ($adFeature.InstallState -eq "Installed") { + # Specify your AD tests + $tests = ("Advertising", "FrsSysVol", "MachineAccount", "Replications", "RidManager", "Services", "FsmoCheck", "SysVolCheck") + # Call the function with the AD tests + Write-Host "DCDIAG" + $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 "" + Write-Host "GPO Versions checks" + # Call the function to compare GPO versions + Compare-GPOVersions + } else { + Write-Host "Active Directory Domain Services feature is not installed or not in the 'Installed' state." + exit + } +} catch { + Write-Error "Failed to retrieve information about Active Directory Domain Services feature: $_" + $global:exitCode++ +} + +exit $exitCode \ No newline at end of file From 5f672d8a93c8cc65ca8ae6f22769ec7710226839 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:48:33 +0000 Subject: [PATCH 15/43] Update ./scripts/Checks/Is TCP port open.ps1 --- scripts_staging/Checks/Is TCP port open.ps1 | 87 +++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 scripts_staging/Checks/Is TCP port open.ps1 diff --git a/scripts_staging/Checks/Is TCP port open.ps1 b/scripts_staging/Checks/Is TCP port open.ps1 new file mode 100644 index 00000000..b46b3e31 --- /dev/null +++ b/scripts_staging/Checks/Is TCP port open.ps1 @@ -0,0 +1,87 @@ +<# +.SYNOPSIS + Checks if a TCP port is open on the local machine based on the environment variable "TCP_PORT". + +.DESCRIPTION + This script checks if the TCP port defined by the environment variable "TCP_PORT" is open using the `Test-NetConnection` cmdlet. + If `Test-NetConnection` is not available, it falls back to using the `System.Net.Sockets.TcpClient` class to perform the check. + Additionally, it will display the executable and process information that is holding the port open. + If the application is linked to a service, the service name and status will be displayed. + The script will exit with a status code of 1 if the port is closed or if the environment variable is not set. + +.EXEMPLE + TCP_PORT=3435 + +.NOTES + Author: SAN + Date: 01.10.2024 + #public + +.CHANGELOG + +#> + +$portStr = [System.Environment]::GetEnvironmentVariable("TCP_PORT") + +# Initialize the port variable +$port = 0 + +# Check if the environment variable is set and valid +if (-not $portStr -or -not [int]::TryParse($portStr, [ref]$port) -or $port -lt 1) { + Write-Output "Error: Environment variable 'TCP_PORT' is not set or is invalid." + exit 1 +} + +$address = "localhost" + +Write-Output "Checking connectivity to $address on port $port..." + +# Try Test-NetConnection if available +if (Get-Command Test-NetConnection -ErrorAction SilentlyContinue) { + $tcpConnection = Test-NetConnection -ComputerName $address -Port $port + if ($tcpConnection.TcpTestSucceeded) { + Write-Output "Success: Port $port on $address is open." + } else { + Write-Output "Failure: Port $port on $address is not open." + Write-Output "Details: TCP connection test failed." + exit 1 + } +} else { + # Fallback using TcpClient + try { + $tcpClient = New-Object System.Net.Sockets.TcpClient + $tcpClient.Connect($address, $port) + Write-Output "Success: Port $port on $address is open." + $tcpClient.Close() + } catch { + Write-Output "Failure: Port $port on $address is not open." + Write-Output "Details: TCP connection test threw an exception." + exit 1 + } +} + +# Find the process holding the port open for incoming connections only +$netstatOutput = netstat -ano | Select-String ":$port\s" | ForEach-Object { $_.Line } | Where-Object { $_ -match 'LISTENING' -and $_ -match '0.0.0.0|127.0.0.1' } +if ($netstatOutput) { + $portPID = $netstatOutput -replace '^.*\s+(\d+)$', '$1' + $process = Get-Process -Id $portPID -ErrorAction SilentlyContinue + + if ($process) { + Write-Output "The port $port is being used by the process '$($process.ProcessName)' (PID: $portPID)." + Write-Output "Executable Path: $($process.Path)" + + # Check if the process is linked to a service + $service = Get-WmiObject Win32_Service | Where-Object { $_.ProcessId -eq $portPID } + if ($service) { + Write-Output "This process is linked to the service: '$($service.Name)'" + Write-Output "Service Display Name: $($service.DisplayName)" + Write-Output "Service Status: $($service.State)" + } else { + Write-Output "This process is not linked to any service." + } + } else { + Write-Output "Unable to retrieve the process details for PID $portPID." + } +} else { + Write-Output "No process is currently using port $port for incoming connections." +} \ No newline at end of file From f924415ee4a88c3c07620d0c7ee1b3a65e29ad5e Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:48:41 +0000 Subject: [PATCH 16/43] Update ./scripts/Fixes/Ensure all services with startup type Automatic are running.ps1 --- ...ith startup type Automatic are running.ps1 | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 scripts_staging/Fixes/Ensure all services with startup type Automatic are running.ps1 diff --git a/scripts_staging/Fixes/Ensure all services with startup type Automatic are running.ps1 b/scripts_staging/Fixes/Ensure all services with startup type Automatic are running.ps1 new file mode 100644 index 00000000..2b2920ad --- /dev/null +++ b/scripts_staging/Fixes/Ensure all services with startup type Automatic are running.ps1 @@ -0,0 +1,56 @@ +<# +.SYNOPSIS + This script retrieves Windows services that are set to start automatically (including delayed start) but are not currently running, + and optionally starts those services based on the value of an environment variable. + +.DESCRIPTION + The script checks for the environment variable "START_SERVICES". If this variable is set to "true", the script will attempt to start + all services that are configured to start automatically (including delayed start) but are not currently running. It displays the + list of such services in a formatted table for the user. If the environment variable is not set to "true", the script will only + display the list of services without starting them. + +.PARAMETER None + "START_SERVICES" environment variable to determine whether to start the services. + +.EXEMPLE + START_SERVICES=true + +.NOTES + Author:Dave Long + Date: 2021-05-12 + #public + +.Changelog + 02.12.24 SAN Full code refactorisation + +#> + +# Check for an environment variable (e.g., "START_SERVICES") to determine if services should be started +$StartServices = $false +if ($env:START_SERVICES -eq "true") { + $StartServices = $true + Write-Output "Start Services enabled" +} + +# Retrieve services that are set to start automatically (including delayed) but are not currently running +$servicesToStart = Get-Service | Where-Object { + $_.StartType -in @("Automatic", "AutomaticDelayedStart") -and + $_.Status -ne "Running" +} + +# Display the services in a formatted table +$servicesToStart | Format-Table -AutoSize + +# Start the services if the environment variable is set to true +if ($StartServices) { + foreach ($service in $servicesToStart) { + try { + Start-Service -Name $service.Name -ErrorAction Stop + Write-Output "Started service: $($service.Name)" + } catch { + Write-Warning "Failed to start service: $($service.Name). Error: $_" + } + } +} else { + Write-Output "Services will not be started. Set environment variable 'START_SERVICES' to 'true' to enable this." +} From a2fed5d994d7e0c9d795da97a5224c6423b2a829 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:54:43 +0000 Subject: [PATCH 17/43] Update ./scripts/Checks/Task Scheduler scanner.ps1 --- .../Checks/Task Scheduler scanner.ps1 | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 scripts_staging/Checks/Task Scheduler scanner.ps1 diff --git a/scripts_staging/Checks/Task Scheduler scanner.ps1 b/scripts_staging/Checks/Task Scheduler scanner.ps1 new file mode 100644 index 00000000..160fe2aa --- /dev/null +++ b/scripts_staging/Checks/Task Scheduler scanner.ps1 @@ -0,0 +1,135 @@ +<# +.SYNOPSIS + This script retrieves scheduled tasks and filters out those that match specific ignore conditions based on task folder, name, or user. It identifies rogue tasks that do not meet the ignore criteria. + +.DESCRIPTION + The script retrieves all scheduled tasks on the system and checks each task against predefined conditions to ignore certain tasks. + It filters tasks based on their folder path, task name, and user ID. If a task does not match any of the ignore criteria, its details (folder, name, and user) are collected. + The script provides a debug mode for verbose output during task processing. If rogue tasks are found, they are displayed in a table, and the script exits with a non-zero status code. + + +.EXAMPLE + + +.NOTES + Author: SAN + Date: ??? + #public + +.CHANGELOG + + +.TODO + Use a flag for debug + set ignore value from env + +#> + + + +# Set the debug flag +$debug = 0 + +# Retrieve all scheduled tasks +$tasks = Get-ScheduledTask + +# Initialize an array to hold the task details +$taskDetails = @() + +# Define ignore conditions +$ignoreFolders = @( + "\Mozilla\", + "\Microsoft\Office\", + "\Microsoft\Windows\", + "\MySQL\Installer\" +) +$ignoreNames = @( + "Optimize Start Menu Cache", + "DropboxUpdateTaskUserS", + "GoogleUpdate", + "User_Feed_Synchronization", + "Adobe Acrobat", + "RMM", + "edgeupdate", + "OneDrive Reporting Task", + "ZoomUpdateTaskUser", + "OneDrive Standalone Update Task" + "CreateExplorerShellUnelevatedTask" +) +$ignoreUsers = @( + "*svc*", + "*Systme*", + "*Système*", + "*Syst*", + "SYSTEM" +) + +# Loop through each scheduled task +foreach ($task in $tasks) { + $taskFolder = $task.TaskPath + $taskName = $task.TaskName + $principalUserId = $task.Principal.UserId + + # Check if triggers are null and handle accordingly + if ($task.Triggers) { + # Commented out because Triggers are not needed + # $taskTriggers = $task.Triggers | ForEach-Object { $_.ToString() } + $taskTriggers = "Triggers present" + } else { + $taskTriggers = "No triggers" + } + + if ($debug -eq 1) { + # Debug: Print the current task details + Write-Output "Checking Task: Folder='$taskFolder', Name='$taskName', UserID='$principalUserId'" + # Commented out because Triggers are not needed + # Write-Output "Triggers: $($taskTriggers -join ', ')" + } + + # Check ignore conditions + $folderIgnored = $ignoreFolders | Where-Object { $taskFolder -like "*$_*" } | Measure-Object | Select-Object -ExpandProperty Count + $nameIgnored = $ignoreNames | Where-Object { $taskName -like "*$_*" } | Measure-Object | Select-Object -ExpandProperty Count + $userIgnored = $ignoreUsers | Where-Object { $principalUserId -like $_ } | Measure-Object | Select-Object -ExpandProperty Count + + if ($debug -eq 1) { + # Debug: Print ignore conditions + Write-Output "Folder Ignored Count: $folderIgnored" + Write-Output "Name Ignored Count: $nameIgnored" + Write-Output "User Ignored Count: $userIgnored" + } + + # Determine if the task should be ignored + $shouldIgnore = ($folderIgnored -gt 0) -or ($nameIgnored -gt 0) -or ($userIgnored -gt 0) + + if ($debug -eq 1) { + # Debug: Print ignore decision + Write-Output "Should Ignore: $shouldIgnore" + } + + if (-not $shouldIgnore) { + # Get the task registration info + $registrationInfo = $task.RegistrationInfo + + # Add the task details to the array + $taskDetails += [PSCustomObject]@{ + Folder = $taskFolder + TaskName = $taskName + RunBy = $principalUserId + # Commented out because CreatedBy is not needed + # CreatedBy = $registrationInfo.Author + # Commented out because Triggers are not needed + # Triggers = $taskTriggers -join ', ' + } + } +} + +# Check if the taskDetails array is empty +if ($taskDetails.Count -eq 0) { + Write-Output "No rogue tasks found" +} else { + Write-Output "Rogue tasks found, please execute with a service user" + # Output the task details + $taskDetails | Format-Table -AutoSize + # Exit with status code 1 + exit 1 +} \ No newline at end of file From 4b22d68ff2f7e7dcce59162ff5e5815cae1213d9 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 23:44:46 +0000 Subject: [PATCH 18/43] Update ./snippets/VHDXCleaner.ps1 --- scripts_staging/snippets/VHDXCleaner.ps1 | 192 +++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 scripts_staging/snippets/VHDXCleaner.ps1 diff --git a/scripts_staging/snippets/VHDXCleaner.ps1 b/scripts_staging/snippets/VHDXCleaner.ps1 new file mode 100644 index 00000000..36e0dd31 --- /dev/null +++ b/scripts_staging/snippets/VHDXCleaner.ps1 @@ -0,0 +1,192 @@ +<# +.SYNOPSIS + This script optimizes VHDX files by performing cleanup, defragmentation, and compaction, with options for targeted or random selection and download folder management. + +.DESCRIPTION + This PowerShell script optimizes VHDX files located in a specified directory. + It performs the following tasks: + - Cleanup operations to remove temporary files and optionally clean the Downloads folder. + - Defragments the disks to improve performance. + - Compacts the VHDX files to reduce their size. + + The script behavior can be controlled using environment variables: + - Specify the directory containing VHDX files. + - Optionally target a specific VHDX file or randomly process 50% of the files. + - Enable or disable cleanup of the Downloads folder. + +.EXEMPLE + VHDX_PATH + Specifies the path where the VHDX files are located. + RANDOM_PICKS + If set to "1", the script will randomly pick 50% of the VHDX files for optimization. Default is to process all files. + VHDX_TARGET + Specifies the name of a specific VHDX file to be optimized. If specified, only the targeted VHDX file will be optimized. + ENABLE_DOWNLOAD_CLEANUP + If set to "1", the script cleans up the Downloads folder in the mounted VHDX images. Default is to skip this step. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + 29/08/24 SAN Swapped Write-Host to Write-Output + 19/09/24 SAN Added a disabled flag to avoid alerts + 23/10/24 SAN Prepared download cleanup + 28/11/24 SAN Updated script to use environment variables to prep transfers to snippet and added download cleanup toggle + 28/11/24 SAN added Temporary Internet Files + +.TODO + - Investigate "compact vdisk" errors related to non-read-only mode + - Finalize download cleanup implementation. + - Logoff only 1 user when using target + +#> + +# Read environment variables +$Path = $env:VHDX_PATH +$RandomPicks = $env:RANDOM_PICKS -eq "1" +$Target = $env:VHDX_TARGET +$EnableDownloadCleanup = $env:ENABLE_DOWNLOAD_CLEANUP -eq "1" + +# Check if Get-RDUserSession is available, if not exit with code 0 +try { + $null = Get-RDUserSession -ErrorAction Stop +} +catch { + if ($_.Exception.Message -match "A Remote Desktop Services deployment does not exist") { + Write-Output "Remote Desktop Services deployment does not exist. Exiting." + exit 0 + } + else { + Write-Output "An unexpected error occurred while checking for RDS deployment." + Write-Output "Error: $($_.Exception.Message)" + exit 0 + } +} + +# Check if the path contains the word "disabled" +if ($Path -like "*disabled*") { + Write-Output "Script disabled for this server" + exit 0 +} + +# Check if the specified path exists +if (-not (Test-Path $Path)) { + Write-Output "Specified path '$Path' does not exist or is invalid." + exit 1 +} + +# Close active user sessions +Write-Output "Closing active user sessions..." +Get-RDUserSession | ForEach-Object { + Write-Output "Logging off session ID $($_.UnifiedSessionId) on host $($_.HostServer)..." + Invoke-RDUserLogoff -HostServer $_.HostServer -UnifiedSessionID $_.UnifiedSessionId -Force +} + +# Define function to perform cleanup, defragmentation, and compaction +function Optimize-VHDX { + param ( + [string]$VHDXFilePath + ) + + Write-Output "Processing VHDX file: $VHDXFilePath" + + # Mount VHDX + Write-Output "Mounting VHDX file: $VHDXFilePath..." + Mount-DiskImage $VHDXFilePath -ErrorAction Stop + $mountedDisk = Get-DiskImage $VHDXFilePath | Get-Disk | Get-Partition + if (-not $mountedDisk) { + Write-Output "Failed to mount disk: $VHDXFilePath" + return + } + $driveLetter = $mountedDisk.DriveLetter + + # Cleanup temporary files + Write-Output "Cleaning up temporary files on drive $driveLetter..." + $tempPaths = @( + "$driveLetter\Windows\Temp", + "$driveLetter\Users\*\AppData\Local\Temp", + "$driveLetter\Users\*\AppData\Local\Microsoft\Windows\INetCache", + "$driveLetter\Users\*\AppData\Local\Microsoft\Windows\Temporary Internet Files" + + ) + foreach ($tempPath in $tempPaths) { + if (Test-Path $tempPath) { + Write-Output "Removing temporary files from $tempPath..." + Get-ChildItem $tempPath -Include * -Recurse | Remove-Item -Force -ErrorAction SilentlyContinue + } + } + + # Conditional cleanup of Downloads folder + if ($EnableDownloadCleanup) { + Write-Output "Cleaning up Downloads folder..." + $downloadPaths = "$driveLetter\Users\*\Downloads" + $timeLimit = (Get-Date).AddDays(-30) + $noticeFileName = "Downloads Notice - Files in this folder will be deleted regularly.txt" + + # Content of the notice in multiple languages + $noticeFileContent = @" +Les fichiers dans ce dossier seront supprimés régulièrement s'ils sont âgés de plus de 30 jours. +The files in this folder will be deleted regularly if they are older than 30 days. +"@ + + # Create or overwrite the notice file in each Downloads folder + foreach ($path in (Get-ChildItem -Path $downloadPaths -Directory -Recurse)) { + $noticeFilePath = Join-Path -Path $path.FullName -ChildPath $noticeFileName + if (Test-Path -Path $noticeFilePath) { + Remove-Item -Path $noticeFilePath -Force + } + Set-Content -Path $noticeFilePath -Value $noticeFileContent -Force + } + + # Clean up files older than 30 days + if (Test-Path $downloadPaths) { + Write-Output "Removing files older than 30 days from $downloadPaths..." + Get-ChildItem $downloadPaths -Include * -Recurse | Where-Object { $_.LastWriteTime -lt $timeLimit -and $_.Name -ne $noticeFileName } | Remove-Item -Force -ErrorAction SilentlyContinue + } + } + + # Defragment profile disk + Write-Output "Defragmenting profile disk on drive $driveLetter..." + Optimize-Volume -DriveLetter $driveLetter -Defrag -Verbose + + # Compact disk using DISKPART + Write-Output "Compacting profile disk on drive $driveLetter..." + $diskpartScript = @" +select vdisk file="$VHDXFilePath" +compact vdisk +"@ + + $diskpartScript | diskpart + + # Unmount VHDX + Write-Output "Unmounting VHDX file: $VHDXFilePath..." + Dismount-DiskImage $VHDXFilePath -ErrorAction SilentlyContinue +} + +# Process VHDX files based on environment variables +if ($Target) { + $targetFile = Join-Path $Path $Target + if (Test-Path $targetFile) { + Optimize-VHDX -VHDXFilePath $targetFile + } else { + Write-Output "Specified target file '$Target' does not exist." + } +} else { + $vhdxFiles = Get-ChildItem -Path "$Path\*.vhdx" -File + if ($vhdxFiles.Count -eq 0) { + Write-Output "No VHDX files found in the specified path: $Path" + } else { + if ($RandomPicks) { + $randomFiles = $vhdxFiles | Get-Random -Count ($vhdxFiles.Count / 2) + foreach ($file in $randomFiles) { + Optimize-VHDX -VHDXFilePath $file.FullName + } + } else { + $vhdxFiles | ForEach-Object { + Optimize-VHDX -VHDXFilePath $_.FullName + } + } + } +} From 3ab33299d2778e912a56bab7a1423c45010d959c Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Wed, 11 Dec 2024 23:50:58 +0000 Subject: [PATCH 19/43] Update ./scripts/Tools/Windows update force install new updates.ps1 --- ...ndows update force install new updates.ps1 | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 scripts_staging/Tools/Windows update force install new updates.ps1 diff --git a/scripts_staging/Tools/Windows update force install new updates.ps1 b/scripts_staging/Tools/Windows update force install new updates.ps1 new file mode 100644 index 00000000..8ecd0dd1 --- /dev/null +++ b/scripts_staging/Tools/Windows update force install new updates.ps1 @@ -0,0 +1,67 @@ +<# +.SYNOPSIS + This script checks for available Windows updates and installs them using the PSWindowsUpdate module. + +.DESCRIPTION + This PowerShell script is designed to automate the process of checking for and installing Windows updates. + It first ensures that the PSWindowsUpdate module is installed and then proceeds to check for available updates. + If updates are found, it initiates the update process, installs all available updates, and reboots if necessary. + Finally, it retrieves and displays the date of the last successful installation of Windows updates. + +.NOTES + Author: SAN + Date: 02.04.24 + Dependency: PowerShell 7 snippet, PSWindowsUpdate module + #public + +.CHANGELOG + +#> + + + + +{{CallPowerShell7}} + +# Set TLS version to 1.2 +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +# Check if PSWindowsUpdate module is available +if (Get-Module -ListAvailable -Name PSWindowsUpdate) { + Write-Output "PSWindowsUpdate is already installed" +} else { + # If module is not available, install it + Write-Output "Installing PSWindowsUpdate module..." + Install-Module -Name PSWindowsUpdate -Force + + # Check if there was an error during installation and attempt to install NuGet package provider if necessary + if ($?) { + Write-Output "PSWindowsUpdate module installed successfully." + } else { + Write-Output "Error occurred during PSWindowsUpdate module installation. Attempting to install NuGet package provider..." + Install-PackageProvider -Name NuGet -Force + + # Re-attempt to install PSWindowsUpdate module + Write-Output "Re-running PSWindowsUpdate module installation..." + Install-Module -Name PSWindowsUpdate -Force + } +} + +# Function to start the update process +function StartUpdateProcess { + Write-Host "Start updates:" + Get-WindowsUpdate -Verbose -Install -AcceptAll -AutoReboot +} + +# Check updates +Write-Host "Check for available updates:" +Get-WindowsUpdate + +# Start update process +Write-Host "Updating Windows" +StartUpdateProcess + +Write-Host "Output of the last updates results:" +$results = Get-WULastResults +$lastInstallationSuccessDate = $results | Select-Object -ExpandProperty LastInstallationSuccessDate +$lastInstallationSuccessDate \ No newline at end of file From 6560fb2661ad7f527bb08c1dc668639e3d0e76ba Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:19:41 +0000 Subject: [PATCH 20/43] Update ./scripts/Checks/Active Directory Health.ps1 --- scripts_staging/Checks/Active Directory Health.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts_staging/Checks/Active Directory Health.ps1 b/scripts_staging/Checks/Active Directory Health.ps1 index a112caef..c72bc466 100644 --- a/scripts_staging/Checks/Active Directory Health.ps1 +++ b/scripts_staging/Checks/Active Directory Health.ps1 @@ -9,7 +9,7 @@ .NOTES Author: SAN - Date: ??? + Date: 01.01.24 #public .CHANGELOG From 3ab9e46e4dcfd89767abb8a0d1289e7524b5e0d4 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:20:03 +0000 Subject: [PATCH 21/43] Update ./scripts/Tools/Activate windows with KMS.ps1 --- scripts_staging/Tools/Activate windows with KMS.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts_staging/Tools/Activate windows with KMS.ps1 b/scripts_staging/Tools/Activate windows with KMS.ps1 index 7f8de58c..49bf0f79 100644 --- a/scripts_staging/Tools/Activate windows with KMS.ps1 +++ b/scripts_staging/Tools/Activate windows with KMS.ps1 @@ -11,7 +11,7 @@ .NOTES Author: SAN - Date: ??? + Date: 14.11.24 #public .CHANGELOG @@ -19,6 +19,8 @@ .TODO Convert the script to use the PowerShell module as the future of vbs is uncertain. + see code bellow for prototype + #> From 5413b3de8f24fc2755d337ab30426001366a7dc7 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:26:15 +0000 Subject: [PATCH 22/43] Update ./scripts/Tasks/Auto-logoff users.ps1 --- scripts_staging/Tasks/Auto-logoff users.ps1 | 156 ++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 scripts_staging/Tasks/Auto-logoff users.ps1 diff --git a/scripts_staging/Tasks/Auto-logoff users.ps1 b/scripts_staging/Tasks/Auto-logoff users.ps1 new file mode 100644 index 00000000..c16413b2 --- /dev/null +++ b/scripts_staging/Tasks/Auto-logoff users.ps1 @@ -0,0 +1,156 @@ +<# +.SYNOPSIS + Logs off users who have been inactive for a specified duration. + +.DESCRIPTION + This script retrieves all active user sessions on the server and logs off users + who have been inactive for more than the specified duration (50 minutes by default). + It handles different session states and extracts session IDs properly for both active and disconnected sessions. + +.PARAMETER maxInactivityTime + The maximum period of inactivity in seconds before a user is logged off. Default is 3000 seconds (50 minutes). + +.EXEMPLE + -maxInactivityTime 3600 + +.NOTES + Author: SAN + Date: 12.06.24 + #public + +.TODO + Add user warning for the users + move var to env + +#> + + +param ( + [int]$maxInactivityTime = 3000 +) + +function Get-LastInputTime { + Add-Type @" + using System; + using System.Runtime.InteropServices; + public class IdleTime { + [DllImport("user32.dll")] + public static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); + public struct LASTINPUTINFO { + public uint cbSize; + public uint dwTime; + } + public static uint GetIdleTime() { + LASTINPUTINFO lastInputInfo = new LASTINPUTINFO(); + lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo); + GetLastInputInfo(ref lastInputInfo); + return (uint)Environment.TickCount - lastInputInfo.dwTime; + } + public static DateTime GetLastInputTime() { + return DateTime.Now.AddMilliseconds(-(long)GetIdleTime()); + } + } +"@ + return [IdleTime]::GetLastInputTime() +} + +# Arrays to store user information +$foundUsers = @() +$keptConnectedUsers = @() +$disconnectedUsers = @() + +# Get all explorer.exe processes +$explorerProcesses = Get-Process -Name explorer -ErrorAction SilentlyContinue + +if ($explorerProcesses) { + Write-Host "Explorer.exe processes found: $($explorerProcesses.Count)" + + # Get current time + $currentTime = Get-Date + + foreach ($process in $explorerProcesses) { + try { + # Get the user session ID + $sessionId = $process.SessionId + + if ($sessionId -ge 0) { + Write-Host "Processing Session ID: $sessionId" + + # Use query user to get session information + $queryUserOutput = query user + Write-Host "Query User Output:`n$queryUserOutput" + + $sessionInfo = $queryUserOutput | Select-String -Pattern " $sessionId " -SimpleMatch + + if ($sessionInfo) { + $sessionInfoParts = $sessionInfo -split '\s+' + Write-Host "Session Info Parts: $sessionInfoParts" + + # Find the username and idle time in session info parts + $username = $null + $idleTime = $null + for ($i = 0; $i -lt $sessionInfoParts.Length; $i++) { + if ($sessionInfoParts[$i] -match '^\d+$' -and $sessionInfoParts[$i] -eq $sessionId.ToString()) { + $username = $sessionInfoParts[$i-1] + $idleTime = $sessionInfoParts[$i+2] + break + } + } + + if ($username -and $idleTime) { + # Debugging information + Write-Host "Username: $username, Idle Time String: $idleTime" + + # Attempt to parse idle time to TimeSpan + $idleTimeSpan = $null + if ($idleTime -match '^\d{1,2}:\d{2}$') { + # Handle formats like "1:30" or "12:45" + $idleTimeSpan = [TimeSpan]::Parse("00:$idleTime") + } elseif ($idleTime -match '^\d{1,2}:\d{2}:\d{2}$') { + # Handle formats like "1:30:00" or "12:45:00" + $idleTimeSpan = [TimeSpan]::Parse($idleTime) + } elseif ($idleTime -match '^\d+$') { + # Handle single digit idle time representing minutes + $idleTimeSpan = New-TimeSpan -Minutes $idleTime + } else { + Write-Host "Unable to parse idle time: $idleTime" + } + + if ($idleTimeSpan) { + Write-Host "Username: $username, Session ID: $sessionId, Idle Time: $idleTimeSpan" + + # Add username to found users list + $foundUsers += $username + + # Check if the user is idle for more than X + if ($idleTimeSpan.TotalSeconds -ge $maxInactivityTime) { + Write-Host "User $username (Session ID: $sessionId) has been idle for more than 4 hours. Logging off..." + $disconnectedUsers += $username + + # Log off the user session + logoff $sessionId + } else { + $keptConnectedUsers += $username + } + } + } else { + Write-Host "Unable to find username or idle time in session info parts." + } + } else { + Write-Host "No session info found for Session ID: $sessionId" + } + } else { + Write-Host "Invalid session ID: $sessionId" + } + } catch { + Write-Error "Failed to process session ID $($process.SessionId): $_" + } + } +} else { + Write-Host "No explorer.exe processes found." +} + +# Output the lists +Write-Host "Users Found: $($foundUsers -join ', ')" +Write-Host "Users Kept Connected: $($keptConnectedUsers -join ', ')" +Write-Host "Users Disconnected: $($disconnectedUsers -join ', ')" \ No newline at end of file From 2b9ddac6902c9516d57547f3a0b7e129686117f6 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:34:11 +0000 Subject: [PATCH 23/43] Update ./scripts/Tasks/Change user password.ps1 --- .../Tasks/Change user password.ps1 | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 scripts_staging/Tasks/Change user password.ps1 diff --git a/scripts_staging/Tasks/Change user password.ps1 b/scripts_staging/Tasks/Change user password.ps1 new file mode 100644 index 00000000..2638cb7a --- /dev/null +++ b/scripts_staging/Tasks/Change user password.ps1 @@ -0,0 +1,38 @@ +<# +.SYNOPSIS + This script changes the password for the user to a randomly generated password. + +.DESCRIPTION + The script defines a function to generate a random password and then sets the generated password for the specified user. + +.NOTES + Author: SAN + Date: 01.01.24 + Dependencies: + GeneratedPassphrase snippet + #public + +.TODO + Do not allow to change the password on non primary DC it causes conflicts + move param to env +#> + +param( + [Parameter(Mandatory=$true)] + [string]$username +) + +#Call snippet +{{GeneratedPassphrase}} +$newPassword = $GeneratedPassphrase + +# Set the new password for the user +net user $username $newPassword + +# Check if the password change was successful +if ($LASTEXITCODE -eq 0) { + Write-Host "$newPassword" +} else { + Write-Host "Password change for $username failed. Please check for errors." + exit 1 +} \ No newline at end of file From 43e7282c664bf4ae0e7ac6e88beb45fa443a6c10 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:46:20 +0000 Subject: [PATCH 24/43] Update ./scripts/Collectors/Collect Licensing 1 General.ps1 --- .../Collect Licensing 1 General.ps1 | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 scripts_staging/Collectors/Collect Licensing 1 General.ps1 diff --git a/scripts_staging/Collectors/Collect Licensing 1 General.ps1 b/scripts_staging/Collectors/Collect Licensing 1 General.ps1 new file mode 100644 index 00000000..88e4c351 --- /dev/null +++ b/scripts_staging/Collectors/Collect Licensing 1 General.ps1 @@ -0,0 +1,54 @@ +<# +.SYNOPSIS + Gathers system information for licensing reporting to Microsoft. + +.DESCRIPTION + This script collects and displays key system details required for licensing reports, + such as OS version, build number, edition, workgroup or domain nameand the number of CPU sockets. + It utilizes PowerShell cmdlets like `Get-CimInstance` and `Get-WmiObject` to retrieve system data. + +.NOTES + Author: SAN + Date: YYYY-MM-DD + #public + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + +.TODO + Optimize the calculation of CPU sockets for clarity and accuracy. + +#> + + +function Get-WindowsVersion { + $osInfo = Get-CimInstance Win32_OperatingSystem + $osVersion = $osInfo.Version + $osBuild = $osInfo.BuildNumber + $osEdition = $osInfo.Caption + + $hostname = $env:COMPUTERNAME + $workgroup = (Get-WmiObject Win32_ComputerSystem).Domain + $localIP = (Test-Connection -ComputerName $hostname -Count 1).IPV4Address.IPAddressToString + + $CPU = Get-WmiObject -Class Win32_Processor + $CPUs = 0 + $Sockets = 0 + + foreach ($Processor in $CPU) { + $CPUs++ + $Sockets += $Processor.NumberOfLogicalProcessors / $Processor.NumberOfCores + } + + #Write-Host "Hostname: $hostname" + Write-Host "OS: $osEdition" + Write-Host "Workgroup/Domain: $workgroup" + #Write-Host "Local IP Address: $localIP" + Write-Host "Sockets: $Sockets" +} + +Get-WindowsVersion \ No newline at end of file From 5aa9e92df743bfdaf553eafe735cc2eff9bcda5c Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:46:22 +0000 Subject: [PATCH 25/43] Update ./scripts/Collectors/Collect Licensing 2 SQL.ps1 --- .../Collectors/Collect Licensing 2 SQL.ps1 | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 scripts_staging/Collectors/Collect Licensing 2 SQL.ps1 diff --git a/scripts_staging/Collectors/Collect Licensing 2 SQL.ps1 b/scripts_staging/Collectors/Collect Licensing 2 SQL.ps1 new file mode 100644 index 00000000..327c45e4 --- /dev/null +++ b/scripts_staging/Collectors/Collect Licensing 2 SQL.ps1 @@ -0,0 +1,92 @@ +<# +.SYNOPSIS + Collects Microsoft SQL Server instance details for licensing and capacity reporting. + +.DESCRIPTION + This script identifies running Microsoft SQL Server instances on the local machine, + retrieves their edition, and provides detailed hardware and configuration information. + It includes data such as the number of CPUs, cores, and SQL Server capacity limits based on the edition. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + +.TODO + +#> + + +function Get-MSSQLVersion { + $SQLInstances = Get-Service -Name MSSQL* | Where-Object { $_.Status -eq "Running" -and $_.Name -notlike 'MSSQLFDLauncher*' -and $_.Name -notlike 'MSSQLLaunchpad*' -and $_.Name -notlike '*WID*' -and $_.Name -notlike 'MSSQLServerOLAPService*' } | Select-Object -Property @{label='InstanceName';expression={$_.Name -replace '^.*\$'}} + + if ($SQLInstances.Count -eq 0) { + Write-Host "MS SQL not found" + return + } + + foreach ($SQLInstance in $SQLInstances.InstanceName) { + $ServerName = $env:COMPUTERNAME + + # Get Default SQL Server instance's Edition + if ($SQLInstance -like 'MSSQLSERVER') { + $SQLName = $ServerName + } else { + $SQLName = "$ServerName\$SQLInstance" + } + + $sqlconn = New-Object System.Data.SqlClient.SqlConnection("server=$SQLName;Trusted_Connection=true") + $query = "SELECT SERVERPROPERTY('Edition') AS Edition, SERVERPROPERTY('MachineName') AS MachineName, SERVERPROPERTY('IsClustered') AS [Clustered];" + + $sqlconn.Open() + $sqlcmd = New-Object System.Data.SqlClient.SqlCommand ($query, $sqlconn) + $sqlcmd.CommandTimeout = 0 + $dr = $sqlcmd.ExecuteReader() + + while ($dr.Read()) { + $SQLEdition = $dr.GetValue(0) + $MachineName = $dr.GetValue(1) + $IsClustered = $dr.GetValue(2) + } + + $dr.Close() + $sqlconn.Close() + + # Get processors information + $CPU = Get-WmiObject -Class Win32_Processor + + # Get Computer model information + $OS_Info = Get-WmiObject -Class Win32_ComputerSystem + + # Reset number of cores and use count for the CPUs counting + $CPUs = 0 + $Cores = 0 + + foreach ($Processor in $CPU) { + $CPUs++ + # Count the total number of cores + $Cores += $Processor.NumberOfCores + } + + Write-Host "ServerName: $ServerName" + Write-Host "Model: $($OS_Info.Model)" + Write-Host "InstanceName: $SQLInstance" + Write-Host "DataSource: $($sqlconn.DataSource)" + Write-Host "Edition: $SQLEdition" + Write-Host "SocketNumber: $CPUs" + Write-Host "TotalCores: $Cores" + $CoresPerSocketCPUsRatio = $Cores / $CPUs + Write-Host "Cores per Socket/CPUs Ratio: $CoresPerSocketCPUsRatio" + $ResumeCapacityLimits = + if ($SQLEdition -like "Developer*") { "Max SQL Server compute capacity: OS max" } + elseif ($SQLEdition -like "Express*") { "Max SQL Server compute capacity: 1 sockets or 4 cores" } + elseif ($SQLEdition -like "Standard*") { "Max SQL Server compute capacity: Lesser of 4 sockets or 24 cores" } + elseif ($SQLEdition -like "Web*") { "Max SQL Server compute capacity Lesser of 4 sockets or 16 cores" } + else { "SQL edition not detected" } + Write-Host "ResumeCapacityLimits: $ResumeCapacityLimits" + } +} + +Get-MSSQLVersion \ No newline at end of file From f514812297d31d2a06ea291b56fdb2e35035993f Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:46:24 +0000 Subject: [PATCH 26/43] Update ./scripts/Collectors/Collect Licensing 3 Exchange.ps1 --- .../Collect Licensing 3 Exchange.ps1 | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 scripts_staging/Collectors/Collect Licensing 3 Exchange.ps1 diff --git a/scripts_staging/Collectors/Collect Licensing 3 Exchange.ps1 b/scripts_staging/Collectors/Collect Licensing 3 Exchange.ps1 new file mode 100644 index 00000000..81d9dddd --- /dev/null +++ b/scripts_staging/Collectors/Collect Licensing 3 Exchange.ps1 @@ -0,0 +1,38 @@ +<# +.SYNOPSIS + Retrieves the number of Exchange mailboxes for licensing compliance reporting. + +.DESCRIPTION + This script uses the Exchange Management Shell to determine the number of mailboxes + associated with a specific Exchange Server CAL (Client Access License), + such as the "Exchange Server 2016 Standard CAL." It ensures the Exchange snap-in is loaded and + captures the mailbox count for licensing purposes. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.TODO + Extend support to handle multiple CAL types dynamically. + +#> + +function Get-ExchangeMailboxCount { + # Launch the Exchange Management Shell + Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn -ErrorAction SilentlyContinue + + # Check if the Exchange snap-in is available + if (Get-PSSnapin -Registered | Where-Object { $_.Name -eq 'Microsoft.Exchange.Management.PowerShell.SnapIn' }) { + try { + # Run the command directly in the Exchange Management Shell and capture the count + $mailboxCount = (Get-ExchangeServerAccessLicenseUser -LicenseName "Exchange Server 2016 Standard CAL" | Measure-Object).Count + "Number of Exchange Mailboxes: $mailboxCount" + } catch { + "Error running command: $_" + } + } else { + "" + } +} +Get-ExchangeMailboxCount \ No newline at end of file From 4b9f23543d4978c8a633479fb8406e28e96942cc Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:52:15 +0000 Subject: [PATCH 27/43] Update ./scripts/Collectors/Collect Licensing 4 RDS.ps1 --- .../Collectors/Collect Licensing 4 RDS.ps1 | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 scripts_staging/Collectors/Collect Licensing 4 RDS.ps1 diff --git a/scripts_staging/Collectors/Collect Licensing 4 RDS.ps1 b/scripts_staging/Collectors/Collect Licensing 4 RDS.ps1 new file mode 100644 index 00000000..d5fe61b8 --- /dev/null +++ b/scripts_staging/Collectors/Collect Licensing 4 RDS.ps1 @@ -0,0 +1,35 @@ +<# +.SYNOPSIS + Checks if the Remote Desktop Services role is installed and retrieves RDS license key pack details. + +.DESCRIPTION + This script verifies whether the Remote Desktop Services role is installed on the local machine. + If installed, it retrieves information about RDS license key packs, including details such as product version, + license type, total licenses, available licenses, and issued licenses. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.TODO + Extend reporting to include CAL types and expiration details. +#> + + +try { + # Check if the Remote Desktop Services role is installed + $rdsRoleInstalled = Get-Service -Name TermServLicensing -ErrorAction Stop + # If the service is not installed, display a message and return + if ($rdsRoleInstalled -eq $null -or $rdsRoleInstalled.Installed -eq $false) { + #"TermServLicensing service is not installed." + return + } + # Get information about RDS license key packs + Get-WmiObject Win32_TSLicenseKeyPack | + Where-Object { $_.ProductVersion -like "*Windows Server*" } | + Select-Object PSComputerName, KeyPackId, ProductVersion, TypeAndModel, TotalLicenses, AvailableLicenses, IssuedLicenses +} catch { + # If an error occurs, display the error message + #"Error: $_" +} \ No newline at end of file From df1a574e8fcaf464918126edccdd499db95df260 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:52:18 +0000 Subject: [PATCH 28/43] Update ./scripts/Collectors/Collect Licensing 5 Office.ps1 --- .../Collectors/Collect Licensing 5 Office.ps1 | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 scripts_staging/Collectors/Collect Licensing 5 Office.ps1 diff --git a/scripts_staging/Collectors/Collect Licensing 5 Office.ps1 b/scripts_staging/Collectors/Collect Licensing 5 Office.ps1 new file mode 100644 index 00000000..f86cd0c5 --- /dev/null +++ b/scripts_staging/Collectors/Collect Licensing 5 Office.ps1 @@ -0,0 +1,19 @@ +<# +.SYNOPSIS + Retrieves licensing information for installed Microsoft Office products. + +.DESCRIPTION + This script uses the `Get-CimInstance` cmdlet to query the `SoftwareLicensingProduct` class for + details about installed Microsoft Office products with active licenses. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + + +#> + +Get-CimInstance -ClassName SoftwareLicensingProduct | where {$_.name -like "*office*" -and $_.LicenseStatus -gt 0 }| select Name,description,LicenseStatus,ProductKeyChannel,PartialProductKey \ No newline at end of file From 7a2d483b550bd5f85d71ce9bb904533d53c2d82d Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:12:41 +0000 Subject: [PATCH 29/43] Update ./scripts/Checks/DFS replication.ps1 --- scripts_staging/Checks/DFS replication.ps1 | 133 +++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 scripts_staging/Checks/DFS replication.ps1 diff --git a/scripts_staging/Checks/DFS replication.ps1 b/scripts_staging/Checks/DFS replication.ps1 new file mode 100644 index 00000000..a44797f5 --- /dev/null +++ b/scripts_staging/Checks/DFS replication.ps1 @@ -0,0 +1,133 @@ +<# +.SYNOPSIS + Monitors DFS Replication backlog and generates status based on the file count in the backlog for specified replication groups. + +.DESCRIPTION + This script checks the DFS Replication backlog for specified replication groups using WMI queries and the 'dfsrdiag' command. + It generates success, warning, or error statuses based on the backlog file count, helping to monitor replication health. + +.PARAMETER ReplicationGroupList + An array of DFS Replication Group names to monitor. If not specified, all groups will be checked. + This can be specified through the variable `ReplicationGroupList`. + +.EXAMPLE + ReplicationGroupList = @("Group1", "Group2") + This will check the backlog for "Group1" and "Group2" replication groups. + +.NOTES + Author: matty-uk + Date: ???? + Usefull links: + https://exchange.nagios.org/directory/Addons/Monitoring-Agents/DFSR-Replication-and-BackLog/details + #public + +.CHANGELOG + 01.01.24 SAN Re-implementation for rmm + 12.12.24 SAN code cleanup + +.TODO + Add additional options for backlog threshold customization. + move list to env + +#> + + + +# Define parameter for specifying replication groups (default is an empty array) +Param ( + [String[]]$ReplicationGroupList = @("") # Default is no specific group +) + +# Retrieve all DFS Replication Group configurations via WMI +$ReplicationGroups = Get-WmiObject -Namespace "root\MicrosoftDFS" -Query "SELECT * FROM DfsrReplicationGroupConfig" + +# Filter replication groups if specific group names are provided +if ($ReplicationGroupList) { + $FilteredReplicationGroups = @() + foreach ($ReplicationGroup in $ReplicationGroupList) { + $FilteredReplicationGroups += $ReplicationGroups | Where-Object { $_.ReplicationGroupName -eq $ReplicationGroup } + } + + # Exit with UNKNOWN status if no groups match + if ($FilteredReplicationGroups.Count -eq 0) { + Write-Host "UNKNOWN: None of the specified group names were found." + exit 3 + } else { + $ReplicationGroups = $FilteredReplicationGroups + } +} + +# Initialize counters for success, warning, and error +$SuccessCount = 0 +$WarningCount = 0 +$ErrorCount = 0 + +# Initialize an array to store output messages +$OutputMessages = @() + +# Iterate through each DFS Replication Group +foreach ($ReplicationGroup in $ReplicationGroups) { + # Query for DFS Replicated Folder configurations for the current replication group + $ReplicatedFoldersQuery = "SELECT * FROM DfsrReplicatedFolderConfig WHERE ReplicationGroupGUID='" + $ReplicationGroup.ReplicationGroupGUID + "'" + $ReplicatedFolders = Get-WmiObject -Namespace "root\MicrosoftDFS" -Query $ReplicatedFoldersQuery + + # Query for DFS Replication Connection configurations for the current replication group + $ReplicationConnectionsQuery = "SELECT * FROM DfsrConnectionConfig WHERE ReplicationGroupGUID='" + $ReplicationGroup.ReplicationGroupGUID + "'" + $ReplicationConnections = Get-WmiObject -Namespace "root\MicrosoftDFS" -Query $ReplicationConnectionsQuery + + # Iterate through each DFS Replication Connection for the current replication group + foreach ($ReplicationConnection in $ReplicationConnections) { + $ConnectionName = $ReplicationConnection.PartnerName + + # Check if the connection is enabled + if ($ReplicationConnection.Enabled -eq $True) { + # Iterate through each DFS Replicated Folder for the current connection + foreach ($ReplicatedFolder in $ReplicatedFolders) { + $ReplicationGroupName = $ReplicationGroup.ReplicationGroupName + $ReplicatedFolderName = $ReplicatedFolder.ReplicatedFolderName + + # Execute the 'dfsrdiag' command to get backlog information + $BacklogCommand = "dfsrdiag Backlog /RGName:'$ReplicationGroupName' /RFName:'$ReplicatedFolderName' /SendingMember:$ConnectionName /ReceivingMember:$env:ComputerName" + $BacklogOutput = Invoke-Expression -Command $BacklogCommand + + $BacklogFileCount = 0 + # Parse the 'dfsrdiag' output to retrieve the backlog file count + foreach ($Item in $BacklogOutput) { + if ($Item -ilike "*Backlog File count*") { + $BacklogFileCount = [int]$Item.Split(":")[1].Trim() + } + } + + # Generate status messages based on backlog file count and update counters + if ($BacklogFileCount -eq 0) { + $OutputMessages += "OK: $BacklogFileCount files in backlog for $ConnectionName->$env:ComputerName in $ReplicationGroupName" + $SuccessCount++ + } elseif ($BacklogFileCount -lt 10) { + $OutputMessages += "WARNING: $BacklogFileCount files in backlog for $ConnectionName->$env:ComputerName in $ReplicationGroupName" + $WarningCount++ + } else { + $OutputMessages += "CRITICAL: $BacklogFileCount files in backlog for $ConnectionName->$env:ComputerName in $ReplicationGroupName" + $ErrorCount++ + } + } + } + } +} + +# Generate the final status based on the success, warning, and error counters +if ($ErrorCount -gt 0) { + Write-Host "CRITICAL: $ErrorCount errors, $WarningCount warnings, and $SuccessCount successful replications." + Write-Host "$OutputMessages" + $host.SetShouldExit(2) + exit 2 +} elseif ($WarningCount -gt 0) { + Write-Host "WARNING: $WarningCount warnings, and $SuccessCount successful replications." + Write-Host "$OutputMessages" + $host.SetShouldExit(1) + exit 1 +} else { + Write-Host "OK: $SuccessCount successful replications." + Write-Host "$OutputMessages" + $host.SetShouldExit(0) + exit 0 +} From ace1448ccca8733271ed8cf51f4026c78c968cc4 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:12:43 +0000 Subject: [PATCH 30/43] Update ./scripts/Checks/Disk Free Space.ps1 --- scripts_staging/Checks/Disk Free Space.ps1 | 99 ++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 scripts_staging/Checks/Disk Free Space.ps1 diff --git a/scripts_staging/Checks/Disk Free Space.ps1 b/scripts_staging/Checks/Disk Free Space.ps1 new file mode 100644 index 00000000..2f71887b --- /dev/null +++ b/scripts_staging/Checks/Disk Free Space.ps1 @@ -0,0 +1,99 @@ +<# +.SYNOPSIS + Disk Space Check Script + +.DESCRIPTION + This PowerShell script checks the free disk space on local drives, excluding network drives + and optionally specified drives to ignore, + and exits with different codes based on warning and error thresholds. + +.PARAMETER warningThreshold + The percentage of free disk space at which a warning is issued. Default is 10%. + +.PARAMETER errorThreshold + The percentage of free disk space at which an error is issued. Default is 5%. + +.PARAMETER ignoreDisks + An array of drive letters representing the disks to ignore during the disk space check. + +.EXAMPLE + -warningThreshold 15 -errorThreshold 10 + Checks disk space with custom warning (15%) and error (10%) thresholds. + + -ignoreDisks "D:", "E:" + Checks disk space excluding drives D: and E: from the check. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + +.TODO + Add debug flag + move flags to env + +#> + + +param( + [int]$warningThreshold = 10, + [int]$errorThreshold = 5, + [string[]]$ignoreDisks = @() +) + +function CheckDiskSpace { + [CmdletBinding()] + 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 } + + $failedDrives = @() + $warningDrives = @() + + foreach ($drive in $drives) { + $freeSpacePercent = [math]::Round(($drive.FreeSpace / $drive.Size) * 100, 2) + + if ($freeSpacePercent -lt $errorThreshold) { + $failedDrives += $drive + } + elseif ($freeSpacePercent -lt $warningThreshold) { + $warningDrives += $drive + } + } + + foreach ($drive in $drives) { + $freeSpacePercent = [math]::Round(($drive.FreeSpace / $drive.Size) * 100, 2) + + if ($failedDrives -contains $drive) { + Write-Host "ERROR: $($drive.DeviceID) has less than $($errorThreshold)% free space ($freeSpacePercent%)." + } + elseif ($warningDrives -contains $drive) { + Write-Host "WARNING: $($drive.DeviceID) has less than $($warningThreshold)% free space ($freeSpacePercent%)." + } + else { + Write-Host "OK: $($drive.DeviceID) has $($freeSpacePercent)% 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" + $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" + $host.SetShouldExit(1) + } + else { + # Write-Host "OK: All drives have sufficient free space." + $host.SetShouldExit(0) + } +} + +# Execute the function +CheckDiskSpace \ No newline at end of file From e340df3496cdd013a42485108264042ddcdd1ebb Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:19:15 +0000 Subject: [PATCH 31/43] Update ./scripts/Lab/Fake CheckRandom Alert 2.py --- .../Lab/Fake CheckRandom Alert 2.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 scripts_staging/Lab/Fake CheckRandom Alert 2.py diff --git a/scripts_staging/Lab/Fake CheckRandom Alert 2.py b/scripts_staging/Lab/Fake CheckRandom Alert 2.py new file mode 100644 index 00000000..b16920a3 --- /dev/null +++ b/scripts_staging/Lab/Fake CheckRandom Alert 2.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +#public +import random +import sys + +def main(): + # Randomly choose an exit code with 50% probability for 0 + exit_code = random.choices([0, 1, 2, 3], weights=[0.5, 0.1667, 0.1667, 0.1667])[0] + + # Print the exit code and status message + if exit_code == 0: + print(f"Exit Code: {exit_code} - Resolved") + else: + print(f"Exit Code: {exit_code} - Failed") + + # Print some Lorem Ipsum text + print("Lorem ipsum dolor sit amet, consectetur adipiscing elit.") + print("Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") + print("Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.") + print("Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.") + + # Exit with the chosen code + sys.exit(exit_code) + +if __name__ == "__main__": + main() \ No newline at end of file From 9954e127e72228ab75a9ec25b24db56d582ba549 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:19:18 +0000 Subject: [PATCH 32/43] Update ./scripts/Lab/Fake CheckRandom Alert.py --- scripts_staging/Lab/Fake CheckRandom Alert.py | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 scripts_staging/Lab/Fake CheckRandom Alert.py diff --git a/scripts_staging/Lab/Fake CheckRandom Alert.py b/scripts_staging/Lab/Fake CheckRandom Alert.py new file mode 100644 index 00000000..aac987c6 --- /dev/null +++ b/scripts_staging/Lab/Fake CheckRandom Alert.py @@ -0,0 +1,145 @@ +#!/usr/bin/python3 +import random +import sys +import io +import json +#public + +# Ensure the standard output is set to UTF-8 encoding +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') + +# Utility function to handle safe printing of Unicode +def safe_print(s): + try: + print(s) + except UnicodeEncodeError: + # Replace unprintable characters with replacement character + print(s.encode('utf-8', 'replace').decode('utf-8')) + +# Define problematic strings and edge cases +unicode_tests = [ + # Unicode and Special Cases + "Unicode test: \U0001D11E\u266B", # Musical symbol and note + "Contains special chars: !@#$%^&*()_+|}{:\"?><,./;'[]\\=-`~", + "Embedded newlines\nshould be handled", + "Embedded quotes \"double\" and 'single' quotes", + "JSON breaking chars: {}[]:,", + "Non-printable characters: \x1B \x07 \x00", + "Backslashes \\ and forward slashes /", + "Tab characters\t should be included", + "Carriage return\r characters", + "Mixed escape sequences \n \r \t \b \f", + "Control characters: " + "".join(chr(i) for i in range(32)), + "Combining characters: a\u0301 e\u0301 i\u0301 o\u0301 u\u0301", # á é í ó ú + "Right-to-left text: \u202Ethis text is rtl", + "Null character: \x00 in the middle", + "Special Unicode: \uFFFF \uFFFE", + "Mathematical symbols: ∑∏∫∬∭", + "Currency symbols: ¢£¤¥", + "Different spaces: \u2000 \u2001 \u2002 \u2003", + "Zero-width spaces: \u200B\u200C\u200D\uFEFF", + "Emoji sequence: 🧩🧪🧫", + "Surrogate pairs: \U0001F600\U0001F606", # 😀😆 + "Large code points: \U0002A7D4 \U0001F6D0", # Mathematical Operators, Shield + "Math symbols: ∆√∛∞", + "Text direction: \u061C\u202D\u202C", + "High surrogate: \uD83D\uDE00", # 😀 + "Low surrogate: \uDE00", # Low surrogate to pair with high surrogate + "Complex sequences: \uD83C\uDF1F\uD83C\uDF1F\uD83C\uDF1F", # Multiple Sun Symbols + "Combining diacritics: \u0041\u030A\u0042\u0308\u0043\u0327", # Å B̈ Ç + "Zero-width joiners: \u200D\u200D\u200D", # Zero-width joiner repetition + "Double-byte characters: \u4F60\u597D\uFF0C\u4E16\u754C", # Chinese characters + + # Escaping Characters + "Escaping quotes: \"\" \"'\" \\'", + # "Escaping backslashes: \\\\ \\\\ \\\\\\\\\\\", + "Escaping newlines: Line1\\nLine2", + "Escaping carriage returns: Line1\\rLine2", + + # Extremely Large Strings + "Extremely large string: " + "x" * 10000, + "Extremely large multiline string:\n" + "\n".join(["This is a test line."] * 500), + + # Additional Special Characters + "Rare symbols: ⧫ ⍟ ⎈ ⍾ ⏚", + "Technical symbols: ⌘ ⌥ ⌫ ⌦ ⎋", + "Mathematical operators: ⊥ ⊗ ⊙ ⊚ ⊻", + + # More Languages and Scripts + "Armenian: բարև աշխարհ", # Hello, World + "Bengali: হ্যালো বিশ্ব", # Hello, World + "Georgian: გამარჯობა მსოფლიო", # Hello, World + "Gujarati: નમસ્તે વિશ્વ", # Hello, World + "Hmong: Nyob zoo ntiaj teb", # Hello, World + "Javanese: Halo Dunia", # Hello, World + "Kannada: ನಮಸ್ಕಾರ ಜಗತ್ತಿಗೆ", # Hello, World + "Lao: ສະບາຍດີໂລກ", # Hello, World + "Malayalam: ഹലോ ലോകം", # Hello, World + "Myanmar: မင်္ဂလာပါ ကမ္ဘာလောက", # Hello, World + "Nepali: नमस्कार संसार", # Hello, World + "Sinhala: හෙලෝ ලෝකය", # Hello, World + "Tamil: வணக்கம் உலகம்", # Hello, World + "Telugu: హలో వరల్డ్", # Hello, World + "Tibetan: བཀྲིས་གནང་བརྗེད་", # Hello, World + "Uzbek: Salom Dunyo", # Hello, World + + # Extreme Unicode Edge Cases + "Extremely high Unicode: \U0010FFFF", # Highest code point in Unicode + "Extremely low Unicode: \u0001", # Lowest code point in Unicode + "Middle of Unicode range: \U00010000", # Supplementary Planes + + # Text Layout and Formatting + "Bidirectional text: \u05D0\u05D1\u05D2\u200F\u202E\u05D3\u05D4\u05D5", # Hebrew text with RTL overrides + "Zalgo text: H̵e̸l̷l̶o̴ W̵o̸r̷l̶d̴", # Zalgo effect + "Invisible characters: \u200B\u200C\u200D\uFEFF", # Invisible characters in between + + # Complex Combining Sequences + "Multiple combining diacritics: a\u0300\u0301\u0302\u0303\u0304\u0305", # Combining diacritics over a base character + "Overlapping combining characters: a\u0336\u0336\u0336\u0336", # Multiple strikethroughs + + # Additional Complex Cases + "Surrogates edge cases: \uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03", # Multiple emojis + "Mirrored text: \u0623\u0646\u0633\u0627\u0646", # Arabic script (human) + "Vowel diacritics: a\u0316\u0317\u0318\u0319\u031A", # Various vowel diacritics + "Overlap text: ᎣᏢᏯᏪᏮ", # Cherokee text + "Long text with mixed scripts: 你好, こんにちは, 안녕하세요, Hello!", # Multiple scripts + "Emoji with skin tones: 👋🏻👋🏼👋🏽👋🏾👋🏿", # Wave emoji with skin tones + "Complex formatting: \u2063\u2064\u2065\u2066", # Invisible and non-visible formatting characters + "Extremely high code point combined with low: \u0001\U0010FFFF", # Low and high code points combined + "Languages with various punctuation: ÀÀÀÀ àààà ¡Hola! ¿Cómo estás?", # Punctuation and accents + + # SQL Injection and Special Characters + "Basic SQL Injection: ' OR '1'='1", + "SQL Injection with comment: '; DROP TABLE users;--", + "SQL Injection with nested query: ' UNION SELECT null, username, password FROM users--", + "SQL Injection with hex encoding: 0x27 UNION SELECT null, username, password FROM users--", + "SQL Injection with multiple queries: ' ; SELECT * FROM users;--", + "SQL Injection with special characters: ' OR 1=1; --", + "SQL Injection with Unicode: ' OR 1=1 -- 𝒜𝒷𝒸", + "SQL Injection with long payload: " + "a" * 10000, + + # PostgreSQL Specific Cases + "PostgreSQL large string: " + "a" * 10000, + "PostgreSQL special chars: \u00A9 \u00AE \u20AC", + "PostgreSQL JSON injection: {\"key\": \"value\", \"test\": 1}", + "PostgreSQL JSON special chars: {\"key\": \"value\\nwith\\nnewlines\", \"test\": 1}", + "PostgreSQL complex JSON: {\"key\": {\"subkey\": [1, 2, 3], \"otherkey\": true}}", + "PostgreSQL JSON with Unicode: {\"key\": \"value\", \"emoji\": \"\U0001F600\"}", + "PostgreSQL JSON with SQL Injection: {\"key\": \"value' OR '1'='1\"}", +] + +# Randomly set the exit code to 1 or 2 +exit_code = random.choice([1, 2]) + +# Print out the Unicode test strings +for test in unicode_tests: + safe_print(test) + +# Output the exit code to a JSON file +output = {"exit_code": exit_code} + +with open("output.json", "w", encoding='utf-8') as f: + json.dump(output, f, ensure_ascii=False) + +# Exit with the chosen code +sys.exit(exit_code) \ No newline at end of file From 67b3eb309133469355ea3b7f701e852e7677c470 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:25:33 +0000 Subject: [PATCH 33/43] Update ./scripts/Tools/Force Azureo365 AD sync.ps1 --- .../Tools/Force Azureo365 AD sync.ps1 | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 scripts_staging/Tools/Force Azureo365 AD sync.ps1 diff --git a/scripts_staging/Tools/Force Azureo365 AD sync.ps1 b/scripts_staging/Tools/Force Azureo365 AD sync.ps1 new file mode 100644 index 00000000..c79c43ef --- /dev/null +++ b/scripts_staging/Tools/Force Azureo365 AD sync.ps1 @@ -0,0 +1,35 @@ +<# +.SYNOPSIS + Initiates an Azure AD synchronization cycle. + +.DESCRIPTION + This script checks if the ADSync module is loaded, and if not, imports it. + It then triggers a delta synchronization cycle using the `Start-ADSyncSyncCycle` command. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + 12.12.24 Simple polish + +#> + +# Check if the ADSync module is already imported, if not, import it +if (-not (Get-Module -Name 'ADSync' -ErrorAction SilentlyContinue)) { + Write-Host "Importing the Azure AD Sync module..." + Import-Module ADSync +} + +try { + Write-Host "Starting Azure AD Delta Synchronization..." + Start-ADSyncSyncCycle -PolicyType Delta + Write-Host "Azure AD sync initiated successfully!" + Write-Host "Please check the Azure AD Connect Health for status." + +} +catch { + Write-Host "An error occurred while initiating the Azure AD sync: $_" + Write-Host "Please check the Azure AD Connect logs for more details." +} From a0f05e81c7a4f87c1f682141440b993bb022ce81 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:31:22 +0000 Subject: [PATCH 34/43] Update ./scripts/Collectors/get Domains or Workgroup name.ps1 --- .../get Domains or Workgroup name.ps1 | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 scripts_staging/Collectors/get Domains or Workgroup name.ps1 diff --git a/scripts_staging/Collectors/get Domains or Workgroup name.ps1 b/scripts_staging/Collectors/get Domains or Workgroup name.ps1 new file mode 100644 index 00000000..80a8bc4d --- /dev/null +++ b/scripts_staging/Collectors/get Domains or Workgroup name.ps1 @@ -0,0 +1,28 @@ +<# +.SYNOPSIS + Retrieves and displays the domain or workgroup name of the computer. + +.DESCRIPTION + This script checks if the computer is part of a domain or a workgroup. + If the computer is part of a domain, it outputs the domain name. + Otherwise, it outputs the workgroup name. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + + +#> + +# Check if the computer is a member of a domain or workgroup +$computerInfo = Get-WmiObject Win32_ComputerSystem + +if ($computerInfo.PartOfDomain -eq $true) { + Write-Host "D: $($computerInfo.Domain)" +} else { + $workgroupName = $computerInfo.Workgroup + Write-Host "W: $workgroupName" +} \ No newline at end of file From f64f73f27b1c97cf12bd30eef1344ae071910a4d Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:31:52 +0000 Subject: [PATCH 35/43] Update ./scripts/Tools/Get last shutdown info.ps1 --- .../Tools/Get last shutdown info.ps1 | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 scripts_staging/Tools/Get last shutdown info.ps1 diff --git a/scripts_staging/Tools/Get last shutdown info.ps1 b/scripts_staging/Tools/Get last shutdown info.ps1 new file mode 100644 index 00000000..8749570c --- /dev/null +++ b/scripts_staging/Tools/Get last shutdown info.ps1 @@ -0,0 +1,41 @@ +<# +.SYNOPSIS + Retrieves and displays system uptime and shutdown event information. + +.DESCRIPTION + This script retrieves the last boot time of the system, calculates the uptime (in days, hours, and minutes), + and retrieves the most recent shutdown event from the system's event log (EventID 1074). + +.NOTES + Author: SAN + Date: 03.10.24 + #public + +.CHANGELOG + SAN 12.12.24 Code cleanup + +#> + +$lastBootTime = (Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime +$uptime = (Get-Date) - $lastBootTime +$shutdownEvent = Get-WinEvent -LogName System -FilterXPath "*[System/EventID=1074]" | Select-Object -First 1 + +Write-Output "===========================" +Write-Output " Last Reboot Information" +Write-Output "===========================" + +Write-Output "Last Boot Time : $($lastBootTime)" +Write-Output "Uptime (since last boot) : $($uptime.Days) days, $($uptime.Hours) hours, $($uptime.Minutes) minutes" +Write-Output "Event Log Time : $($shutdownEvent.TimeCreated)" + +Write-Output "===========================" +Write-Output " All Event Properties" +Write-Output "===========================" + +Write-Output "Initiating Process/Executable : $($shutdownEvent.Properties[0].Value)" +Write-Output "Initiating Machine : $($shutdownEvent.Properties[1].Value)" +Write-Output "Shutdown Reason : $($shutdownEvent.Properties[2].Value)" +Write-Output "Shutdown Code : $($shutdownEvent.Properties[3].Value)" +Write-Output "Shutdown Type : $($shutdownEvent.Properties[4].Value)" +Write-Output "Additional Info : $($shutdownEvent.Properties[5].Value)" +Write-Output "User Account : $($shutdownEvent.Properties[6].Value)" From ebcaa92a5192ca55663a9d520fe28a2d34daee30 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:39:13 +0000 Subject: [PATCH 36/43] Update ./scripts/Collectors/OS Install Date.ps1 --- .../Collectors/OS Install Date.ps1 | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 scripts_staging/Collectors/OS Install Date.ps1 diff --git a/scripts_staging/Collectors/OS Install Date.ps1 b/scripts_staging/Collectors/OS Install Date.ps1 new file mode 100644 index 00000000..309efa17 --- /dev/null +++ b/scripts_staging/Collectors/OS Install Date.ps1 @@ -0,0 +1,23 @@ +<# +.SYNOPSIS + Retrieves and formats the installation date of the operating system. + +.DESCRIPTION + This script fetches the installation date of the current Windows operating system and + formats it into a "dd/MM/yyyy" format, then outputs the formatted date to the console. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + + +#> + + +$osInfo = Get-WmiObject Win32_OperatingSystem +$installDate = $osInfo.ConvertToDateTime($osInfo.InstallDate) +$formattedDate = $installDate.ToString("dd/MM/yyyy") +Write-Host "$formattedDate" \ No newline at end of file From 7f0ace1e2a5e11d94c507470e07635e3f4104f60 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:39:46 +0000 Subject: [PATCH 37/43] Update ./scripts/Tools/Get logon events.ps1 --- scripts_staging/Tools/Get logon events.ps1 | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 scripts_staging/Tools/Get logon events.ps1 diff --git a/scripts_staging/Tools/Get logon events.ps1 b/scripts_staging/Tools/Get logon events.ps1 new file mode 100644 index 00000000..e8f3cdc5 --- /dev/null +++ b/scripts_staging/Tools/Get logon events.ps1 @@ -0,0 +1,40 @@ +<# +.SYNOPSIS + Retrieves successful user logon events in the last 24 hours, + filtering for interactive logons and excluding system accounts. + +.DESCRIPTION + This script queries the Security event log for event ID 4624, which corresponds to successful user logons. + It filters the results to include only logon events from the last 24 hours and focuses on interactive logons + (LogonType 2). The script excludes events where the username is "NT AUTHORITY\SYSTEM". + +.NOTES + Author: SAN + Date: 19.09.24 + #public + +.CHANGELOG + + +.TODO + Add error handling for event log retrieval. + Add support for additional logon types or custom filters if required. +#> + +Get-WinEvent -FilterHashtable @{ + LogName = 'Security' + Id = 4624 + StartTime = (Get-Date).AddHours(-24) +} | +ForEach-Object { + $Event = [xml]$_.ToXml() + [pscustomobject]@{ + TimeCreated = $_.TimeCreated + Username = $Event.Event.EventData.Data[5].'#text' + LogonType = $Event.Event.EventData.Data[8].'#text' + IPAddress = $Event.Event.EventData.Data[18].'#text' + } +} | +Where-Object { + $_.Username -ne "NT AUTHORITY\SYSTEM" -and $_.LogonType -eq "2" +} \ No newline at end of file From 1685765f8d728e62c5f9a4fc4266a139858cf602 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:45:43 +0000 Subject: [PATCH 38/43] Update ./scripts/Collectors/Retrieve all IIS bindings.ps1 --- .../Collectors/Retrieve all IIS bindings.ps1 | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 scripts_staging/Collectors/Retrieve all IIS bindings.ps1 diff --git a/scripts_staging/Collectors/Retrieve all IIS bindings.ps1 b/scripts_staging/Collectors/Retrieve all IIS bindings.ps1 new file mode 100644 index 00000000..ea3be3bd --- /dev/null +++ b/scripts_staging/Collectors/Retrieve all IIS bindings.ps1 @@ -0,0 +1,64 @@ +<# +.SYNOPSIS + This script retrieves IIS bindings, extracts and sorts unique domain names from the bindings. + +.DESCRIPTION + The script imports the WebAdministration module, retrieves all IIS bindings, + and extracts unique domain names from the binding information. + The script excludes wildcard bindings and invalid domain names. + It then outputs the sorted list of unique domain names, with optional debugging information if the debug flag is set. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.CHANGELOG + + +.TODO + set debug flag in env + more gracefully handle execution on non-iis devices +#> + + +# Set the debug flag +$debug = 0 + +# Import the WebAdministration module +Import-Module WebAdministration + +# Retrieve all IIS bindings +$bindings = Get-WebBinding + +# Output the initial bindings for debugging +if ($debug -eq 1) { + Write-Output "Initial Bindings:" + $bindings | ForEach-Object { Write-Output $_ } +} + +# Create a hash table to store unique domain names +$uniqueDomains = @{} + +# Process each binding +foreach ($binding in $bindings) { + $bindingInformation = $binding.bindingInformation + $hostname = $bindingInformation -replace ".*:(.*?)(:\d+)?$", '$1' # Extract only the domain part + + # Only add if the hostname is not empty, not a wildcard, and is a valid domain name + if ($hostname -ne "" -and $hostname -ne "*" -and $hostname -match '^(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$') { + $uniqueDomains[$hostname] = $null + } +} + +# Output the unique domain names for debugging +if ($debug -eq 1) { + Write-Output "Unique Domain Names:" + $uniqueDomains.Keys | ForEach-Object { Write-Output $_ } +} + +# Sort unique domain names alphabetically +$sortedDomains = $uniqueDomains.Keys | Sort-Object + +# Output the sorted unique domain names +$sortedDomains | ForEach-Object { Write-Output $_ } \ No newline at end of file From a5d525f7b36c05daac3908cd5a1c23e245985c76 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:52:03 +0000 Subject: [PATCH 39/43] Update ./scripts/Lab/Send mail test.ps1 --- scripts_staging/Lab/Send mail test.ps1 | 73 ++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 scripts_staging/Lab/Send mail test.ps1 diff --git a/scripts_staging/Lab/Send mail test.ps1 b/scripts_staging/Lab/Send mail test.ps1 new file mode 100644 index 00000000..774b56db --- /dev/null +++ b/scripts_staging/Lab/Send mail test.ps1 @@ -0,0 +1,73 @@ +<# +.SYNOPSIS + Sends an email using SMTP commands over a TCP connection. + +.DESCRIPTION + This script sends an email using minimal SMTP commands via a TCP connection. + It retrieves configuration details (SMTP server, port, sender, recipient, subject, and body) + from environment variables. + +.EXEMPLE + SMTP_SERVER=XXXXX.mail.protection.outlook.com + SMTP_PORT=25 + EMAIL_FROM=whatever@domain1.asdf + EMAIL_TO=whatever@domain2.asdf + EMAIL_SUBJECT=Test Email via TCP + EMAIL_BODY=This is a test email sent using SMTP commands over TCP. + +.NOTE + Author: SAN + Date: 03.12.24 + #public + +.CHANGELOG + SAN 12.12.24 Fixed HELO to extract domain from "to" +#> + + +$smtpServer = $env:SMTP_SERVER +$smtpPort = [int]$env:SMTP_PORT +$from = $env:EMAIL_FROM +$to = $env:EMAIL_TO +$subject = $env:EMAIL_SUBJECT +$body = $env:EMAIL_BODY + +$domain = ($to -split '@')[1] + +$tcpClient = New-Object System.Net.Sockets.TcpClient($smtpServer, $smtpPort) +$stream = $tcpClient.GetStream() +$writer = New-Object System.IO.StreamWriter($stream) +$reader = New-Object System.IO.StreamReader($stream) + +function Send-SMTPCommand { + param ([string]$command) + if ($command) { + $writer.WriteLine($command) + $writer.Flush() + } + $response = $reader.ReadLine() + Write-Host "SERVER RESPONSE: $response" + return $response +} + +Send-SMTPCommand "" +Send-SMTPCommand "HELO $domain" +Send-SMTPCommand "MAIL FROM:<$from>" +Send-SMTPCommand "RCPT TO:<$to>" +Send-SMTPCommand "DATA" +Send-SMTPCommand @" +From: $from +To: $to +Subject: $subject + +$body +. +"@ +Send-SMTPCommand "QUIT" + +$writer.Close() +$reader.Close() +$stream.Close() +$tcpClient.Close() + +Write-Host "Email sent!" From 6f7e4d9bf47af3bab90f9134bf2c54222d1e40d4 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:16:15 +0000 Subject: [PATCH 40/43] Update ./scripts/Checks/is RDP port ok.ps1 --- scripts_staging/Checks/is RDP port ok.ps1 | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts_staging/Checks/is RDP port ok.ps1 b/scripts_staging/Checks/is RDP port ok.ps1 index dff54378..6a1fa48b 100644 --- a/scripts_staging/Checks/is RDP port ok.ps1 +++ b/scripts_staging/Checks/is RDP port ok.ps1 @@ -11,6 +11,10 @@ Author: SAN Date: 26.09.2024 #public + +.CHANGELOG + 12.12.24 SAN Changed outputs + #> $port = 3389 @@ -20,9 +24,9 @@ $address = "localhost" if (Get-Command Test-NetConnection -ErrorAction SilentlyContinue) { $tcpConnection = Test-NetConnection -ComputerName $address -Port $port if ($tcpConnection.TcpTestSucceeded) { - Write-Output "Port $port is open." + Write-Output "RDP is open." } else { - Write-Output "Port $port is not open." + Write-Output "Port $port is not open RDP will not work." exit 1 } } else { @@ -30,10 +34,10 @@ if (Get-Command Test-NetConnection -ErrorAction SilentlyContinue) { try { $tcpClient = New-Object System.Net.Sockets.TcpClient $tcpClient.Connect($address, $port) - Write-Output "Port $port is open." + Write-Output "RDP is open but TNC does not work." $tcpClient.Close() } catch { - Write-Output "Port $port is not open." + Write-Output "Port $port is not open and TNC does not work." exit 1 } } \ No newline at end of file From bacb2d219933b2da9a7a80582f24df4f2a4685ac Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:28:15 +0000 Subject: [PATCH 41/43] Update ./scripts/Checks/Boot mode.ps1 --- scripts_staging/Checks/Boot mode.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts_staging/Checks/Boot mode.ps1 b/scripts_staging/Checks/Boot mode.ps1 index c5dbe18e..b77e31df 100644 --- a/scripts_staging/Checks/Boot mode.ps1 +++ b/scripts_staging/Checks/Boot mode.ps1 @@ -12,7 +12,7 @@ #public .CHANGELOG - + 12.12.24 SAN Changed outputs #> @@ -21,9 +21,9 @@ $regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SafeBoot\Option" $safeModeKeyExists = Test-Path $regPath if ($safeModeKeyExists) { - Write-Host "System is booted in Safe Mode." + Write-Host "KO: System is booted in Safe Mode." exit 1 } else { - Write-Host "System is not booted in Safe Mode." + Write-Host "OK: System is not booted in Safe Mode." exit 0 } \ No newline at end of file From c329a155a68dc0e14acec17d7fe8e9a2b639d9ca Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:28:17 +0000 Subject: [PATCH 42/43] Update ./scripts/Checks/Maximum UpTime.ps1 --- scripts_staging/Checks/Maximum UpTime.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts_staging/Checks/Maximum UpTime.ps1 b/scripts_staging/Checks/Maximum UpTime.ps1 index 2cb527cc..f24dce87 100644 --- a/scripts_staging/Checks/Maximum UpTime.ps1 +++ b/scripts_staging/Checks/Maximum UpTime.ps1 @@ -19,6 +19,7 @@ move var to env .CHANGELOG + 12.12.24 SAN Changed outputs #> @@ -39,7 +40,7 @@ if ($uptimeDays -gt $MaxTime) { Write-Output "The computer has an uptime greater than $MaxTime days." exit 1 } else { - Write-Output "Uptime OK" + Write-Output "OK: Uptime is not above max" #Write-Output "The computer has an uptime of $formattedUptime." #Write-Output "The computer has an uptime lower than $MaxTime days." exit 0 From c7ca46519fec7f3b51dd26f3239fcd9682db2042 Mon Sep 17 00:00:00 2001 From: P6g9YHK6 <17877371+P6g9YHK6@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:28:19 +0000 Subject: [PATCH 43/43] Update ./scripts/Checks/SQL Health.ps1 --- scripts_staging/Checks/SQL Health.ps1 | 172 ++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 scripts_staging/Checks/SQL Health.ps1 diff --git a/scripts_staging/Checks/SQL Health.ps1 b/scripts_staging/Checks/SQL Health.ps1 new file mode 100644 index 00000000..254ba924 --- /dev/null +++ b/scripts_staging/Checks/SQL Health.ps1 @@ -0,0 +1,172 @@ +<# +.SYNOPSIS + This script performs health checks on a machine with SQL Server installed. + +.DESCRIPTION + The script checks various aspects of SQL Server health, including version and blocked requests. + It provides a modular approach with separate functions for each check. + +.NOTES + Author: SAN + Date: 01.01.24 + #public + +.TODO + Need to go back to version 1 and implement the missing functions of this check + handle Master-slave monitoring + +.CHANGELOG + SAN 12.12.24 Changed outputs +#> + +function Get-SqlServerVersion { + Write-Host "function Get-SqlServerVersion" + + $computername = $env:computername + $instances = (Get-ItemProperty -Path "HKLM:\Software\Microsoft\Microsoft SQL Server" -Name "InstalledInstances").InstalledInstances + + $errorEncountered = $false # Initialize flag for tracking errors + + foreach ($instance in $instances) { + if ($instance -eq "MSSQLSERVER") { + $serverInstance = "localhost" + } else { + $portsql = (Get-ItemProperty -Path "HKLM:\Software\Microsoft\Microsoft SQL Server\$instance\MSSQLServer\SuperSocketNetLib\Tcp" -Name "TcpPort").TcpPort + $serverInstance = "$computername\$instance,$portsql" + } + + $cmdName = 'Invoke-Sqlcmd' + + if (-not (Get-Command $cmdName -ErrorAction SilentlyContinue)) { + Add-PSSnapin SqlServerCmdletSnapin100 + Add-PSSnapin SqlServerProviderSnapin100 + } + + try { + $version = Invoke-Sqlcmd -ServerInstance $serverInstance -Query "SELECT @@VERSION;" -QueryTimeout 3 -ErrorAction Stop + + if (-not $version) { + Write-Host "Error: SQL Check Failed for $serverInstance" + $errorEncountered = $true # Set flag to indicate error + } else { + Write-Host "OK: $($serverInstance) $($version[0].replace("`n`t"," "))" + } + } catch { + Write-Host "Error: $($_.Exception.Message) for $serverInstance" + $errorEncountered = $true # Set flag to indicate error + } + } + + if ($errorEncountered) { + return "Error" # Return "Error" if any error occurred during the process + } else { + return "OK" # Return "OK" if no errors occurred + } +} + + + + +function Get-BlockedSqlRequests { + Write-Host "function Get-BlockedSqlRequests" + + $computername = $env:computername + $instances = (Get-ItemProperty -Path "HKLM:\Software\Microsoft\Microsoft SQL Server" -Name "InstalledInstances").InstalledInstances + + $errorEncountered = $false # Initialize flag for tracking errors + + foreach ($instance in $instances) { + if ($instance -eq "MSSQLSERVER") { + $serverInstance = "localhost" + } else { + $portsql = (Get-ItemProperty -Path "HKLM:\Software\Microsoft\Microsoft SQL Server\$instance\MSSQLServer\SuperSocketNetLib\Tcp" -Name "TcpPort").TcpPort + $serverInstance = "$computername\$instance,$portsql" + } + + $mysqlrequest = @" + USE master + SELECT db.name DBName, + tl.request_session_id, + wt.blocking_session_id, + OBJECT_NAME(p.OBJECT_ID) BlockedObjectName, + tl.resource_type, + h1.TEXT AS RequestingText, + h2.TEXT AS BlockingTest, + tl.request_mode + FROM sys.dm_tran_locks AS tl + INNER JOIN sys.databases db ON db.database_id = tl.resource_database_id + INNER JOIN sys.dm_os_waiting_tasks AS wt ON tl.lock_owner_address = wt.resource_address + INNER JOIN sys.partitions AS p ON p.hobt_id = tl.resource_associated_entity_id + INNER JOIN sys.dm_exec_connections ec1 ON ec1.session_id = tl.request_session_id + INNER JOIN sys.dm_exec_connections ec2 ON ec2.session_id = wt.blocking_session_id + CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1 + CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2 +"@ + + $cmdName = 'Invoke-Sqlcmd' + + if (-not (Get-Command $cmdName -ErrorAction SilentlyContinue)) { + Add-PSSnapin SqlServerCmdletSnapin100 + Add-PSSnapin SqlServerProviderSnapin100 + } + + try { + $result = Invoke-Sqlcmd -ServerInstance $serverInstance -Query $mysqlrequest -QueryTimeout 60 -ErrorAction Stop + + if (-not $result) { + Write-Host "OK: $($serverInstance) No Blocking Requests" + } else { + Write-Host "Error: Unable to retrieve blocked requests for $($serverInstance). $($result[0].Exception.Message)" + $errorEncountered = $true # Set flag to indicate error + } + } catch { + Write-Host "Error: Unable to retrieve blocked requests for $($serverInstance). $($Error[0].Exception.Message)" + $errorEncountered = $true # Set flag to indicate error + } + } + + if ($errorEncountered) { + return "Error" # Return "Error" if any error occurred during the process + } else { + return "OK" # Return "OK" if no errors occurred + } +} + + +function Check-SqlServerInstallation { + # Check if Invoke-Sqlcmd is available + if (Get-Command Invoke-Sqlcmd -ErrorAction SilentlyContinue) { + return $true + } else { + Write-Host "SQL Server is not installed on this device" + return $false + } +} + +# Check if SQL Server is installed before proceeding with health checks +if (Check-SqlServerInstallation) { + $cmdName = 'Invoke-Sqlcmd' + + # Run each function and report the result + $result1 = Get-SqlServerVersion + $result2 = Get-BlockedSqlRequests + + # Check the results and provide the overall status + if ($result1 -eq "OK" -and $result2 -eq "OK") { + Write-Host "OK: All components are functioning properly" + } else { + $errorComponents = @() + + if ($result1 -ne "OK") { + $errorComponents += "SQL Server Version Check" + } + + if ($result2 -ne "OK") { + $errorComponents += "Blocked SQL Requests Check" + } + + $errorList = $errorComponents -join ", " + Write-Host "Overall Status: Some components encountered errors. Errors in: $errorList" + Exit 1 + } +} \ No newline at end of file